diff --git a/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.test.ts index 73dbb42ca8752..faed5c9655158 100644 --- a/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.test.ts @@ -351,6 +351,14 @@ const resumed = ( ...(options?.version === "2.2.5" || options?.version === "2.2.6" ? [out("rpc.call", ""), reply("rpc.call", { output: null })] : []), + out("agent.list", ""), + reply("agent.list", agentList), + ...(options?.supervised === true + ? [ + out("session.update", { sessionID: SESSION, permissions: supervisedRules }), + reply("session.update", null), + ] + : []), ...(options?.shellRecovery ?? (options?.version === undefined ? [] @@ -359,17 +367,7 @@ const resumed = ( ? [out("job.list", { kind: "shell" }), reply("job.list", { data: [] })] : emptyShellInventory), ])), - ...withInstructions([ - ...(options?.supervised === true - ? [ - out("agent.list", ""), - reply("agent.list", agentList), - out("session.update", { sessionID: SESSION, permissions: supervisedRules }), - reply("session.update", null), - ] - : []), - ...entries, - ]), + ...withInstructions(entries), ], { ...(options?.version === undefined ? {} : { version: options.version }), @@ -1531,8 +1529,6 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { const { runtime, thread } = yield* resumed([ ...backgroundLaunch(CHILD), event("session.execution.succeeded", { sessionID: SESSION }), - out("agent.list", ""), - reply("agent.list", agentList), out("session.update", { sessionID: SESSION, permissions: supervisedRules }), reply("session.update", null), out("session.update", { @@ -1831,6 +1827,8 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { replyData("permission.list", []), out("session.form.list", { sessionID: CHILD }), replyData("session.form.list", []), + out("session.update", { sessionID: CHILD, permissions: "" }), + reply("session.update", null), out("shell.list", ""), reply("shell.list", response([shell])), out("shell.get", { id: shell.id }), @@ -1840,8 +1838,6 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { "shell.output", response({ output: "done", cursor: 4, size: 4, truncated: false }), ), - out("session.update", { sessionID: CHILD, permissions: "" }), - reply("session.update", null), ] : []), ...launchedAndRunning, @@ -2852,6 +2848,8 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { }), ), ...noOpenRequests, + out("agent.list", ""), + reply("agent.list", agentList), out("session.update", { sessionID: SESSION, permissions: t3Rules }), reply("session.update", null), ]); @@ -2872,6 +2870,8 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { out("session.get", { sessionID: SESSION }), replyData("session.get", sessionInfo()), ...noOpenRequests, + out("agent.list", ""), + reply("agent.list", agentList), out("session.move", { sessionID: SESSION, directory: "/work/opencode2-feature" }), reply("session.move", null), ]); @@ -2927,6 +2927,8 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { reply("permission.reply", null), out("session.form.cancel", { sessionID: SESSION, formID: colorForm.id }), reply("session.form.cancel", null), + out("agent.list", ""), + reply("agent.list", agentList), out("session.active"), reply("session.active", { data: {} }), event("session.execution.interrupted", { sessionID: SESSION, reason: "user" }), @@ -3143,6 +3145,164 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { }), ); + it.effect.each([ + ["auto", true], + ["full-access", true], + ["approval-required", true], + ["auto-accept-edits", true], + ["auto", false], + ] as const)( + "preserves OpenCode permissions in %s (configured: %s)", + ([runtimeMode, configured]) => + Effect.gen(function* () { + const nativeRules = configured + ? [ + { action: "shell", resource: "*", effect: "allow" }, + { action: "shell", resource: "git push *", effect: "ask" }, + { action: "shell", resource: "rm *", effect: "deny" }, + { action: "read", resource: "*.env*", effect: "deny" }, + { action: "read", resource: "*.env.example", effect: "allow" }, + { action: "webfetch", resource: "*", effect: "ask" }, + ] + : []; + const permissions = [ + ...(runtimeMode === "auto" + ? [...supervisedRules.slice(configured ? 1 : 0, 3), ...buildPaths, ...nativeRules] + : runtimeMode === "full-access" + ? [ + t3Rules[0], + ...nativeRules.slice(2, -1), + { action: "webfetch", resource: "*", effect: "allow" }, + ] + : [ + { action: "shell", resource: "*", effect: "ask" }, + { + action: "edit", + resource: "*", + effect: runtimeMode === "auto-accept-edits" ? "allow" : "ask", + }, + { action: "external_directory", resource: "*", effect: "ask" }, + ...buildPaths, + ]), + ...mcpRules, + ]; + const runtime = yield* openCode2ReplayRuntime([ + ...opening, + out("agent.list", ""), + reply("agent.list", { + ...agentList, + data: agentList.data.map((agent) => ({ + ...agent, + permissions: [...agent.permissions, ...nativeRules], + })), + }), + out("session.create", { + location: { directory: WORK }, + model: { providerID: "opencode", id: "big-pickle" }, + permissions, + }), + replyData("session.create", sessionInfo({ permissions })), + ]); + const thread = yield* runtime.ensureThread({ + threadId, + modelSelection: bigPickle, + runtimePolicy: { ...policy(), runtimeMode }, + }); + assert.equal(thread.nativeThreadRef?.nativeId, SESSION); + }).pipe(Effect.scoped), + ); + + it.effect.each(["auto", "full-access"] as const)( + "retains ordered native edit denies and exceptions in %s plan mode", + (runtimeMode) => + Effect.gen(function* () { + const privatePath = { action: "edit", resource: "/plans/private/*", effect: "allow" }; + const denied = { action: "edit", resource: "/plans/*", effect: "deny" }; + const allowed = { action: "edit", resource: "/plans/allowed.md", effect: "allow" }; + const externalDeny = { + action: "external_directory", + resource: "/private/*", + effect: "deny", + }; + const externalAsk = { + action: "external_directory", + resource: "/private/reference/*", + effect: "ask", + }; + const externalFallback = { ...externalAsk, resource: "*" }; + const permissions = [ + ...(runtimeMode === "full-access" ? [t3Rules[0]] : [supervisedRules[0]]), + // Native outside-directory rules keep their order, ahead of any session grant. + externalDeny, + ...(runtimeMode === "full-access" + ? [ + { ...externalAsk, effect: "allow" }, + { ...externalFallback, effect: "allow" }, + ] + : [externalAsk, externalFallback]), + { action: "edit", resource: "*", effect: "deny" }, + ...buildPaths, + privatePath, + denied, + allowed, + ...mcpRules, + ]; + const runtime = yield* openCode2ReplayRuntime([ + ...opening, + out("agent.list", ""), + reply("agent.list", { + ...agentList, + data: agentList.data.map((agent) => ({ + ...agent, + permissions: [ + ...agent.permissions, + privatePath, + { ...denied, effect: "allow" }, + denied, + allowed, + externalDeny, + externalAsk, + externalFallback, + ], + })), + }), + out("session.create", { + location: { directory: WORK }, + model: { providerID: "opencode", id: "big-pickle" }, + permissions, + }), + replyData("session.create", sessionInfo({ permissions })), + ]); + const thread = yield* runtime.ensureThread({ + threadId, + modelSelection: bigPickle, + runtimePolicy: { ...policy(), runtimeMode, interactionMode: "plan" }, + }); + assert.equal(thread.nativeThreadRef?.nativeId, SESSION); + }).pipe(Effect.scoped), + ); + + it.effect("does not create an unrestricted session when native permissions cannot be read", () => + Effect.gen(function* () { + const runtime = yield* openCode2ReplayRuntime([ + ...opening, + out("agent.list", ""), + reply("agent.list", { + status: 500, + body: { _tag: "UnknownError", message: "Cannot load permissions" }, + }), + ]); + const failed = yield* runtime + .ensureThread({ + threadId, + modelSelection: bigPickle, + runtimePolicy: policy(), + }) + .pipe(Effect.flip); + assert.equal(failed._tag, "ProviderAdapterEnsureThreadError"); + }).pipe(Effect.scoped), + ); + it.effect("reads the agents again while a fresh server still lists none", () => Effect.gen(function* () { // OpenCode 2.0.18 briefly returns no agents on startup; using that response drops agent paths. @@ -4084,6 +4244,7 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { it.effect("keeps a turn still running after a reconnect open for its next events", () => Effect.gen(function* () { + const denied = { action: "shell", resource: "*", effect: "deny" }; const { runtime, thread } = yield* resumed([ out("session.prompt", { sessionID: SESSION, id: PROMPT_ID, text: "" }), promptAccepted, @@ -4096,6 +4257,23 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { text: "Arrived on the new stream.", }), event("session.execution.succeeded", { sessionID: SESSION }), + ...directoryModels(WORK), + out("agent.list", ""), + reply("agent.list", { + ...agentList, + data: agentList.data.map((agent) => ({ + ...agent, + permissions: [...agent.permissions, denied], + })), + }), + out("session.update", { + sessionID: SESSION, + permissions: [t3Rules[0], denied, ...mcpRules], + }), + reply("session.update", null), + out("session.prompt", { sessionID: SESSION, text: "" }), + promptAccepted, + event("session.execution.succeeded", { sessionID: SESSION }), ]); const events = yield* runtime.events.pipe( Stream.takeUntil((event) => event.type === "turn.terminal"), @@ -4114,6 +4292,9 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { "Arrived on the new stream.", ]); assert.deepInclude(collected.at(-1), { type: "turn.terminal", status: "completed" }); + const terminal = yield* terminalOf(runtime).pipe(Effect.forkScoped); + yield* runtime.startTurn({ ...turnInput(thread), runOrdinal: 2, providerTurnOrdinal: 2 }); + assert.equal((yield* Fiber.join(terminal))?.status, "completed"); }), ); @@ -5016,6 +5197,8 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { }), ), ...noOpenRequests, + out("agent.list", ""), + reply("agent.list", agentList), out("mcp.add", { server, "location[directory]": WORK, @@ -5387,6 +5570,8 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { out("session.get", { sessionID: SESSION }), replyData("session.get", sessionInfo()), ...noOpenRequests, + out("agent.list", ""), + reply("agent.list", agentList), out("session.prompt", { sessionID: SESSION, text: "" }), promptAccepted, event("session.execution.succeeded", { sessionID: SESSION }), @@ -5746,6 +5931,8 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { limit: { context: 64000, input: 48000, output: 8000 }, })), }), + out("agent.list", { "location[directory]": custom }), + reply("agent.list", { ...agentList, location: { directory: custom } }), out("session.create", { location: { directory: custom }, model: { providerID: "opencode", id: "big-pickle" }, @@ -6432,6 +6619,8 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { out("session.get", { sessionID: SESSION }), replyData("session.get", sessionInfo()), ...noOpenRequests, + out("agent.list", ""), + reply("agent.list", agentList), out("message.list", ""), reply("message.list", { data: [ @@ -6598,6 +6787,8 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { const { runtime, thread } = yield* resumed([ out("session.fork", { sessionID: SESSION }), replyData("session.fork", sessionInfo({ id: FORK })), + out("agent.list", { "location[directory]": target }), + reply("agent.list", { ...agentList, location: { directory: target } }), out("session.update", { sessionID: FORK, permissions: [ @@ -7328,6 +7519,9 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { sessionID: SESSION, model: { id: "buffered", providerID: "opencode" }, }), + ...directoryModels(WORK), + out("agent.list", ""), + reply("agent.list", agentList), out("session.switchModel", { sessionID: SESSION, model: "" }), reply("session.switchModel", null), ...backgroundLaunch(CHILD), @@ -7807,6 +8001,8 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { input: { sessionID: SESSION }, }), reply("rpc.call", { output: nativeGoal("paused") }), + out("agent.list", ""), + reply("agent.list", agentList), ...emptyShellInventory, ], { version: "2.2.5" }, diff --git a/apps/server/src/orchestration-v2/OpenCode2OrchestratorV2.integration.test.ts b/apps/server/src/orchestration-v2/OpenCode2OrchestratorV2.integration.test.ts index 8681ce4333cb4..1193d9d4c2e7f 100644 --- a/apps/server/src/orchestration-v2/OpenCode2OrchestratorV2.integration.test.ts +++ b/apps/server/src/orchestration-v2/OpenCode2OrchestratorV2.integration.test.ts @@ -119,8 +119,9 @@ const autoEditRules = (name: string) => [ const planRules = (name: string) => [ { action: "*", resource: "*", effect: "allow" }, + ...BUILD_PATHS, { action: "edit", resource: "*", effect: "deny" }, - ...PLAN_PATHS, + ...PLAN_PATHS.slice(BUILD_PATHS.length), ...mcpRules(name), ]; @@ -186,8 +187,6 @@ const createdSession = ( directory: string, name: string, permissions: ReadonlyArray = t3Rules(name), - - narrows = false, ): ReadonlyArray => [ out("event.subscribe"), out("model.list", ""), @@ -198,7 +197,8 @@ const createdSession = ( catalogModel("mimo-v2.6-flash-free", "MiMo V2.6 Flash Free"), ], }), - ...(narrows ? [out("agent.list", ""), reply("agent.list", agentList(directory))] : []), + out("agent.list", ""), + reply("agent.list", agentList(directory)), out("session.create", { location: { directory }, model: "", permissions, title: name }), reply("session.create", sessionInfo(directory, permissions, name)), @@ -371,8 +371,6 @@ describe("OpenCode 2 through the orchestrator", () => { out("session.get", { sessionID: SESSION }), reply("session.get", sessionInfo(cwd, t3Rules(name), name)), - out("agent.list", ""), - reply("agent.list", agentList(cwd)), out("session.update", { sessionID: SESSION, permissions: autoEditRules(name) }), reply("session.update", null), ...answeredPrompt("SECOND"), @@ -409,7 +407,7 @@ describe("OpenCode 2 through the orchestrator", () => { name, threadId: thread.threadId, entries: [ - ...createdSession(cwd, name, planRules(name), true), + ...createdSession(cwd, name, planRules(name)), out("session.switchAgent", { sessionID: SESSION, agent: "plan" }), reply("session.switchAgent", null), @@ -417,10 +415,20 @@ describe("OpenCode 2 through the orchestrator", () => { ...answeredPrompt("PLANNED"), out("session.get", { sessionID: SESSION }), reply("session.get", sessionInfo(cwd, planRules(name), name)), - out("session.update", { sessionID: SESSION, permissions: t3Rules(name) }), + out("session.update", { + sessionID: SESSION, + permissions: [ + ...FULL_ACCESS, + { action: "edit", resource: "*", effect: "deny" }, + ...PLAN_PATHS.slice(BUILD_PATHS.length), + ...mcpRules(name), + ], + }), reply("session.update", null), out("session.switchAgent", { sessionID: SESSION, agent: "build" }), reply("session.switchAgent", null), + out("session.update", { sessionID: SESSION, permissions: t3Rules(name) }), + reply("session.update", null), ...answeredPrompt("BUILT"), ], commands: [ @@ -598,7 +606,7 @@ describe("OpenCode 2 through the orchestrator", () => { readonly name: string; readonly cut: string; readonly hold?: string; - readonly reconnect: (parent: string) => ReadonlyArray; + readonly reconnect: (parent: string, directory: string) => ReadonlyArray; readonly steps: ( thread: ReturnType, run: (ordinal: number) => OrchestrationV2Run["id"], @@ -631,7 +639,7 @@ describe("OpenCode 2 through the orchestrator", () => { entries: [ ...kept, { type: "runtime_exit", status: "success" }, - ...input.reconnect(BACKGROUND_PARENT), + ...input.reconnect(BACKGROUND_PARENT, cwd), ], }); const thread = threadCommands({ name: input.name, worktreePath: cwd }); @@ -756,7 +764,7 @@ describe("OpenCode 2 through the orchestrator", () => { name: "opencode2-background-lost", cut: "session.step.started.3", - reconnect: (parent) => [ + reconnect: (parent, directory) => [ out("event.subscribe"), out("session.active"), @@ -789,6 +797,10 @@ describe("OpenCode 2 through the orchestrator", () => { ], cursor: {}, }), + out("model.list", { "location[directory]": directory }), + reply("model.list", { location: { directory }, data: [] }), + out("agent.list", ""), + reply("agent.list", agentList(directory)), out("session.prompt", { sessionID: parent, id: "", text: "" }), reply("session.prompt", { data: { diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_background/opencode_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_background/opencode_transcript.ndjson index 104b2c2aadf68..0d72b9d2ad59c 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_background/opencode_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_background/opencode_transcript.ndjson @@ -1,8 +1,10 @@ -{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_background", "metadata": {"source": "opencode2-spike/recordings/background.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f1485cda4ffeXuc6GjAU9vDiRb", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn."}} +{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_background", "metadata": {"source": "opencode2-spike/recordings/background.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f1485cda4ffeXuc6GjAU9vDiRb", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn.", "permissionInventorySource": "opencode2_permission: agent.list recording reused for the permission-policy lookup"}} {"type": "expect_outbound", "label": "event.subscribe", "frame": {"type": "event.subscribe"}} {"type": "expect_outbound", "label": "model.list", "frame": {"type": "model.list", "input": {"location[directory]": ""}}} {"type": "emit_inbound", "label": "model.list.response", "frame": {"type": "sdk.response", "operation": "model.list", "data": {"location": {"directory": ""}, "data": [{"id": "space-bunny-free", "modelID": "space-bunny-free", "providerID": "opencode", "name": "Space Bunny Free", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text", "image", "video"], "output": ["text"]}, "variants": [{"id": "low", "settings": {"reasoningEffort": "low"}}, {"id": "medium", "settings": {"reasoningEffort": "medium"}}, {"id": "high", "settings": {"reasoningEffort": "high"}}, {"id": "xhigh", "settings": {"reasoningEffort": "xhigh"}}, {"id": "max", "settings": {"reasoningEffort": "max"}}], "time": {"released": 1790121600000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 1048576, "input": 524288, "output": 524288}}, {"id": "big-pickle", "modelID": "big-pickle", "providerID": "opencode", "family": "big-pickle", "name": "Big Pickle", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text"], "output": ["text"]}, "variants": [], "time": {"released": 1760659200000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 200000, "input": 160000, "output": 32000}}]}}} {"type": "emit_inbound", "label": "server.connected", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb7a3259001f54idsWUsyJ1uY", "type": "server.connected", "data": {}}}} +{"type": "expect_outbound", "label": "agent.list", "frame": {"type": "agent.list", "input": {"location[directory]": ""}}} +{"type": "emit_inbound", "label": "agent.list.response", "frame": {"type": "sdk.response", "operation": "agent.list", "data": {"location": {"directory": ""}, "data": [{"id": "build", "name": "Build", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "The default agent. Executes tools based on configured permissions.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}]}, {"id": "plan", "name": "Plan", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "Read-only agent for exploring the codebase and planning work before implementation.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}, {"action": "edit", "resource": "*", "effect": "deny"}, {"action": "edit", "resource": "/.opencode/plan/*", "effect": "allow"}, {"action": "external_directory", "resource": "/.opencode/plan/*", "effect": "allow"}]}]}}} {"type": "expect_outbound", "label": "session.create", "frame": {"type": "session.create", "input": ""}} {"type": "emit_inbound", "label": "session.create.response", "frame": {"type": "sdk.response", "operation": "session.create", "data": {"data": {"id": "ses_f1485cda4ffeXuc6GjAU9vDiRb", "projectID": "global", "model": {"id": "big-pickle", "providerID": "opencode", "variant": "default"}, "cost": 0, "tokens": {"input": 0, "output": 0, "reasoning": 0, "cache": {"read": 0, "write": 0}}, "time": {"created": 1790657049201, "updated": 1790657049201}, "title": "spike background", "location": {"directory": ""}}}}} {"type": "emit_inbound", "label": "session.created", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb7a3271001Z06XG1eDh56UaK", "created": 1790657049201, "type": "session.created", "location": {"directory": ""}, "data": {"sessionID": "ses_f1485cda4ffeXuc6GjAU9vDiRb", "slug": "cosmic-harbor", "version": "2.0.18", "projectID": "global", "location": {"directory": ""}, "subpath": "", "title": "spike background", "model": {"id": "big-pickle", "providerID": "opencode"}}, "durable": {"aggregateID": "ses_f1485cda4ffeXuc6GjAU9vDiRb", "seq": 0, "version": 1}}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_command/opencode_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_command/opencode_transcript.ndjson index 36c35d5799ad0..75f610c6fb9f1 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_command/opencode_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_command/opencode_transcript.ndjson @@ -1,8 +1,10 @@ -{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_command", "metadata": {"source": "oc2-8 live recording command_t3.ndjson (2.0.18, openrouter/deepseek/deepseek-v4-flash)", "capturedAt": "2026-09-29", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary: `/hello WORLD` names a workspace command (.opencode/command/hello.md), so T3 reads the directory's commands and runs it with session.command; OpenCode expands the template into the user message. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn. Before the command, T3 reads the session's newest history item (none on a new session, as 2.0.18 answers) so a reconnect can backfill a command, whose answer carries no item id."}} +{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_command", "metadata": {"source": "oc2-8 live recording command_t3.ndjson (2.0.18, openrouter/deepseek/deepseek-v4-flash)", "capturedAt": "2026-09-29", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary: `/hello WORLD` names a workspace command (.opencode/command/hello.md), so T3 reads the directory's commands and runs it with session.command; OpenCode expands the template into the user message. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn. Before the command, T3 reads the session's newest history item (none on a new session, as 2.0.18 answers) so a reconnect can backfill a command, whose answer carries no item id.", "permissionInventorySource": "opencode2_permission: agent.list recording reused for the permission-policy lookup"}} {"type": "expect_outbound", "label": "event.subscribe", "frame": {"type": "event.subscribe"}} {"type": "expect_outbound", "label": "model.list", "frame": {"type": "model.list", "input": {"location[directory]": ""}}} {"type": "emit_inbound", "label": "model.list.response", "frame": {"type": "sdk.response", "operation": "model.list", "data": {"location": {"directory": ""}, "data": [{"id": "deepseek/deepseek-v4-flash", "modelID": "deepseek/deepseek-v4-flash", "providerID": "openrouter", "family": "deepseek-flash", "name": "DeepSeek V4 Flash", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openrouter", "settings": {"baseURL": "https://openrouter.ai/api/v1"}, "headers": {"HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode"}, "capabilities": {"tools": true, "input": ["text"], "output": ["text"]}, "variants": [{"id": "none", "settings": {"reasoning": {"enabled": false}}}, {"id": "high", "settings": {"reasoning": {"effort": "high"}}}, {"id": "xhigh", "settings": {"reasoning": {"effort": "xhigh"}}}], "time": {"released": 1776988800000}, "cost": [{"input": 0.14, "output": 0.28, "cache": {"read": 0.028, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 1048576, "output": 131072}}]}}} {"type": "emit_inbound", "label": "server.connected", "frame": {"type": "sdk.event", "event": {"id": "evt_0f0d83284001dfaN03YCqzlu9F", "type": "server.connected", "data": {}}}} +{"type": "expect_outbound", "label": "agent.list", "frame": {"type": "agent.list", "input": {"location[directory]": ""}}} +{"type": "emit_inbound", "label": "agent.list.response", "frame": {"type": "sdk.response", "operation": "agent.list", "data": {"location": {"directory": ""}, "data": [{"id": "build", "name": "Build", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "The default agent. Executes tools based on configured permissions.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}]}, {"id": "plan", "name": "Plan", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "Read-only agent for exploring the codebase and planning work before implementation.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}, {"action": "edit", "resource": "*", "effect": "deny"}, {"action": "edit", "resource": "/.opencode/plan/*", "effect": "allow"}, {"action": "external_directory", "resource": "/.opencode/plan/*", "effect": "allow"}]}]}}} {"type": "expect_outbound", "label": "session.create", "frame": {"type": "session.create", "input": ""}} {"type": "emit_inbound", "label": "session.create.response", "frame": {"type": "sdk.response", "operation": "session.create", "data": {"data": {"id": "ses_f0f27cc49ffe6wl0jdiu4kO4FC", "projectID": "global", "model": {"id": "deepseek/deepseek-v4-flash", "providerID": "openrouter", "variant": "default"}, "cost": 0, "tokens": {"input": 0, "output": 0, "reasoning": 0, "cache": {"read": 0, "write": 0}}, "time": {"created": 1790747096015, "updated": 1790747096015}, "title": "oc2-8 command_t3", "permissions": [{"action": "*", "resource": "*", "effect": "allow"}], "location": {"directory": ""}}}}} {"type": "emit_inbound", "label": "session.created", "frame": {"type": "sdk.event", "event": {"id": "evt_0f0d833cf001vCmbwtG6y6Wg1U", "created": 1790747096015, "type": "session.created", "location": {"directory": ""}, "data": {"sessionID": "ses_f0f27cc49ffe6wl0jdiu4kO4FC", "slug": "shiny-squid", "version": "2.0.18", "projectID": "global", "location": {"directory": ""}, "subpath": "", "title": "oc2-8 command_t3", "permissions": [{"action": "*", "resource": "*", "effect": "allow"}], "model": {"id": "deepseek/deepseek-v4-flash", "providerID": "openrouter"}}, "durable": {"aggregateID": "ses_f0f27cc49ffe6wl0jdiu4kO4FC", "seq": 0, "version": 1}}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_compaction/opencode_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_compaction/opencode_transcript.ndjson index 3469e11199a2d..5d6ba65c1326f 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_compaction/opencode_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_compaction/opencode_transcript.ndjson @@ -1,8 +1,10 @@ -{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_compaction", "metadata": {"source": "opencode2-spike/recordings/compaction.ndjson", "capturedAt": "2026-09-28", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary by the spike recorder: a turn, /compact through session.compact, then a turn that recalls the compacted fact. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn."}} +{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_compaction", "metadata": {"source": "opencode2-spike/recordings/compaction.ndjson", "capturedAt": "2026-09-28", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary by the spike recorder: a turn, /compact through session.compact, then a turn that recalls the compacted fact. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn.", "permissionInventorySource": "opencode2_permission: agent.list recording reused for the permission-policy lookup"}} {"type": "expect_outbound", "label": "event.subscribe", "frame": {"type": "event.subscribe"}} {"type": "expect_outbound", "label": "model.list", "frame": {"type": "model.list", "input": {"location[directory]": ""}}} {"type": "emit_inbound", "label": "model.list.response", "frame": {"type": "sdk.response", "operation": "model.list", "data": {"location": {"directory": ""}, "data": [{"id": "space-bunny-free", "modelID": "space-bunny-free", "providerID": "opencode", "name": "Space Bunny Free", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text", "image", "video"], "output": ["text"]}, "variants": [{"id": "low", "settings": {"reasoningEffort": "low"}}, {"id": "medium", "settings": {"reasoningEffort": "medium"}}, {"id": "high", "settings": {"reasoningEffort": "high"}}, {"id": "xhigh", "settings": {"reasoningEffort": "xhigh"}}, {"id": "max", "settings": {"reasoningEffort": "max"}}], "time": {"released": 1790121600000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 1048576, "input": 524288, "output": 524288}}, {"id": "big-pickle", "modelID": "big-pickle", "providerID": "opencode", "family": "big-pickle", "name": "Big Pickle", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text"], "output": ["text"]}, "variants": [], "time": {"released": 1760659200000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 200000, "input": 160000, "output": 32000}}]}}} {"type": "emit_inbound", "label": "server.connected", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb7b6d17001qoknVVU292OuxN", "type": "server.connected", "data": {}}}} +{"type": "expect_outbound", "label": "agent.list", "frame": {"type": "agent.list", "input": {"location[directory]": ""}}} +{"type": "emit_inbound", "label": "agent.list.response", "frame": {"type": "sdk.response", "operation": "agent.list", "data": {"location": {"directory": ""}, "data": [{"id": "build", "name": "Build", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "The default agent. Executes tools based on configured permissions.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}]}, {"id": "plan", "name": "Plan", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "Read-only agent for exploring the codebase and planning work before implementation.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}, {"action": "edit", "resource": "*", "effect": "deny"}, {"action": "edit", "resource": "/.opencode/plan/*", "effect": "allow"}, {"action": "external_directory", "resource": "/.opencode/plan/*", "effect": "allow"}]}]}}} {"type": "expect_outbound", "label": "session.create", "frame": {"type": "session.create", "input": ""}} {"type": "emit_inbound", "label": "session.create.response", "frame": {"type": "sdk.response", "operation": "session.create", "data": {"data": {"id": "ses_f148492e3ffeQ91rU0GmbvwzYL", "projectID": "global", "model": {"id": "big-pickle", "providerID": "opencode", "variant": "default"}, "cost": 0, "tokens": {"input": 0, "output": 0, "reasoning": 0, "cache": {"read": 0, "write": 0}}, "time": {"created": 1790657129772, "updated": 1790657129772}, "title": "spike compaction", "location": {"directory": ""}}}}} {"type": "emit_inbound", "label": "session.created", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb7b6d2c0017hjEa3lkp3ZkkO", "created": 1790657129772, "type": "session.created", "location": {"directory": ""}, "data": {"sessionID": "ses_f148492e3ffeQ91rU0GmbvwzYL", "slug": "neon-wizard", "version": "2.0.18", "projectID": "global", "location": {"directory": ""}, "subpath": "", "title": "spike compaction", "model": {"id": "big-pickle", "providerID": "opencode"}}, "durable": {"aggregateID": "ses_f148492e3ffeQ91rU0GmbvwzYL", "seq": 0, "version": 1}}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_fork/opencode_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_fork/opencode_transcript.ndjson index a483f04813b31..dbbaecba1581e 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_fork/opencode_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_fork/opencode_transcript.ndjson @@ -1,8 +1,10 @@ -{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_fork", "metadata": {"source": "opencode2-spike/recordings/fork.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f1484ea8affe5aTo8CcgbGIszj", "forkedNativeSessionId": "ses_f1484db83ffeLGtrRCFimo1H0e", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary by the spike recorder: two turns, then a fork before the second turn's user message, then a turn on the fork that only sees the first. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn. The fork is a new thread to T3, so its first turn writes the entry on the fork too."}} +{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_fork", "metadata": {"source": "opencode2-spike/recordings/fork.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f1484ea8affe5aTo8CcgbGIszj", "forkedNativeSessionId": "ses_f1484db83ffeLGtrRCFimo1H0e", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary by the spike recorder: two turns, then a fork before the second turn's user message, then a turn on the fork that only sees the first. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn. The fork is a new thread to T3, so its first turn writes the entry on the fork too.", "permissionInventorySource": "opencode2_permission: agent.list recording reused for the permission-policy lookup"}} {"type": "expect_outbound", "label": "event.subscribe", "frame": {"type": "event.subscribe"}} {"type": "expect_outbound", "label": "model.list", "frame": {"type": "model.list", "input": {"location[directory]": ""}}} {"type": "emit_inbound", "label": "model.list.response", "frame": {"type": "sdk.response", "operation": "model.list", "data": {"location": {"directory": ""}, "data": [{"id": "space-bunny-free", "modelID": "space-bunny-free", "providerID": "opencode", "name": "Space Bunny Free", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text", "image", "video"], "output": ["text"]}, "variants": [{"id": "low", "settings": {"reasoningEffort": "low"}}, {"id": "medium", "settings": {"reasoningEffort": "medium"}}, {"id": "high", "settings": {"reasoningEffort": "high"}}, {"id": "xhigh", "settings": {"reasoningEffort": "xhigh"}}, {"id": "max", "settings": {"reasoningEffort": "max"}}], "time": {"released": 1790121600000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 1048576, "input": 524288, "output": 524288}}, {"id": "big-pickle", "modelID": "big-pickle", "providerID": "opencode", "family": "big-pickle", "name": "Big Pickle", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text"], "output": ["text"]}, "variants": [], "time": {"released": 1760659200000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 200000, "input": 160000, "output": 32000}}]}}} {"type": "emit_inbound", "label": "server.connected", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb7b1573001elLn2XpVURsxOW", "type": "server.connected", "data": {}}}} +{"type": "expect_outbound", "label": "agent.list", "frame": {"type": "agent.list", "input": {"location[directory]": ""}}} +{"type": "emit_inbound", "label": "agent.list.response", "frame": {"type": "sdk.response", "operation": "agent.list", "data": {"location": {"directory": ""}, "data": [{"id": "build", "name": "Build", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "The default agent. Executes tools based on configured permissions.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}]}, {"id": "plan", "name": "Plan", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "Read-only agent for exploring the codebase and planning work before implementation.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}, {"action": "edit", "resource": "*", "effect": "deny"}, {"action": "edit", "resource": "/.opencode/plan/*", "effect": "allow"}, {"action": "external_directory", "resource": "/.opencode/plan/*", "effect": "allow"}]}]}}} {"type": "expect_outbound", "label": "session.create", "frame": {"type": "session.create", "input": ""}} {"type": "emit_inbound", "label": "session.create.response", "frame": {"type": "sdk.response", "operation": "session.create", "data": {"data": {"id": "ses_f1484ea8affe5aTo8CcgbGIszj", "projectID": "global", "model": {"id": "big-pickle", "providerID": "opencode", "variant": "default"}, "cost": 0, "tokens": {"input": 0, "output": 0, "reasoning": 0, "cache": {"read": 0, "write": 0}}, "time": {"created": 1790657107336, "updated": 1790657107336}, "title": "spike fork", "location": {"directory": ""}}}}} {"type": "emit_inbound", "label": "session.created", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb7b1588001REWGPFiBf7rtLq", "created": 1790657107336, "type": "session.created", "location": {"directory": ""}, "data": {"sessionID": "ses_f1484ea8affe5aTo8CcgbGIszj", "slug": "playful-garden", "version": "2.0.18", "projectID": "global", "location": {"directory": ""}, "subpath": "", "title": "spike fork", "model": {"id": "big-pickle", "providerID": "opencode"}}, "durable": {"aggregateID": "ses_f1484ea8affe5aTo8CcgbGIszj", "seq": 0, "version": 1}}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_inbox/opencode_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_inbox/opencode_transcript.ndjson index b0f63d8ee9282..0ca67deadaf57 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_inbox/opencode_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_inbox/opencode_transcript.ndjson @@ -1,8 +1,10 @@ -{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_inbox", "metadata": {"source": "opencode2-spike/recordings/inbox.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f14854c71ffeuPLxKSK70AHWvd", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary by the spike recorder: a steer lands in a running shell turn at its next step boundary. The recording also queued two prompts natively; T3 queues them itself, so those requests are dropped and the first queued message runs as its own turn, in the execution OpenCode ran it in, after the steered turn ends. The steer is sent while the shell call runs, as T3 can only steer a turn it sees running. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn."}} +{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_inbox", "metadata": {"source": "opencode2-spike/recordings/inbox.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f14854c71ffeuPLxKSK70AHWvd", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary by the spike recorder: a steer lands in a running shell turn at its next step boundary. The recording also queued two prompts natively; T3 queues them itself, so those requests are dropped and the first queued message runs as its own turn, in the execution OpenCode ran it in, after the steered turn ends. The steer is sent while the shell call runs, as T3 can only steer a turn it sees running. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn.", "permissionInventorySource": "opencode2_permission: agent.list recording reused for the permission-policy lookup"}} {"type": "expect_outbound", "label": "event.subscribe", "frame": {"type": "event.subscribe"}} {"type": "expect_outbound", "label": "model.list", "frame": {"type": "model.list", "input": {"location[directory]": ""}}} {"type": "emit_inbound", "label": "model.list.response", "frame": {"type": "sdk.response", "operation": "model.list", "data": {"location": {"directory": ""}, "data": [{"id": "space-bunny-free", "modelID": "space-bunny-free", "providerID": "opencode", "name": "Space Bunny Free", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text", "image", "video"], "output": ["text"]}, "variants": [{"id": "low", "settings": {"reasoningEffort": "low"}}, {"id": "medium", "settings": {"reasoningEffort": "medium"}}, {"id": "high", "settings": {"reasoningEffort": "high"}}, {"id": "xhigh", "settings": {"reasoningEffort": "xhigh"}}, {"id": "max", "settings": {"reasoningEffort": "max"}}], "time": {"released": 1790121600000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 1048576, "input": 524288, "output": 524288}}, {"id": "big-pickle", "modelID": "big-pickle", "providerID": "opencode", "family": "big-pickle", "name": "Big Pickle", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text"], "output": ["text"]}, "variants": [], "time": {"released": 1760659200000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 200000, "input": 160000, "output": 32000}}]}}} {"type": "emit_inbound", "label": "server.connected", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb7ab38c001npTkjN5FpjHmXO", "type": "server.connected", "data": {}}}} +{"type": "expect_outbound", "label": "agent.list", "frame": {"type": "agent.list", "input": {"location[directory]": ""}}} +{"type": "emit_inbound", "label": "agent.list.response", "frame": {"type": "sdk.response", "operation": "agent.list", "data": {"location": {"directory": ""}, "data": [{"id": "build", "name": "Build", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "The default agent. Executes tools based on configured permissions.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}]}, {"id": "plan", "name": "Plan", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "Read-only agent for exploring the codebase and planning work before implementation.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}, {"action": "edit", "resource": "*", "effect": "deny"}, {"action": "edit", "resource": "/.opencode/plan/*", "effect": "allow"}, {"action": "external_directory", "resource": "/.opencode/plan/*", "effect": "allow"}]}]}}} {"type": "expect_outbound", "label": "session.create", "frame": {"type": "session.create", "input": ""}} {"type": "emit_inbound", "label": "session.create.response", "frame": {"type": "sdk.response", "operation": "session.create", "data": {"data": {"id": "ses_f14854c71ffeuPLxKSK70AHWvd", "projectID": "global", "model": {"id": "big-pickle", "providerID": "opencode", "variant": "default"}, "cost": 0, "tokens": {"input": 0, "output": 0, "reasoning": 0, "cache": {"read": 0, "write": 0}}, "time": {"created": 1790657082272, "updated": 1790657082272}, "title": "spike inbox", "location": {"directory": ""}}}}} {"type": "emit_inbound", "label": "session.created", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb7ab3a0001MfhwH3aV04StYO", "created": 1790657082272, "type": "session.created", "location": {"directory": ""}, "data": {"sessionID": "ses_f14854c71ffeuPLxKSK70AHWvd", "slug": "lucky-mountain", "version": "2.0.18", "projectID": "global", "location": {"directory": ""}, "subpath": "", "title": "spike inbox", "model": {"id": "big-pickle", "providerID": "opencode"}}, "durable": {"aggregateID": "ses_f14854c71ffeuPLxKSK70AHWvd", "seq": 0, "version": 1}}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_interrupt/opencode_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_interrupt/opencode_transcript.ndjson index ce4c048e19000..90d94116d65b2 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_interrupt/opencode_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_interrupt/opencode_transcript.ndjson @@ -1,8 +1,10 @@ -{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_interrupt", "metadata": {"source": "opencode2-spike/recordings/interrupt.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f148c8240ffegmJ5ZhqhyYOcSH", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary by the spike recorder. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn."}} +{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_interrupt", "metadata": {"source": "opencode2-spike/recordings/interrupt.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f148c8240ffegmJ5ZhqhyYOcSH", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary by the spike recorder. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn.", "permissionInventorySource": "opencode2_permission: agent.list recording reused for the permission-policy lookup"}} {"type": "expect_outbound", "label": "event.subscribe", "frame": {"type": "event.subscribe"}} {"type": "expect_outbound", "label": "model.list", "frame": {"type": "model.list", "input": {"location[directory]": ""}}} {"type": "emit_inbound", "label": "model.list.response", "frame": {"type": "sdk.response", "operation": "model.list", "data": {"location": {"directory": ""}, "data": [{"id": "space-bunny-free", "modelID": "space-bunny-free", "providerID": "opencode", "name": "Space Bunny Free", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text", "image", "video"], "output": ["text"]}, "variants": [{"id": "low", "settings": {"reasoningEffort": "low"}}, {"id": "medium", "settings": {"reasoningEffort": "medium"}}, {"id": "high", "settings": {"reasoningEffort": "high"}}, {"id": "xhigh", "settings": {"reasoningEffort": "xhigh"}}, {"id": "max", "settings": {"reasoningEffort": "max"}}], "time": {"released": 1790121600000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 1048576, "input": 524288, "output": 524288}}, {"id": "big-pickle", "modelID": "big-pickle", "providerID": "opencode", "family": "big-pickle", "name": "Big Pickle", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text"], "output": ["text"]}, "variants": [], "time": {"released": 1760659200000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 200000, "input": 160000, "output": 32000}}]}}} {"type": "emit_inbound", "label": "server.connected", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb737dbd001VzvgLDSnWZmKmp", "type": "server.connected", "data": {}}}} +{"type": "expect_outbound", "label": "agent.list", "frame": {"type": "agent.list", "input": {"location[directory]": ""}}} +{"type": "emit_inbound", "label": "agent.list.response", "frame": {"type": "sdk.response", "operation": "agent.list", "data": {"location": {"directory": ""}, "data": [{"id": "build", "name": "Build", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "The default agent. Executes tools based on configured permissions.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}]}, {"id": "plan", "name": "Plan", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "Read-only agent for exploring the codebase and planning work before implementation.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}, {"action": "edit", "resource": "*", "effect": "deny"}, {"action": "edit", "resource": "/.opencode/plan/*", "effect": "allow"}, {"action": "external_directory", "resource": "/.opencode/plan/*", "effect": "allow"}]}]}}} {"type": "expect_outbound", "label": "session.create", "frame": {"type": "session.create", "input": ""}} {"type": "emit_inbound", "label": "session.create.response", "frame": {"type": "sdk.response", "operation": "session.create", "data": {"data": {"id": "ses_f148c8240ffegmJ5ZhqhyYOcSH", "projectID": "global", "model": {"id": "big-pickle", "providerID": "opencode", "variant": "default"}, "cost": 0, "tokens": {"input": 0, "output": 0, "reasoning": 0, "cache": {"read": 0, "write": 0}}, "time": {"created": 1790656609743, "updated": 1790656609743}, "title": "spike interrupt", "location": {"directory": ""}}}}} {"type": "emit_inbound", "label": "session.created", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb737dcf001Bcj0k2QTAM0Kvj", "created": 1790656609743, "type": "session.created", "location": {"directory": ""}, "data": {"sessionID": "ses_f148c8240ffegmJ5ZhqhyYOcSH", "slug": "gentle-cabin", "version": "2.0.18", "projectID": "global", "location": {"directory": ""}, "subpath": "", "title": "spike interrupt", "model": {"id": "big-pickle", "providerID": "opencode"}}, "durable": {"aggregateID": "ses_f148c8240ffegmJ5ZhqhyYOcSH", "seq": 0, "version": 1}}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_missing_step_usage/opencode_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_missing_step_usage/opencode_transcript.ndjson index a15b2adbc1da4..8c98c98bd4c79 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_missing_step_usage/opencode_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_missing_step_usage/opencode_transcript.ndjson @@ -1,8 +1,10 @@ -{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_missing_step_usage", "metadata": {"source": "opencode2-spike/recordings/simple.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f148cdfa1ffeIFwbC0FCx0fKpk", "description": "The opencode2_simple recording with a second step added by hand that fails without a token report, so the turn has one measured step and one missing report."}} +{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_missing_step_usage", "metadata": {"source": "opencode2-spike/recordings/simple.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f148cdfa1ffeIFwbC0FCx0fKpk", "description": "The opencode2_simple recording with a second step added by hand that fails without a token report, so the turn has one measured step and one missing report.", "permissionInventorySource": "opencode2_permission: agent.list recording reused for the permission-policy lookup"}} {"type": "expect_outbound", "label": "event.subscribe", "frame": {"type": "event.subscribe"}} {"type": "expect_outbound", "label": "model.list", "frame": {"type": "model.list", "input": {"location[directory]": ""}}} {"type": "emit_inbound", "label": "model.list.response", "frame": {"type": "sdk.response", "operation": "model.list", "data": {"location": {"directory": ""}, "data": [{"id": "space-bunny-free", "modelID": "space-bunny-free", "providerID": "opencode", "name": "Space Bunny Free", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text", "image", "video"], "output": ["text"]}, "variants": [{"id": "low", "settings": {"reasoningEffort": "low"}}, {"id": "medium", "settings": {"reasoningEffort": "medium"}}, {"id": "high", "settings": {"reasoningEffort": "high"}}, {"id": "xhigh", "settings": {"reasoningEffort": "xhigh"}}, {"id": "max", "settings": {"reasoningEffort": "max"}}], "time": {"released": 1790121600000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 1048576, "input": 524288, "output": 524288}}, {"id": "big-pickle", "modelID": "big-pickle", "providerID": "opencode", "family": "big-pickle", "name": "Big Pickle", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text"], "output": ["text"]}, "variants": [], "time": {"released": 1760659200000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 200000, "input": 160000, "output": 32000}}]}}} {"type": "emit_inbound", "label": "server.connected", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb73205b001QM7spc7rTwRevR", "type": "server.connected", "data": {}}}} +{"type": "expect_outbound", "label": "agent.list", "frame": {"type": "agent.list", "input": {"location[directory]": ""}}} +{"type": "emit_inbound", "label": "agent.list.response", "frame": {"type": "sdk.response", "operation": "agent.list", "data": {"location": {"directory": ""}, "data": [{"id": "build", "name": "Build", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "The default agent. Executes tools based on configured permissions.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}]}, {"id": "plan", "name": "Plan", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "Read-only agent for exploring the codebase and planning work before implementation.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}, {"action": "edit", "resource": "*", "effect": "deny"}, {"action": "edit", "resource": "/.opencode/plan/*", "effect": "allow"}, {"action": "external_directory", "resource": "/.opencode/plan/*", "effect": "allow"}]}]}}} {"type": "expect_outbound", "label": "session.create", "frame": {"type": "session.create", "input": {"location": "", "model": {"providerID": "opencode", "id": "space-bunny-free", "variant": "high"}, "permissions": ""}}} {"type": "emit_inbound", "label": "session.create.response", "frame": {"type": "sdk.response", "operation": "session.create", "data": {"data": {"id": "ses_f148cdfa1ffeIFwbC0FCx0fKpk", "projectID": "global", "model": {"id": "space-bunny-free", "providerID": "opencode", "variant": "high"}, "cost": 0, "tokens": {"input": 0, "output": 0, "reasoning": 0, "cache": {"read": 0, "write": 0}}, "time": {"created": 1790656585840, "updated": 1790656585840}, "title": "spike simple", "location": {"directory": ""}}}}} {"type": "emit_inbound", "label": "session.created", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb732070001MeC12AQ1Ds6a0a", "created": 1790656585840, "type": "session.created", "location": {"directory": ""}, "data": {"sessionID": "ses_f148cdfa1ffeIFwbC0FCx0fKpk", "slug": "sunny-pixel", "version": "2.0.18", "projectID": "global", "location": {"directory": ""}, "subpath": "", "title": "spike simple", "model": {"id": "space-bunny-free", "providerID": "opencode", "variant": "high"}}, "durable": {"aggregateID": "ses_f148cdfa1ffeIFwbC0FCx0fKpk", "seq": 0, "version": 1}}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_nested_background/opencode_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_nested_background/opencode_transcript.ndjson index 4204f275fd8dc..cf09e7e24e7d3 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_nested_background/opencode_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_nested_background/opencode_transcript.ndjson @@ -1,7 +1,9 @@ -{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_nested_background", "metadata": {"source": "OpenCode 2.0.18 live run, openrouter/deepseek/deepseek-v4-flash, experimental.subagent_depth 2", "capturedAt": "2026-10-01", "nativeSessionId": "ses_f08c08821ffefFAD3sMjkyP0Fd", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary: a foreground subagent launches its own background subagent and returns at once, so the thread's turn ends while that grandchild still runs. When the grandchild ends, OpenCode reports it to the foreground subagent's session and wakes that session to answer the report. Nesting needs experimental.subagent_depth 2. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn."}} +{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_nested_background", "metadata": {"source": "OpenCode 2.0.18 live run, openrouter/deepseek/deepseek-v4-flash, experimental.subagent_depth 2", "capturedAt": "2026-10-01", "nativeSessionId": "ses_f08c08821ffefFAD3sMjkyP0Fd", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary: a foreground subagent launches its own background subagent and returns at once, so the thread's turn ends while that grandchild still runs. When the grandchild ends, OpenCode reports it to the foreground subagent's session and wakes that session to answer the report. Nesting needs experimental.subagent_depth 2. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn.", "permissionInventorySource": "opencode2_permission: agent.list recording reused for the permission-policy lookup"}} {"type": "expect_outbound", "label": "event.subscribe", "frame": {"type": "event.subscribe"}} {"type": "expect_outbound", "label": "model.list", "frame": {"type": "model.list", "input": {"location[directory]": ""}}} {"type": "emit_inbound", "label": "model.list.response", "frame": {"type": "sdk.response", "operation": "model.list", "data": {"location": {"directory": ""}, "data": [{"id": "deepseek/deepseek-v4-flash", "modelID": "deepseek/deepseek-v4-flash", "providerID": "openrouter", "family": "deepseek-flash", "name": "DeepSeek V4 Flash", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openrouter", "settings": {"baseURL": "https://openrouter.ai/api/v1"}, "headers": {"HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode"}, "capabilities": {"tools": true, "input": ["text"], "output": ["text"]}, "variants": [{"id": "none", "settings": {"reasoning": {"enabled": false}}}, {"id": "high", "settings": {"reasoning": {"effort": "high"}}}, {"id": "xhigh", "settings": {"reasoning": {"effort": "xhigh"}}}], "time": {"released": 1776988800000}, "cost": [{"input": 0.04186, "output": 0.08372, "cache": {"read": 0.008372, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 1048576, "output": 131072}}]}}} +{"type": "expect_outbound", "label": "agent.list", "frame": {"type": "agent.list", "input": {"location[directory]": ""}}} +{"type": "emit_inbound", "label": "agent.list.response", "frame": {"type": "sdk.response", "operation": "agent.list", "data": {"location": {"directory": ""}, "data": [{"id": "build", "name": "Build", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "The default agent. Executes tools based on configured permissions.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}]}, {"id": "plan", "name": "Plan", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "Read-only agent for exploring the codebase and planning work before implementation.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}, {"action": "edit", "resource": "*", "effect": "deny"}, {"action": "edit", "resource": "/.opencode/plan/*", "effect": "allow"}, {"action": "external_directory", "resource": "/.opencode/plan/*", "effect": "allow"}]}]}}} {"type": "expect_outbound", "label": "session.create", "frame": {"type": "session.create", "input": ""}} {"type": "emit_inbound", "label": "session.create.response", "frame": {"type": "sdk.response", "operation": "session.create", "data": {"data": {"id": "ses_f08c08821ffefFAD3sMjkyP0Fd", "projectID": "3b9597874c088ee0849b0d68c211296e47876139", "model": {"id": "deepseek/deepseek-v4-flash", "providerID": "openrouter", "variant": "default"}, "cost": 0, "tokens": {"input": 0, "output": 0, "reasoning": 0, "cache": {"read": 0, "write": 0}}, "time": {"created": 1790854526948, "updated": 1790854526948}, "title": "nested nested", "permissions": [{"action": "*", "resource": "*", "effect": "allow"}], "location": {"directory": ""}}}}} {"type": "emit_inbound", "label": "session.created", "frame": {"type": "sdk.event", "event": {"id": "evt_0f73f77e4001G992P0v23j0KVF", "created": 1790854526948, "type": "session.created", "location": {"directory": ""}, "data": {"sessionID": "ses_f08c08821ffefFAD3sMjkyP0Fd", "slug": "quiet-canyon", "version": "2.0.18", "projectID": "3b9597874c088ee0849b0d68c211296e47876139", "location": {"directory": ""}, "subpath": "", "title": "nested nested", "permissions": [{"action": "*", "resource": "*", "effect": "allow"}], "model": {"id": "deepseek/deepseek-v4-flash", "providerID": "openrouter"}}, "durable": {"aggregateID": "ses_f08c08821ffefFAD3sMjkyP0Fd", "seq": 0, "version": 1}}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_question/opencode_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_question/opencode_transcript.ndjson index b36930e840a25..3c92c92018c91 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_question/opencode_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_question/opencode_transcript.ndjson @@ -1,8 +1,10 @@ -{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_question", "metadata": {"source": "opencode2-spike/recordings/question.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f14865e15ffewHnFwxdBlN6Eqc", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary by the spike recorder: the question tool's form is answered through T3. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn."}} +{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_question", "metadata": {"source": "opencode2-spike/recordings/question.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f14865e15ffewHnFwxdBlN6Eqc", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary by the spike recorder: the question tool's form is answered through T3. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn.", "permissionInventorySource": "opencode2_permission: agent.list recording reused for the permission-policy lookup"}} {"type": "expect_outbound", "label": "event.subscribe", "frame": {"type": "event.subscribe"}} {"type": "expect_outbound", "label": "model.list", "frame": {"type": "model.list", "input": {"location[directory]": ""}}} {"type": "emit_inbound", "label": "model.list.response", "frame": {"type": "sdk.response", "operation": "model.list", "data": {"location": {"directory": ""}, "data": [{"id": "space-bunny-free", "modelID": "space-bunny-free", "providerID": "opencode", "name": "Space Bunny Free", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text", "image", "video"], "output": ["text"]}, "variants": [{"id": "low", "settings": {"reasoningEffort": "low"}}, {"id": "medium", "settings": {"reasoningEffort": "medium"}}, {"id": "high", "settings": {"reasoningEffort": "high"}}, {"id": "xhigh", "settings": {"reasoningEffort": "xhigh"}}, {"id": "max", "settings": {"reasoningEffort": "max"}}], "time": {"released": 1790121600000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 1048576, "input": 524288, "output": 524288}}, {"id": "big-pickle", "modelID": "big-pickle", "providerID": "opencode", "family": "big-pickle", "name": "Big Pickle", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text"], "output": ["text"]}, "variants": [], "time": {"released": 1760659200000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 200000, "input": 160000, "output": 32000}}]}}} {"type": "emit_inbound", "label": "server.connected", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb79a1e5001wy4Omf7ROvJv6K", "type": "server.connected", "data": {}}}} +{"type": "expect_outbound", "label": "agent.list", "frame": {"type": "agent.list", "input": {"location[directory]": ""}}} +{"type": "emit_inbound", "label": "agent.list.response", "frame": {"type": "sdk.response", "operation": "agent.list", "data": {"location": {"directory": ""}, "data": [{"id": "build", "name": "Build", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "The default agent. Executes tools based on configured permissions.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}]}, {"id": "plan", "name": "Plan", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "Read-only agent for exploring the codebase and planning work before implementation.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}, {"action": "edit", "resource": "*", "effect": "deny"}, {"action": "edit", "resource": "/.opencode/plan/*", "effect": "allow"}, {"action": "external_directory", "resource": "/.opencode/plan/*", "effect": "allow"}]}]}}} {"type": "expect_outbound", "label": "session.create", "frame": {"type": "session.create", "input": ""}} {"type": "emit_inbound", "label": "session.create.response", "frame": {"type": "sdk.response", "operation": "session.create", "data": {"data": {"id": "ses_f14865e15ffewHnFwxdBlN6Eqc", "projectID": "global", "model": {"id": "big-pickle", "providerID": "opencode", "variant": "default"}, "cost": 0, "tokens": {"input": 0, "output": 0, "reasoning": 0, "cache": {"read": 0, "write": 0}}, "time": {"created": 1790657012250, "updated": 1790657012250}, "title": "spike question", "location": {"directory": ""}, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "subagent", "resource": "*", "effect": "deny"}]}}}} {"type": "emit_inbound", "label": "session.created", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb79a219001VaS9PAEJESAl1s", "created": 1790657012250, "type": "session.created", "location": {"directory": ""}, "data": {"sessionID": "ses_f14865e15ffewHnFwxdBlN6Eqc", "slug": "misty-cactus", "version": "2.0.18", "projectID": "global", "location": {"directory": ""}, "subpath": "", "title": "spike question", "model": {"id": "big-pickle", "providerID": "opencode"}}, "durable": {"aggregateID": "ses_f14865e15ffewHnFwxdBlN6Eqc", "seq": 0, "version": 1}}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_resume_after_restart/opencode_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_resume_after_restart/opencode_transcript.ndjson index 71c807f28abb0..257e12c6d303a 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_resume_after_restart/opencode_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_resume_after_restart/opencode_transcript.ndjson @@ -1,8 +1,10 @@ -{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_resume_after_restart", "metadata": {"source": "opencode2-spike/recordings/resume_after_restart.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f147f6435ffeBiqeQBvUukyPaj", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary by the spike recorder: a shell turn whose server is stopped mid-command (the event stream ends with no execution end), then the restarted server. After the restart `session/active` is empty, `session.get` has no outcome, and the history shows the shell call cancelled. T3 resubscribes, reads those in the order its reconnect does (active, then the history since the prompt), finds no `idle` item after the prompt, and ends the turn as interrupted. The next turn is a live 2.0.18 answer from the same session after a restart (oc2-9 orphan_b recording)."}} +{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_resume_after_restart", "metadata": {"source": "opencode2-spike/recordings/resume_after_restart.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f147f6435ffeBiqeQBvUukyPaj", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary by the spike recorder: a shell turn whose server is stopped mid-command (the event stream ends with no execution end), then the restarted server. After the restart `session/active` is empty, `session.get` has no outcome, and the history shows the shell call cancelled. T3 resubscribes, reads those in the order its reconnect does (active, then the history since the prompt), finds no `idle` item after the prompt, and ends the turn as interrupted. The next turn is a live 2.0.18 answer from the same session after a restart (oc2-9 orphan_b recording).", "permissionInventorySource": "opencode2_permission: agent.list recording reused for the permission-policy lookup", "permissionReadinessSource": "model.list response reused from this recording to await config plugins after reconnect"}} {"type": "expect_outbound", "label": "event.subscribe", "frame": {"type": "event.subscribe"}} {"type": "expect_outbound", "label": "model.list", "frame": {"type": "model.list", "input": {"location[directory]": ""}}} {"type": "emit_inbound", "label": "model.list.response", "frame": {"type": "sdk.response", "operation": "model.list", "data": {"location": {"directory": ""}, "data": [{"id": "space-bunny-free", "modelID": "space-bunny-free", "providerID": "opencode", "name": "Space Bunny Free", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text", "image", "video"], "output": ["text"]}, "variants": [{"id": "low", "settings": {"reasoningEffort": "low"}}, {"id": "medium", "settings": {"reasoningEffort": "medium"}}, {"id": "high", "settings": {"reasoningEffort": "high"}}, {"id": "xhigh", "settings": {"reasoningEffort": "xhigh"}}, {"id": "max", "settings": {"reasoningEffort": "max"}}], "time": {"released": 1790121600000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 1048576, "input": 524288, "output": 524288}}, {"id": "big-pickle", "modelID": "big-pickle", "providerID": "opencode", "family": "big-pickle", "name": "Big Pickle", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text"], "output": ["text"]}, "variants": [], "time": {"released": 1760659200000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 200000, "input": 160000, "output": 32000}}]}}} {"type": "emit_inbound", "label": "server.connected", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb809bc8001yN3A4d64cCRPSv", "type": "server.connected", "data": {}}}} +{"type": "expect_outbound", "label": "agent.list", "frame": {"type": "agent.list", "input": {"location[directory]": ""}}} +{"type": "emit_inbound", "label": "agent.list.response", "frame": {"type": "sdk.response", "operation": "agent.list", "data": {"location": {"directory": ""}, "data": [{"id": "build", "name": "Build", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "The default agent. Executes tools based on configured permissions.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}]}, {"id": "plan", "name": "Plan", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "Read-only agent for exploring the codebase and planning work before implementation.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}, {"action": "edit", "resource": "*", "effect": "deny"}, {"action": "edit", "resource": "/.opencode/plan/*", "effect": "allow"}, {"action": "external_directory", "resource": "/.opencode/plan/*", "effect": "allow"}]}]}}} {"type": "expect_outbound", "label": "session.create", "frame": {"type": "session.create", "input": ""}} {"type": "emit_inbound", "label": "session.create.response", "frame": {"type": "sdk.response", "operation": "session.create", "data": {"data": {"id": "ses_f147f6435ffeBiqeQBvUukyPaj", "projectID": "global", "model": {"id": "big-pickle", "providerID": "opencode", "variant": "default"}, "cost": 0, "tokens": {"input": 0, "output": 0, "reasoning": 0, "cache": {"read": 0, "write": 0}}, "time": {"created": 1790657469405, "updated": 1790657469405}, "title": "spike restart_a", "location": {"directory": ""}}}}} {"type": "emit_inbound", "label": "session.created", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb809bdd001yy0JandzMmaPi5", "created": 1790657469405, "type": "session.created", "location": {"directory": ""}, "data": {"sessionID": "ses_f147f6435ffeBiqeQBvUukyPaj", "slug": "happy-tiger", "version": "2.0.18", "projectID": "global", "location": {"directory": ""}, "subpath": "", "title": "spike restart_a", "model": {"id": "big-pickle", "providerID": "opencode"}}, "durable": {"aggregateID": "ses_f147f6435ffeBiqeQBvUukyPaj", "seq": 0, "version": 1}}}} @@ -31,6 +33,10 @@ {"type": "emit_inbound", "label": "session.active.response", "frame": {"type": "sdk.response", "operation": "session.active", "data": {"data": {}}}} {"type": "expect_outbound", "label": "message.list", "frame": {"type": "message.list", "input": {"sessionID": "ses_f147f6435ffeBiqeQBvUukyPaj", "order": "desc", "limit": "50"}}} {"type": "emit_inbound", "label": "message.list.response", "frame": {"type": "sdk.response", "operation": "message.list", "data": {"data": [{"id": "msg_0eb809c070018it3RDEnTWfluL", "time": {"created": 1790657469481, "streamed": 1790657471264, "completed": 1790657473514}, "type": "assistant", "agent": "build", "model": {"id": "big-pickle", "providerID": "opencode", "variant": "default"}, "content": [{"type": "text", "text": "I'll run that command now."}, {"type": "tool", "id": "call_function_pzunphrbm0be_1", "name": "shell", "executed": false, "state": {"status": "error", "input": {"command": "sleep 25 && echo RESUMED", "timeout": 40000}, "error": {"type": "unknown", "message": "Command cancelled"}, "metadata": {"shellID": "sh_0eb80a32a001qv1E0XuVCb3w7I"}}, "time": {"created": 1790657471251, "ran": 1790657471258, "completed": 1790657473465}}], "snapshot": {"start": "d580eac455a946e5bef414d21d0abbfb32eabdae", "end": "d580eac455a946e5bef414d21d0abbfb32eabdae", "files": []}, "finish": "tool-calls", "rawFinish": "tool_calls", "cost": 0, "tokens": {"input": 8764, "output": 49, "reasoning": 0, "cache": {"read": 486, "write": 0}}}, {"id": "msg_0eb809beb001jL03JQgCeojw1j", "time": {"created": 1790657469440}, "text": "Run the shell command `sleep 25 && echo RESUMED` with the bash tool, then reply with its output.", "type": "user"}], "cursor": {}}}} +{"type": "expect_outbound", "label": "model.list.after_restart", "frame": {"type": "model.list", "input": {"location[directory]": ""}}} +{"type": "emit_inbound", "label": "model.list.after_restart.response", "frame": {"type": "sdk.response", "operation": "model.list", "data": {"location": {"directory": ""}, "data": [{"id": "space-bunny-free", "modelID": "space-bunny-free", "providerID": "opencode", "name": "Space Bunny Free", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text", "image", "video"], "output": ["text"]}, "variants": [{"id": "low", "settings": {"reasoningEffort": "low"}}, {"id": "medium", "settings": {"reasoningEffort": "medium"}}, {"id": "high", "settings": {"reasoningEffort": "high"}}, {"id": "xhigh", "settings": {"reasoningEffort": "xhigh"}}, {"id": "max", "settings": {"reasoningEffort": "max"}}], "time": {"released": 1790121600000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 1048576, "input": 524288, "output": 524288}}, {"id": "big-pickle", "modelID": "big-pickle", "providerID": "opencode", "family": "big-pickle", "name": "Big Pickle", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text"], "output": ["text"]}, "variants": [], "time": {"released": 1760659200000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 200000, "input": 160000, "output": 32000}}]}}} +{"type": "expect_outbound", "label": "agent.list.after_restart", "frame": {"type": "agent.list", "input": {"location[directory]": ""}}} +{"type": "emit_inbound", "label": "agent.list.after_restart.response", "frame": {"type": "sdk.response", "operation": "agent.list", "data": {"location": {"directory": ""}, "data": [{"id": "build", "name": "Build", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "The default agent. Executes tools based on configured permissions.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}]}, {"id": "plan", "name": "Plan", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "Read-only agent for exploring the codebase and planning work before implementation.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}, {"action": "edit", "resource": "*", "effect": "deny"}, {"action": "edit", "resource": "/.opencode/plan/*", "effect": "allow"}, {"action": "external_directory", "resource": "/.opencode/plan/*", "effect": "allow"}]}]}}} {"type": "expect_outbound", "label": "session.prompt.2", "frame": {"type": "session.prompt", "input": {"sessionID": "ses_f147f6435ffeBiqeQBvUukyPaj", "id": "msg_0f0f3d724001pB8VE4h7fMXnjc", "text": ""}}} {"type": "emit_inbound", "label": "session.prompt.response.2", "frame": {"type": "sdk.response", "operation": "session.prompt", "data": {"data": {"id": "msg_0f0f3d724001pB8VE4h7fMXnjc", "sessionID": "ses_f147f6435ffeBiqeQBvUukyPaj", "time": {"created": 1790748907944}, "type": "user", "payload": {"text": "What did I last ask you to run? Answer in one short sentence."}, "delivery": "steer"}}}} {"type": "emit_inbound", "label": "plugin.updated", "frame": {"type": "sdk.event", "event": {"id": "evt_0f0f3d9a6001PmOgj7Wz31URka", "created": 1790748907942, "type": "plugin.updated", "location": {"directory": ""}, "data": {}}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_revert/opencode_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_revert/opencode_transcript.ndjson index bbf51a68926f6..780aa382e0b6a 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_revert/opencode_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_revert/opencode_transcript.ndjson @@ -1,8 +1,10 @@ -{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_revert", "metadata": {"source": "opencode2-spike/recordings/revert.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f1484bef1ffeGfSJ1e2FXLCaus", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary by the spike recorder: two turns write reverted.txt, then the thread rolls back to the first. Stages send files: false because T3 restores its own checkpoint. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn."}} +{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_revert", "metadata": {"source": "opencode2-spike/recordings/revert.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f1484bef1ffeGfSJ1e2FXLCaus", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary by the spike recorder: two turns write reverted.txt, then the thread rolls back to the first. Stages send files: false because T3 restores its own checkpoint. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn.", "permissionInventorySource": "opencode2_permission: agent.list recording reused for the permission-policy lookup"}} {"type": "expect_outbound", "label": "event.subscribe", "frame": {"type": "event.subscribe"}} {"type": "expect_outbound", "label": "model.list", "frame": {"type": "model.list", "input": {"location[directory]": ""}}} {"type": "emit_inbound", "label": "model.list.response", "frame": {"type": "sdk.response", "operation": "model.list", "data": {"location": {"directory": ""}, "data": [{"id": "space-bunny-free", "modelID": "space-bunny-free", "providerID": "opencode", "name": "Space Bunny Free", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text", "image", "video"], "output": ["text"]}, "variants": [{"id": "low", "settings": {"reasoningEffort": "low"}}, {"id": "medium", "settings": {"reasoningEffort": "medium"}}, {"id": "high", "settings": {"reasoningEffort": "high"}}, {"id": "xhigh", "settings": {"reasoningEffort": "xhigh"}}, {"id": "max", "settings": {"reasoningEffort": "max"}}], "time": {"released": 1790121600000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 1048576, "input": 524288, "output": 524288}}, {"id": "big-pickle", "modelID": "big-pickle", "providerID": "opencode", "family": "big-pickle", "name": "Big Pickle", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text"], "output": ["text"]}, "variants": [], "time": {"released": 1760659200000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 200000, "input": 160000, "output": 32000}}]}}} {"type": "emit_inbound", "label": "server.connected", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb7b410d001mJ5lqjFqXTbW7y", "type": "server.connected", "data": {}}}} +{"type": "expect_outbound", "label": "agent.list", "frame": {"type": "agent.list", "input": {"location[directory]": ""}}} +{"type": "emit_inbound", "label": "agent.list.response", "frame": {"type": "sdk.response", "operation": "agent.list", "data": {"location": {"directory": ""}, "data": [{"id": "build", "name": "Build", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "The default agent. Executes tools based on configured permissions.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}]}, {"id": "plan", "name": "Plan", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "Read-only agent for exploring the codebase and planning work before implementation.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}, {"action": "edit", "resource": "*", "effect": "deny"}, {"action": "edit", "resource": "/.opencode/plan/*", "effect": "allow"}, {"action": "external_directory", "resource": "/.opencode/plan/*", "effect": "allow"}]}]}}} {"type": "expect_outbound", "label": "session.create", "frame": {"type": "session.create", "input": ""}} {"type": "emit_inbound", "label": "session.create.response", "frame": {"type": "sdk.response", "operation": "session.create", "data": {"data": {"id": "ses_f1484bef1ffeGfSJ1e2FXLCaus", "projectID": "global", "model": {"id": "big-pickle", "providerID": "opencode", "variant": "default"}, "cost": 0, "tokens": {"input": 0, "output": 0, "reasoning": 0, "cache": {"read": 0, "write": 0}}, "time": {"created": 1790657118493, "updated": 1790657118493}, "title": "spike revert", "location": {"directory": ""}}}}} {"type": "emit_inbound", "label": "session.created", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb7b411d0014yaniNS8WiN6zE", "created": 1790657118493, "type": "session.created", "location": {"directory": ""}, "data": {"sessionID": "ses_f1484bef1ffeGfSJ1e2FXLCaus", "slug": "witty-canyon", "version": "2.0.18", "projectID": "global", "location": {"directory": ""}, "subpath": "", "title": "spike revert", "model": {"id": "big-pickle", "providerID": "opencode"}}, "durable": {"aggregateID": "ses_f1484bef1ffeGfSJ1e2FXLCaus", "seq": 0, "version": 1}}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_simple/opencode_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_simple/opencode_transcript.ndjson index 17b02dae519b9..d2fbcc2708106 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_simple/opencode_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_simple/opencode_transcript.ndjson @@ -1,8 +1,10 @@ -{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_simple", "metadata": {"source": "opencode2-spike/recordings/simple.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f148cdfa1ffeIFwbC0FCx0fKpk", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary by the spike recorder. The session.create expectation is T3's request for the recorded model and variant. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn."}} +{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_simple", "metadata": {"source": "opencode2-spike/recordings/simple.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f148cdfa1ffeIFwbC0FCx0fKpk", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary by the spike recorder. The session.create expectation is T3's request for the recorded model and variant. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn.", "permissionInventorySource": "opencode2_permission: agent.list recording reused for the permission-policy lookup"}} {"type": "expect_outbound", "label": "event.subscribe", "frame": {"type": "event.subscribe"}} {"type": "expect_outbound", "label": "model.list", "frame": {"type": "model.list", "input": {"location[directory]": ""}}} {"type": "emit_inbound", "label": "model.list.response", "frame": {"type": "sdk.response", "operation": "model.list", "data": {"location": {"directory": ""}, "data": [{"id": "space-bunny-free", "modelID": "space-bunny-free", "providerID": "opencode", "name": "Space Bunny Free", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text", "image", "video"], "output": ["text"]}, "variants": [{"id": "low", "settings": {"reasoningEffort": "low"}}, {"id": "medium", "settings": {"reasoningEffort": "medium"}}, {"id": "high", "settings": {"reasoningEffort": "high"}}, {"id": "xhigh", "settings": {"reasoningEffort": "xhigh"}}, {"id": "max", "settings": {"reasoningEffort": "max"}}], "time": {"released": 1790121600000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 1048576, "input": 524288, "output": 524288}}, {"id": "big-pickle", "modelID": "big-pickle", "providerID": "opencode", "family": "big-pickle", "name": "Big Pickle", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text"], "output": ["text"]}, "variants": [], "time": {"released": 1760659200000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 200000, "input": 160000, "output": 32000}}]}}} {"type": "emit_inbound", "label": "server.connected", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb73205b001QM7spc7rTwRevR", "type": "server.connected", "data": {}}}} +{"type": "expect_outbound", "label": "agent.list", "frame": {"type": "agent.list", "input": {"location[directory]": ""}}} +{"type": "emit_inbound", "label": "agent.list.response", "frame": {"type": "sdk.response", "operation": "agent.list", "data": {"location": {"directory": ""}, "data": [{"id": "build", "name": "Build", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "The default agent. Executes tools based on configured permissions.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}]}, {"id": "plan", "name": "Plan", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "Read-only agent for exploring the codebase and planning work before implementation.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}, {"action": "edit", "resource": "*", "effect": "deny"}, {"action": "edit", "resource": "/.opencode/plan/*", "effect": "allow"}, {"action": "external_directory", "resource": "/.opencode/plan/*", "effect": "allow"}]}]}}} {"type": "expect_outbound", "label": "session.create", "frame": {"type": "session.create", "input": {"location": "", "model": {"providerID": "opencode", "id": "space-bunny-free", "variant": "high"}, "permissions": ""}}} {"type": "emit_inbound", "label": "session.create.response", "frame": {"type": "sdk.response", "operation": "session.create", "data": {"data": {"id": "ses_f148cdfa1ffeIFwbC0FCx0fKpk", "projectID": "global", "model": {"id": "space-bunny-free", "providerID": "opencode", "variant": "high"}, "cost": 0, "tokens": {"input": 0, "output": 0, "reasoning": 0, "cache": {"read": 0, "write": 0}}, "time": {"created": 1790656585840, "updated": 1790656585840}, "title": "spike simple", "location": {"directory": ""}}}}} {"type": "emit_inbound", "label": "session.created", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb732070001MeC12AQ1Ds6a0a", "created": 1790656585840, "type": "session.created", "location": {"directory": ""}, "data": {"sessionID": "ses_f148cdfa1ffeIFwbC0FCx0fKpk", "slug": "sunny-pixel", "version": "2.0.18", "projectID": "global", "location": {"directory": ""}, "subpath": "", "title": "spike simple", "model": {"id": "space-bunny-free", "providerID": "opencode", "variant": "high"}}, "durable": {"aggregateID": "ses_f148cdfa1ffeIFwbC0FCx0fKpk", "seq": 0, "version": 1}}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_skill/opencode_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_skill/opencode_transcript.ndjson index 77dc36fbc42b5..5281bcd3de6db 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_skill/opencode_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_skill/opencode_transcript.ndjson @@ -1,8 +1,10 @@ -{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_skill", "metadata": {"source": "oc2-8 live recording skill_t3.ndjson (2.0.18, openrouter/deepseek/deepseek-v4-flash)", "capturedAt": "2026-09-29", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary: a prompt naming `$greet`, a workspace skill (.opencode/skills/greet/SKILL.md), so T3 reads the directory's skills and attaches it with prompt{skills}. Machine-wide skills are dropped from the recorded list. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn."}} +{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_skill", "metadata": {"source": "oc2-8 live recording skill_t3.ndjson (2.0.18, openrouter/deepseek/deepseek-v4-flash)", "capturedAt": "2026-09-29", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary: a prompt naming `$greet`, a workspace skill (.opencode/skills/greet/SKILL.md), so T3 reads the directory's skills and attaches it with prompt{skills}. Machine-wide skills are dropped from the recorded list. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn.", "permissionInventorySource": "opencode2_permission: agent.list recording reused for the permission-policy lookup"}} {"type": "expect_outbound", "label": "event.subscribe", "frame": {"type": "event.subscribe"}} {"type": "expect_outbound", "label": "model.list", "frame": {"type": "model.list", "input": {"location[directory]": ""}}} {"type": "emit_inbound", "label": "model.list.response", "frame": {"type": "sdk.response", "operation": "model.list", "data": {"location": {"directory": ""}, "data": [{"id": "deepseek/deepseek-v4-flash", "modelID": "deepseek/deepseek-v4-flash", "providerID": "openrouter", "family": "deepseek-flash", "name": "DeepSeek V4 Flash", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openrouter", "settings": {"baseURL": "https://openrouter.ai/api/v1"}, "headers": {"HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode"}, "capabilities": {"tools": true, "input": ["text"], "output": ["text"]}, "variants": [{"id": "none", "settings": {"reasoning": {"enabled": false}}}, {"id": "high", "settings": {"reasoning": {"effort": "high"}}}, {"id": "xhigh", "settings": {"reasoning": {"effort": "xhigh"}}}], "time": {"released": 1776988800000}, "cost": [{"input": 0.14, "output": 0.28, "cache": {"read": 0.028, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 1048576, "output": 131072}}]}}} {"type": "emit_inbound", "label": "server.connected", "frame": {"type": "sdk.event", "event": {"id": "evt_0f0d83f30001TUsYBmqL72JOVk", "type": "server.connected", "data": {}}}} +{"type": "expect_outbound", "label": "agent.list", "frame": {"type": "agent.list", "input": {"location[directory]": ""}}} +{"type": "emit_inbound", "label": "agent.list.response", "frame": {"type": "sdk.response", "operation": "agent.list", "data": {"location": {"directory": ""}, "data": [{"id": "build", "name": "Build", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "The default agent. Executes tools based on configured permissions.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}]}, {"id": "plan", "name": "Plan", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "Read-only agent for exploring the codebase and planning work before implementation.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}, {"action": "edit", "resource": "*", "effect": "deny"}, {"action": "edit", "resource": "/.opencode/plan/*", "effect": "allow"}, {"action": "external_directory", "resource": "/.opencode/plan/*", "effect": "allow"}]}]}}} {"type": "expect_outbound", "label": "session.create", "frame": {"type": "session.create", "input": ""}} {"type": "emit_inbound", "label": "session.create.response", "frame": {"type": "sdk.response", "operation": "session.create", "data": {"data": {"id": "ses_f0f27bf90ffeEzhR6OIvZo6Y1l", "projectID": "global", "model": {"id": "deepseek/deepseek-v4-flash", "providerID": "openrouter", "variant": "default"}, "cost": 0, "tokens": {"input": 0, "output": 0, "reasoning": 0, "cache": {"read": 0, "write": 0}}, "time": {"created": 1790747099267, "updated": 1790747099267}, "title": "oc2-8 skill_t3", "permissions": [{"action": "*", "resource": "*", "effect": "allow"}], "location": {"directory": ""}}}}} {"type": "emit_inbound", "label": "session.created", "frame": {"type": "sdk.event", "event": {"id": "evt_0f0d84083001QSHajGz7TcpzgV", "created": 1790747099267, "type": "session.created", "location": {"directory": ""}, "data": {"sessionID": "ses_f0f27bf90ffeEzhR6OIvZo6Y1l", "slug": "happy-rocket", "version": "2.0.18", "projectID": "global", "location": {"directory": ""}, "subpath": "", "title": "oc2-8 skill_t3", "permissions": [{"action": "*", "resource": "*", "effect": "allow"}], "model": {"id": "deepseek/deepseek-v4-flash", "providerID": "openrouter"}}, "durable": {"aggregateID": "ses_f0f27bf90ffeEzhR6OIvZo6Y1l", "seq": 0, "version": 1}}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_subagent/opencode_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_subagent/opencode_transcript.ndjson index e5fee23954291..87126f4d645c0 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_subagent/opencode_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_subagent/opencode_transcript.ndjson @@ -1,8 +1,10 @@ -{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_subagent", "metadata": {"source": "opencode2-spike/recordings/subagent.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f14862990ffe1Cj3Y1mn0nJq0R", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn."}} +{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_subagent", "metadata": {"source": "opencode2-spike/recordings/subagent.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f14862990ffe1Cj3Y1mn0nJq0R", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn.", "permissionInventorySource": "opencode2_permission: agent.list recording reused for the permission-policy lookup"}} {"type": "expect_outbound", "label": "event.subscribe", "frame": {"type": "event.subscribe"}} {"type": "expect_outbound", "label": "model.list", "frame": {"type": "model.list", "input": {"location[directory]": ""}}} {"type": "emit_inbound", "label": "model.list.response", "frame": {"type": "sdk.response", "operation": "model.list", "data": {"location": {"directory": ""}, "data": [{"id": "space-bunny-free", "modelID": "space-bunny-free", "providerID": "opencode", "name": "Space Bunny Free", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text", "image", "video"], "output": ["text"]}, "variants": [{"id": "low", "settings": {"reasoningEffort": "low"}}, {"id": "medium", "settings": {"reasoningEffort": "medium"}}, {"id": "high", "settings": {"reasoningEffort": "high"}}, {"id": "xhigh", "settings": {"reasoningEffort": "xhigh"}}, {"id": "max", "settings": {"reasoningEffort": "max"}}], "time": {"released": 1790121600000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 1048576, "input": 524288, "output": 524288}}, {"id": "big-pickle", "modelID": "big-pickle", "providerID": "opencode", "family": "big-pickle", "name": "Big Pickle", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text"], "output": ["text"]}, "variants": [], "time": {"released": 1760659200000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 200000, "input": 160000, "output": 32000}}]}}} {"type": "emit_inbound", "label": "server.connected", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb79d66d001akjvdikgkVMT2n", "type": "server.connected", "data": {}}}} +{"type": "expect_outbound", "label": "agent.list", "frame": {"type": "agent.list", "input": {"location[directory]": ""}}} +{"type": "emit_inbound", "label": "agent.list.response", "frame": {"type": "sdk.response", "operation": "agent.list", "data": {"location": {"directory": ""}, "data": [{"id": "build", "name": "Build", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "The default agent. Executes tools based on configured permissions.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}]}, {"id": "plan", "name": "Plan", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "Read-only agent for exploring the codebase and planning work before implementation.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}, {"action": "edit", "resource": "*", "effect": "deny"}, {"action": "edit", "resource": "/.opencode/plan/*", "effect": "allow"}, {"action": "external_directory", "resource": "/.opencode/plan/*", "effect": "allow"}]}]}}} {"type": "expect_outbound", "label": "session.create", "frame": {"type": "session.create", "input": ""}} {"type": "emit_inbound", "label": "session.create.response", "frame": {"type": "sdk.response", "operation": "session.create", "data": {"data": {"id": "ses_f14862990ffe1Cj3Y1mn0nJq0R", "projectID": "global", "model": {"id": "big-pickle", "providerID": "opencode", "variant": "default"}, "cost": 0, "tokens": {"input": 0, "output": 0, "reasoning": 0, "cache": {"read": 0, "write": 0}}, "time": {"created": 1790657025663, "updated": 1790657025663}, "title": "spike subagent", "location": {"directory": ""}}}}} {"type": "emit_inbound", "label": "session.created", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb79d67f001cKJ38bhZTN6LOd", "created": 1790657025663, "type": "session.created", "location": {"directory": ""}, "data": {"sessionID": "ses_f14862990ffe1Cj3Y1mn0nJq0R", "slug": "tidy-wizard", "version": "2.0.18", "projectID": "global", "location": {"directory": ""}, "subpath": "", "title": "spike subagent", "model": {"id": "big-pickle", "providerID": "opencode"}}, "durable": {"aggregateID": "ses_f14862990ffe1Cj3Y1mn0nJq0R", "seq": 0, "version": 1}}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_tool_call/opencode_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_tool_call/opencode_transcript.ndjson index 64e5eff7d73ad..df9b87e70db46 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_tool_call/opencode_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/opencode2_tool_call/opencode_transcript.ndjson @@ -1,8 +1,10 @@ -{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_tool_call", "metadata": {"source": "opencode2-spike/recordings/tool_call.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f148ca2deffeJcwCnRQtb0YFNX", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary by the spike recorder. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn."}} +{"type": "transcript_start", "provider": "opencode", "protocol": "opencode2-http.sse", "version": "2.0.18", "scenario": "opencode2_tool_call", "metadata": {"source": "opencode2-spike/recordings/tool_call.ndjson", "capturedAt": "2026-09-28", "nativeSessionId": "ses_f148ca2deffeJcwCnRQtb0YFNX", "description": "A real OpenCode 2.0.18 session recorded at the HTTP and SSE boundary by the spike recorder. T3's per-turn instructions entry (answered 204, as 2.0.18 does) is written before the thread's first turn.", "permissionInventorySource": "opencode2_permission: agent.list recording reused for the permission-policy lookup"}} {"type": "expect_outbound", "label": "event.subscribe", "frame": {"type": "event.subscribe"}} {"type": "expect_outbound", "label": "model.list", "frame": {"type": "model.list", "input": {"location[directory]": ""}}} {"type": "emit_inbound", "label": "model.list.response", "frame": {"type": "sdk.response", "operation": "model.list", "data": {"location": {"directory": ""}, "data": [{"id": "space-bunny-free", "modelID": "space-bunny-free", "providerID": "opencode", "name": "Space Bunny Free", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text", "image", "video"], "output": ["text"]}, "variants": [{"id": "low", "settings": {"reasoningEffort": "low"}}, {"id": "medium", "settings": {"reasoningEffort": "medium"}}, {"id": "high", "settings": {"reasoningEffort": "high"}}, {"id": "xhigh", "settings": {"reasoningEffort": "xhigh"}}, {"id": "max", "settings": {"reasoningEffort": "max"}}], "time": {"released": 1790121600000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 1048576, "input": 524288, "output": 524288}}, {"id": "big-pickle", "modelID": "big-pickle", "providerID": "opencode", "family": "big-pickle", "name": "Big Pickle", "compatibility": {"reasoningField": "reasoning_content"}, "package": "@opencode/ai/providers/openai-compatible", "settings": {"apiKey": "public", "baseURL": "https://opencode.ai/zen/v1", "provider": "opencode"}, "capabilities": {"tools": true, "input": ["text"], "output": ["text"]}, "variants": [], "time": {"released": 1760659200000}, "cost": [{"input": 0, "output": 0, "cache": {"read": 0, "write": 0}}], "status": "active", "enabled": true, "limit": {"context": 200000, "input": 160000, "output": 32000}}]}}} {"type": "emit_inbound", "label": "server.connected", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb735d1f001svh3y4Umi1226r", "type": "server.connected", "data": {}}}} +{"type": "expect_outbound", "label": "agent.list", "frame": {"type": "agent.list", "input": {"location[directory]": ""}}} +{"type": "emit_inbound", "label": "agent.list.response", "frame": {"type": "sdk.response", "operation": "agent.list", "data": {"location": {"directory": ""}, "data": [{"id": "build", "name": "Build", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "The default agent. Executes tools based on configured permissions.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}]}, {"id": "plan", "name": "Plan", "request": {"settings": {}, "headers": {}, "body": {}}, "description": "Read-only agent for exploring the codebase and planning work before implementation.", "mode": "primary", "hidden": false, "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "external_directory", "resource": "*", "effect": "ask"}, {"action": "read", "resource": "*.env", "effect": "ask"}, {"action": "read", "resource": "*.env.*", "effect": "ask"}, {"action": "read", "resource": "*.env.example", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/shell/*/*", "effect": "allow"}, {"action": "external_directory", "resource": "/data/opencode/tool-output/*", "effect": "allow"}, {"action": "external_directory", "resource": "/tmp/opencode/*", "effect": "allow"}, {"action": "external_directory", "resource": "/config/opencode/*", "effect": "allow"}, {"action": "question", "resource": "*", "effect": "allow"}, {"action": "edit", "resource": "*", "effect": "deny"}, {"action": "edit", "resource": "/.opencode/plan/*", "effect": "allow"}, {"action": "external_directory", "resource": "/.opencode/plan/*", "effect": "allow"}]}]}}} {"type": "expect_outbound", "label": "session.create", "frame": {"type": "session.create", "input": ""}} {"type": "emit_inbound", "label": "session.create.response", "frame": {"type": "sdk.response", "operation": "session.create", "data": {"data": {"id": "ses_f148ca2deffeJcwCnRQtb0YFNX", "projectID": "global", "model": {"id": "big-pickle", "providerID": "opencode", "variant": "default"}, "cost": 0, "tokens": {"input": 0, "output": 0, "reasoning": 0, "cache": {"read": 0, "write": 0}}, "time": {"created": 1790656601394, "updated": 1790656601394}, "title": "spike tool_call", "location": {"directory": ""}}}}} {"type": "emit_inbound", "label": "session.created", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb735d32001oBstTIapVlESgH", "created": 1790656601394, "type": "session.created", "location": {"directory": ""}, "data": {"sessionID": "ses_f148ca2deffeJcwCnRQtb0YFNX", "slug": "shiny-pixel", "version": "2.0.18", "projectID": "global", "location": {"directory": ""}, "subpath": "", "title": "spike tool_call", "model": {"id": "big-pickle", "providerID": "opencode"}}, "durable": {"aggregateID": "ses_f148ca2deffeJcwCnRQtb0YFNX", "seq": 0, "version": 1}}}} diff --git a/docs/user/permission-modes.md b/docs/user/permission-modes.md index 796e3f316122d..155949b2cb1e7 100644 --- a/docs/user/permission-modes.md +++ b/docs/user/permission-modes.md @@ -28,7 +28,8 @@ the parent conversation with the subagent's name, so you can respond without swi Providers enforce permissions differently. Some read-only actions can proceed in **Supervised**. **Auto** uses automatic review on Codex, Claude, Cursor, Grok, and fx; providers without an equivalent, -including OpenCode and Antigravity, fall back to asking. On Grok, commands its review blocks come +including OpenCode and Antigravity, fall back to asking. OpenCode 2's Auto also honors +[action-specific native permission rules](./providers-opencode.md#approvals). On Grok, commands its review blocks come to you for approval. On fx, prompts it still sends also come to you. Muse Code offers only **Supervised** and **Full access**. A Muse thread already set to another mode diff --git a/docs/user/providers-opencode.md b/docs/user/providers-opencode.md index c686fe10a7c8d..c6b6239f4b5cf 100644 --- a/docs/user/providers-opencode.md +++ b/docs/user/providers-opencode.md @@ -62,9 +62,14 @@ session. ## Approvals -OpenCode follows the shared [permission modes](./permission-modes.md). **Auto** has -the same rules as **Supervised** because OpenCode has no AI approval reviewer. -Environment files such as `.env` and `.env.local` need approval in restricted +OpenCode follows the shared [permission modes](./permission-modes.md). OpenCode 2's +**Auto** honors native permission rules for specific actions, such as `shell`, +including command patterns that allow routine commands and ask for others. Without +those rules it asks, as **Supervised** does; OpenCode has no AI approval reviewer. +A catch-all `"*": "allow"` does not remove Auto's default approval requirements. +**Full access** skips prompts but preserves native deny rules and their exceptions. +OpenCode 1.x keeps its existing mode rules, including Supervised behavior in Auto. +By default, environment files such as `.env` and `.env.local` need approval in restricted modes even though normal file reads do not; `.env.example` is allowed. **Allow for workspace** applies to matching requests in other OpenCode sessions diff --git a/packages/provider-opencode/src/server/v2/adapter.ts b/packages/provider-opencode/src/server/v2/adapter.ts index 6604f84eb289e..c41b55e560716 100644 --- a/packages/provider-opencode/src/server/v2/adapter.ts +++ b/packages/provider-opencode/src/server/v2/adapter.ts @@ -430,23 +430,34 @@ const mcpRules = (mcpServerName: string | null): ReadonlyArray => const sessionRules = ( policy: RulesPolicy, + nativeRules: ReadonlyArray, paths: ReadonlyArray, grants: ReadonlyArray, mcpServerName: string | null, -): ReadonlyArray => [ - ...(policy.runtimeMode === "full-access" - ? [rule("*", "allow")] - : [ - rule("shell", "ask"), - rule("edit", policy.runtimeMode === "auto-accept-edits" ? "allow" : "ask"), - rule("external_directory", "ask"), - ]), - ...grants, - // Plan edits are re-allowed only for plan paths; free-tier sessions refuse denied shell/read. - ...(policy.interactionMode === "plan" ? [rule("edit", "deny")] : []), - ...paths, - ...mcpRules(mcpServerName), -]; +): ReadonlyArray => { + const rules = [ + ...(policy.runtimeMode === "full-access" + ? [rule("*", "allow")] + : [ + rule("shell", "ask"), + rule("edit", policy.runtimeMode === "auto-accept-edits" ? "allow" : "ask"), + rule("external_directory", "ask"), + ]), + ...nativeRules, + ...grants, + // Plan edits are re-allowed only for plan paths; free-tier sessions refuse denied shell/read. + ...(policy.interactionMode === "plan" ? [rule("edit", "deny")] : []), + ...paths, + ...mcpRules(mcpServerName), + ]; + const lastRules = new Map(); + for (const entry of rules) { + const key = `${entry.action}\u0000${entry.resource}`; + lastRules.delete(key); + lastRules.set(key, entry); + } + return [...lastRules.values()]; +}; const sameRules = (left: ReadonlyArray | undefined, right: ReadonlyArray) => left?.length === right.length && @@ -536,6 +547,30 @@ const agentPaths = (rules: ReadonlyArray) => (entry.action === "edit" || entry.action === "external_directory"), ); +// Plan's edit deny is re-opened only by native edit allows, replayed in order with edit denies so a +// later deny still wins. Other rules stay where Auto and Full access put them, before session grants. +const planPaths = (rules: ReadonlyArray) => + rules.filter((entry) => + entry.action === "edit" + ? entry.effect === "deny" || (entry.effect === "allow" && entry.resource !== "*") + : entry.action === "external_directory" && entry.effect === "allow" && entry.resource !== "*", + ); + +const nativeSessionRules = (rules: ReadonlyArray, policy: RulesPolicy) => { + if (policy.runtimeMode === "auto") { + // The default agent's catch-all allow is not an explicit opt-in to unrestricted Auto. + return rules.filter((entry) => entry.action !== "*" || entry.effect !== "allow"); + } + if (policy.runtimeMode !== "full-access") return []; + const firstDeny = rules.findIndex((entry) => entry.effect === "deny"); + // Keep later exceptions in order: a deny-only copy would also block allowed .env.example files. + return firstDeny < 0 + ? [] + : rules + .slice(firstDeny) + .map((entry) => (entry.effect === "ask" ? { ...entry, effect: "allow" as const } : entry)); +}; + const ref = (nativeId: string, strength: "strong" | "weak" = "strong") => ({ driver: OPENCODE_PROVIDER, nativeId, @@ -3664,6 +3699,8 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr ); }); + const agentRules = new Map>>(); + const staleAgentRules = new Set(); const reconnect = Effect.gen(function* () { // Release the borrow on every failure, including reconnect interruption, so spawned servers can idle-stop. const scope = yield* Scope.make(); @@ -3684,6 +3721,9 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr }; currentScope = scope; yield* Scope.close(previous, Exit.void); + // A restarted server may load a changed native config; re-read permissions on next use. + for (const directory of agentRules.keys()) staleAgentRules.add(directory); + agentRules.clear(); // A restarted server forgot T3's MCP servers; the next turn adds them again. // The gap may have started a pending prompt unseen, so none may set a later wake's boundary. for (const state of threads.values()) { @@ -3864,13 +3904,16 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr contextWindows.has(directoryOf(cwd)) ? Effect.void : readModels(directoryOf(cwd)); yield* readModels(session.cwd); - const agentRules = new Map>>(); - const pathsFor = Effect.fnUntraced(function* ( + const rulesForAgents = Effect.fn("OpenCode2Adapter.rulesForAgents")(function* ( directory: string, agents: ReadonlyArray, ) { let known = agentRules.get(directory); if (known === undefined) { + if (staleAgentRules.delete(directory)) { + // model.list waits for native config plugins; agent.list can return builtins before they load. + yield* readModels(directory); + } // A fresh server lists no agents for its first moments, like its models. const listed = yield* client.agent.list({ location: { directory } }).pipe( Effect.repeat({ @@ -3878,27 +3921,11 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr schedule: Schedule.spaced("250 millis"), }), Effect.timeout("5 seconds"), - Effect.tapCause((cause) => - Effect.logWarning( - "Could not list OpenCode agents; their path rules are skipped.", - cause, - ).pipe(Effect.annotateLogs({ errorTag: causeErrorTag(cause) })), - ), - Effect.option, - ); - if (listed._tag === "None") return []; - known = new Map( - listed.value.data.map((agent) => [agent.id, agentPaths(agent.permissions)]), ); + known = new Map(listed.data.map((agent) => [agent.id, agent.permissions])); agentRules.set(directory, known); } - const paths = new Map(); - for (const agent of agents) { - for (const entry of known.get(agent) ?? []) { - paths.set(`${entry.action}\u0000${entry.resource}`, entry); - } - } - return [...paths.values()]; + return agents.flatMap((agent) => known.get(agent) ?? []); }); const rulesFor = Effect.fnUntraced(function* ( @@ -3907,12 +3934,15 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr appThreadId: string | null, ) { const plan = policy.interactionMode === "plan"; - const paths = - policy.runtimeMode === "full-access" && !plan + const nativeRules = yield* rulesForAgents(thread.directory, [thread.agent]); + const paths = plan + ? planPaths(yield* rulesForAgents(thread.directory, [thread.agent, "plan"])) + : policy.runtimeMode === "auto" || policy.runtimeMode === "full-access" ? [] - : yield* pathsFor(thread.directory, plan ? [thread.agent, "plan"] : [thread.agent]); + : agentPaths(nativeRules); return sessionRules( policy, + nativeSessionRules(nativeRules, policy), paths, policy.runtimeMode === "full-access" ? [] : thread.grants, appThreadId === null ? null : yield* mcpServerNameFor(appThreadId), @@ -4682,8 +4712,8 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr Effect.ignore({ log: true }), ); } - if (!loaded) yield* recoverShells(state).pipe(Effect.ignore({ log: true })); yield* writeRules(state, threadInput.runtimePolicy ?? state.policy); + if (!loaded) yield* recoverShells(state).pipe(Effect.ignore({ log: true })); if (cwd != null && native.location.directory !== cwd) { yield* client.session.move({ sessionID: Session.ID.make(sessionId),