feat(opencode): cut plugin system and remote behavior feeds; add SessionContext assembly and Runner
- delete plugin machinery (loader, install, meta, hooks) and all trigger sites - fold first-party provider auth into static registry (provider/hooks.ts) - remove remote instruction URL fetch, skills remote puller, TUI plugin host - add session/context.ts: single provenance-tagged context assembly point - add session/runner.ts: admit/context/stream/tools loop with compaction policy - relocate audit artifacts to audit/ (FORK-AUDIT, instruction docs as evidence)
This commit is contained in:
@@ -5,7 +5,6 @@ import type { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
import { Agent } from "@/agent/agent"
|
||||
import { MCP } from "@/mcp"
|
||||
import { Permission } from "@/permission"
|
||||
import { Plugin } from "@/plugin"
|
||||
import { Session } from "@/session/session"
|
||||
import { Tool } from "@/tool/tool"
|
||||
import * as Truncate from "@/tool/truncate"
|
||||
@@ -41,12 +40,8 @@ function harness(input: {
|
||||
mcpTools: Record<string, MCP.McpTool>
|
||||
servers: string[]
|
||||
permission?: PermissionV1.Rule[]
|
||||
trigger?: Plugin.Interface["trigger"]
|
||||
}) {
|
||||
return Layer.mergeAll(
|
||||
Layer.mock(Plugin.Service, {
|
||||
trigger: input.trigger ?? (((_name, _input, output) => Effect.succeed(output)) as Plugin.Interface["trigger"]),
|
||||
}),
|
||||
Layer.mock(Truncate.Service, {
|
||||
output: (text: string) => Effect.succeed({ content: text, truncated: false as const }),
|
||||
}),
|
||||
@@ -71,13 +66,12 @@ function build(
|
||||
mcpTools: Record<string, MCP.McpTool>,
|
||||
servers?: string[],
|
||||
permission?: PermissionV1.Rule[],
|
||||
trigger?: Plugin.Interface["trigger"],
|
||||
) {
|
||||
const names = serverNames(mcpTools, servers)
|
||||
return Effect.runPromise(
|
||||
CodeModeTool.pipe(
|
||||
Effect.flatMap(Tool.init),
|
||||
Effect.provide(harness({ mcpTools, servers: names, permission, trigger })),
|
||||
Effect.provide(harness({ mcpTools, servers: names, permission })),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -391,77 +385,6 @@ describe("code mode execute", () => {
|
||||
expect(output.metadata.toolCalls).toEqual([{ tool: "a.tool", status: "error" }])
|
||||
})
|
||||
|
||||
test("child calls fire plugin tool.execute hooks with the MCP key and synthetic parent/N call ids", async () => {
|
||||
const events: { name: string; input: any; output: any }[] = []
|
||||
const trigger = ((name: unknown, input: unknown, output: unknown) =>
|
||||
Effect.sync(() => {
|
||||
events.push({ name: name as string, input, output })
|
||||
return output
|
||||
})) as Plugin.Interface["trigger"]
|
||||
const tool = await build(
|
||||
{
|
||||
a_tool: mcpTool("a", () => ({ content: [{ type: "text", text: "one" }] })),
|
||||
b_tool: mcpTool("b", () => ({ content: [{ type: "text", text: "two" }] })),
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
trigger,
|
||||
)
|
||||
|
||||
const out = await Effect.runPromise(
|
||||
tool.execute({ code: "await tools.a.tool({ x: 1 }); await tools.b.tool({}); return 'done'" }, ctx),
|
||||
)
|
||||
|
||||
expect(out.output).toBe("done")
|
||||
expect(events.map((e) => [e.name, e.input.tool, e.input.callID])).toEqual([
|
||||
["tool.execute.before", "a_tool", "call_code_mode/1"],
|
||||
["tool.execute.after", "a_tool", "call_code_mode/1"],
|
||||
["tool.execute.before", "b_tool", "call_code_mode/2"],
|
||||
["tool.execute.after", "b_tool", "call_code_mode/2"],
|
||||
])
|
||||
const [before, after] = events
|
||||
expect(before!.input.sessionID).toBe(ctx.sessionID)
|
||||
expect(before!.output).toEqual({ args: { x: 1 } })
|
||||
expect(after!.input.args).toEqual({ x: 1 })
|
||||
expect(after!.output).toEqual({ content: [{ type: "text", text: "one" }] })
|
||||
})
|
||||
|
||||
test("a failing before hook fails only that child call as a catchable in-program error", async () => {
|
||||
const trigger = ((name: unknown, input: any, output: unknown) => {
|
||||
if (name === "tool.execute.before" && input.tool === "a_tool") return Effect.die(new Error("hook exploded"))
|
||||
return Effect.succeed(output)
|
||||
}) as Plugin.Interface["trigger"]
|
||||
const called: string[] = []
|
||||
const record = (name: string) => () => {
|
||||
called.push(name)
|
||||
return { content: [{ type: "text", text: "ok" }] }
|
||||
}
|
||||
const tool = await build(
|
||||
{ a_tool: mcpTool("a", record("a")), b_tool: mcpTool("b", record("b")) },
|
||||
undefined,
|
||||
undefined,
|
||||
trigger,
|
||||
)
|
||||
|
||||
const out = await Effect.runPromise(
|
||||
tool.execute(
|
||||
{
|
||||
code: `
|
||||
let caught
|
||||
try { await tools.a.tool({}) } catch (e) { caught = e.message }
|
||||
const r = await tools.b.tool({})
|
||||
return caught + " / " + r
|
||||
`,
|
||||
},
|
||||
ctx,
|
||||
),
|
||||
)
|
||||
|
||||
expect(out.metadata.error).toBeUndefined()
|
||||
expect(out.output).toBe("hook exploded / ok")
|
||||
expect(called).toEqual(["b"])
|
||||
})
|
||||
|
||||
test("streams live per-call metadata as a call starts and finishes", async () => {
|
||||
const snapshots: Array<{ toolCalls: { tool: string; status: string; input?: Record<string, unknown> }[] }> = []
|
||||
const recordingCtx: Tool.Context = {
|
||||
|
||||
Reference in New Issue
Block a user