import { assert, describe, it } from "@effect/vitest"; import type { ToolPart } from "@opencode-ai/sdk/v2"; import { NodeId, OpenCodeSettings, ProjectId, ProviderInstanceId, ProviderSessionId, ProviderThreadId, ProviderTurnId, RunAttemptId, RunId, MessageId, ThreadId, type OrchestrationV2ProviderTurn, } from "@t3tools/contracts"; import * as Effect from "effect/Effect"; import * as DateTime from "effect/DateTime"; import * as Deferred from "effect/Deferred"; import * as Fiber from "effect/Fiber"; import * as Exit from "effect/Exit"; import * as Queue from "effect/Queue"; import * as Schema from "effect/Schema"; import * as Stream from "effect/Stream"; import * as TestClock from "effect/testing/TestClock"; import type { EventNdjsonLogger } from "../../provider/Layers/EventNdjsonLogger.ts"; import type { OpenCodeRuntimeShape } from "../../provider/opencodeRuntime.ts"; import type { ServerConfig } from "../../config.ts"; import { IdAllocatorV2, layer as idAllocatorLayer } from "../IdAllocator.ts"; import { advanceOpenCodePromptAdmission, cancelOpenCodePromptAdmission, openCodeBoundaryAfterProviderTurn, openCodeChildPermissionRules, openCodePermissionRules, openCodePermissionRequestKind, openCodeToolProjectionKind, makeOpenCodeProtocolLogger, makeOpenCodeAdapterV2, OPENCODE_PROVIDER, OpenCodeProviderCapabilitiesV2, reconcileOpenCodePromptAdmissionStatus, } from "./OpenCodeAdapterV2.ts"; import { ProviderAdapterV2RuntimePolicy } from "../ProviderAdapter.ts"; const encodeUnknownJson = Schema.encodeUnknownSync(Schema.fromJsonString(Schema.Unknown)); const OPEN_CODE_TEST_SETTINGS = Schema.decodeSync(OpenCodeSettings)({ serverUrl: "http://test.invalid", }); function promiseGate() { let resolve!: (value: A) => void; const promise = new Promise((done) => { resolve = done; }); return { promise, resolve }; } function asyncEventStream() { const values: Array<{ value: unknown; handled: ReturnType> }> = []; const waiters: Array<(value: IteratorResult) => void> = []; let previousHandled: ReturnType> | undefined; return { close() { for (const waiter of waiters.splice(0)) waiter({ done: true, value: undefined }); }, push(value: unknown) { const handled = promiseGate(); const waiter = waiters.shift(); if (waiter) { previousHandled = handled; waiter({ done: false, value }); } else values.push({ value, handled }); return handled.promise; }, stream: { [Symbol.asyncIterator]() { return { next: () => { previousHandled?.resolve(); const entry = values.shift(); if (entry !== undefined) { previousHandled = entry.handled; return Promise.resolve({ done: false as const, value: entry.value }); } return new Promise>((resolve) => waiters.push(resolve)); }, }; }, }, }; } function runtimePolicy( runtimeMode: ProviderAdapterV2RuntimePolicy["runtimeMode"], override: Partial = {}, ): ProviderAdapterV2RuntimePolicy { return ProviderAdapterV2RuntimePolicy.make({ runtimeMode, interactionMode: "default", cwd: null, ...override, }); } function permissionAction(rules: ReturnType, permission: string) { return rules.findLast((rule) => rule.permission === "*" || rule.permission === permission) ?.action; } function providerTurn(input: { readonly id: string; readonly ordinal: number; readonly nativeId: string | null; }): OrchestrationV2ProviderTurn { return { id: ProviderTurnId.make(input.id), providerThreadId: ProviderThreadId.make("provider-thread:opencode-test"), nodeId: NodeId.make(`node:${input.id}`), runAttemptId: null, nativeTurnRef: input.nativeId === null ? null : { driver: OPENCODE_PROVIDER, nativeId: input.nativeId, strength: "weak" }, ordinal: input.ordinal, status: "completed", startedAt: null, completedAt: null, }; } const makeOpenCodeRuntimeHarness = Effect.fn("makeOpenCodeRuntimeHarness")(function* ( suffix: string, nativeSessionId: string, client: object, ) { const idAllocator = yield* IdAllocatorV2; const instanceId = ProviderInstanceId.make(`opencode-${suffix}`); const threadId = ThreadId.make(`thread-opencode-${suffix}`); const modelSelection = { instanceId, model: "anthropic/claude-sonnet", options: [], }; const policy = runtimePolicy("full-access", { cwd: "/workspace" }); const adapter = makeOpenCodeAdapterV2({ instanceId, settings: OPEN_CODE_TEST_SETTINGS, environment: {}, runtime: { connectToOpenCodeServer: () => Effect.succeed({ url: "http://test.invalid", external: true }), createOpenCodeSdkClient: () => client, } as unknown as OpenCodeRuntimeShape, idAllocator, serverConfig: { cwd: "/workspace", attachmentsDir: "/tmp/attachments", } as ServerConfig["Service"], }); const runtime = yield* adapter.openSession({ threadId, providerSessionId: ProviderSessionId.make(`session-opencode-${suffix}`), modelSelection, runtimePolicy: policy, }); const providerThread = yield* runtime.ensureThread({ threadId, modelSelection, runtimePolicy: policy, }); const now = yield* DateTime.now; const startTurn = (text = "hello") => runtime.startTurn({ appThread: { id: threadId, projectId: ProjectId.make(`project-opencode-${suffix}`), title: suffix, providerInstanceId: modelSelection.instanceId, modelSelection, runtimeMode: "full-access", interactionMode: "default", branch: null, worktreePath: null, activeProviderThreadId: providerThread.id, lineage: { parentThreadId: null, relationshipToParent: null, rootThreadId: threadId }, forkedFrom: null, createdBy: "user", creationSource: "web", createdAt: now, updatedAt: now, archivedAt: null, settledOverride: null, settledAt: null, lastVisitedAt: null, deletedAt: null, }, threadId, runId: RunId.make(`run-opencode-${suffix}`), runOrdinal: 1, providerTurnOrdinal: 1, attemptId: RunAttemptId.make(`attempt-opencode-${suffix}`), rootNodeId: NodeId.make(`node-opencode-${suffix}`), providerThread, message: { createdBy: "user", creationSource: "web", messageId: MessageId.make(`message-opencode-${suffix}`), text, attachments: [], }, modelSelection, runtimePolicy: policy, }); return { nativeSessionId, now, policy, providerThread, runId: RunId.make(`run-opencode-${suffix}`), runtime, startTurn, threadId, }; }); describe("OpenCodeAdapterV2", () => { it.effect( "preserves tool lifecycle, approval kinds, and late assistant text without cached tool payloads", () => Effect.gen(function* () { const nativeEvents = asyncEventStream(); const nativeSessionId = "native-opencode-tool-lifecycle"; const harness = yield* makeOpenCodeRuntimeHarness("tool-lifecycle", nativeSessionId, { event: { subscribe: async (_input: unknown, options: { signal?: AbortSignal }) => { options.signal?.addEventListener("abort", () => nativeEvents.close(), { once: true }); return { stream: nativeEvents.stream }; }, }, session: { create: async () => ({ data: { id: nativeSessionId, time: { created: 1, updated: 1 } }, }), promptAsync: async () => ({ data: true }), }, }); yield* harness.startTurn(); const received = yield* harness.runtime.events.pipe( Stream.takeUntil( (event) => event.type === "turn_item.updated" && event.turnItem.type === "compaction", ), Stream.runCollect, Effect.forkScoped, ); const states = [ { status: "pending", input: { command: "pwd" }, raw: "" }, { status: "running", input: { command: "pwd" }, title: "Working directory", time: { start: 1 }, }, { status: "completed", input: { command: "pwd" }, output: "/repo\n", title: "Working directory", metadata: {}, time: { start: 1, end: 2 }, }, { status: "error", input: { command: "pwd" }, error: "Command failed", time: { start: 3, end: 4 }, }, ] satisfies ReadonlyArray; for (const state of states) { yield* Effect.promise(() => nativeEvents.push({ type: "message.part.updated", properties: { sessionID: nativeSessionId, part: { id: state.status === "error" ? "part-failed" : "part-working", sessionID: nativeSessionId, messageID: "late-assistant", type: "tool", callID: state.status === "error" ? "call-failed" : "call-working", tool: "bash", state, }, }, }), ); } yield* Effect.promise(() => nativeEvents.push({ type: "message.part.updated", properties: { sessionID: nativeSessionId, part: { id: "part-read", sessionID: nativeSessionId, messageID: "late-assistant", type: "tool", callID: "call-read", tool: "read", state: { status: "pending", input: { filePath: "/outside/file" }, raw: "" }, }, }, }), ); yield* Effect.promise(() => nativeEvents.push({ type: "permission.asked", properties: { id: "read-permission", sessionID: nativeSessionId, permission: "external_directory", patterns: ["/outside/*"], always: [], metadata: {}, tool: { messageID: "late-assistant", callID: "call-read" }, }, }), ); yield* Effect.promise(() => nativeEvents.push({ type: "message.part.updated", properties: { sessionID: nativeSessionId, part: { id: "part-text", sessionID: nativeSessionId, messageID: "late-assistant", type: "text", text: "Tool results received", time: { start: 4 }, }, }, }), ); yield* Effect.promise(() => nativeEvents.push({ type: "message.updated", properties: { sessionID: nativeSessionId, info: { id: "late-assistant", sessionID: nativeSessionId, role: "assistant", time: { created: 1, completed: 4 }, }, }, }), ); yield* Effect.promise(() => nativeEvents.push({ type: "session.compacted", properties: { sessionID: nativeSessionId }, }), ); const events = yield* Fiber.join(received); const items = events.flatMap((event) => event.type === "turn_item.updated" ? [event.turnItem] : [], ); const commands = items.filter((item) => item.type === "command_execution"); assert.deepEqual( commands.map((item) => item.status), ["pending", "running", "completed", "failed"], ); assert.deepEqual( commands.map((item) => item.input), ["pwd", "pwd", "pwd", "pwd"], ); assert.equal(commands[2]?.output, "/repo\n"); assert.equal(commands[3]?.output, "Command failed"); assert.isTrue( events.some( (event) => event.type === "runtime_request.updated" && event.runtimeRequest.kind === "file-read", ), ); const assistant = items.filter((item) => item.type === "assistant_message"); assert.equal(assistant.at(-1)?.text, "Tool results received"); assert.equal(assistant.at(-1)?.status, "completed"); }).pipe(Effect.provide(idAllocatorLayer), Effect.scoped), ); it.effect("compacts with the native summarize API and emits a completed compaction", () => Effect.gen(function* () { const nativeEvents = asyncEventStream(); const summarizeCalls: Array = []; let promptCalls = 0; const harness = yield* makeOpenCodeRuntimeHarness("compact", "native-opencode-compact", { event: { subscribe: async (_input: unknown, options: { signal?: AbortSignal }) => { options.signal?.addEventListener("abort", () => nativeEvents.close(), { once: true }); return { stream: nativeEvents.stream }; }, }, session: { create: async () => ({ data: { id: "native-opencode-compact", time: { created: 1, updated: 1 } }, }), summarize: async (input: unknown) => { summarizeCalls.push(input); return { data: true }; }, promptAsync: async () => { promptCalls += 1; return { data: true }; }, }, }); yield* harness.startTurn("/compact"); const events = yield* harness.runtime.events.pipe( Stream.takeUntil((event) => event.type === "turn.terminal"), Stream.runCollect, ); assert.deepEqual(summarizeCalls, [ { sessionID: "native-opencode-compact", providerID: "anthropic", modelID: "claude-sonnet", auto: false, }, ]); assert.equal(promptCalls, 0); assert.equal( events.filter( (event) => event.type === "turn_item.updated" && event.turnItem.type === "compaction" && event.turnItem.status === "completed", ).length, 1, ); assert.isTrue( events.some((event) => event.type === "turn.terminal" && event.status === "completed"), ); }).pipe(Effect.provide(idAllocatorLayer), Effect.scoped), ); it.effect("keeps a newly admitted prompt alive across stale idle and delayed busy evidence", () => Effect.gen(function* () { const idAllocator = yield* IdAllocatorV2; const nativeEvents = asyncEventStream(); const prompt = promiseGate(); const promptStarted = promiseGate(); const steerPrompt = promiseGate(); const steerPromptStarted = promiseGate(); const stopPromptStarted = promiseGate(); const statusCalled = promiseGate(); const steerStatusCalled = promiseGate(); const abortCalled = promiseGate(); let status: "idle" | "busy" = "busy"; let promptCalls = 0; let statusCalls = 0; const promptInputs: Array<{ readonly messageID?: string }> = []; const client = { event: { subscribe: async (_input: unknown, options: { signal?: AbortSignal }) => { options.signal?.addEventListener("abort", () => nativeEvents.close(), { once: true }); return { stream: nativeEvents.stream }; }, }, session: { create: async () => ({ data: { id: "native-opencode-race", time: { created: 1, updated: 1 } }, }), promptAsync: async ( input: { readonly messageID?: string }, options?: { readonly signal?: AbortSignal }, ) => { promptInputs.push(input); promptCalls += 1; if (promptCalls === 1) { promptStarted.resolve(); await prompt.promise; } else if (promptCalls === 2) { steerPromptStarted.resolve(); await steerPrompt.promise; } else { stopPromptStarted.resolve(); await new Promise((_resolve, reject) => options?.signal?.addEventListener("abort", () => reject(options.signal!.reason), { once: true, }), ); } return { data: true }; }, status: async () => ({ data: (() => { statusCalls += 1; if (statusCalls === 1) statusCalled.resolve(); else steerStatusCalled.resolve(); return { "native-opencode-race": { type: status } }; })(), }), messages: async () => ({ data: [] }), abort: async () => { abortCalled.resolve(); return { data: true }; }, }, mcp: { add: async () => ({ data: true }) }, }; const adapter = makeOpenCodeAdapterV2({ instanceId: ProviderInstanceId.make("opencode-test"), settings: OPEN_CODE_TEST_SETTINGS, environment: {}, runtime: { connectToOpenCodeServer: () => Effect.succeed({ url: "http://test.invalid", external: true }), createOpenCodeSdkClient: () => client, } as unknown as OpenCodeRuntimeShape, idAllocator, serverConfig: { cwd: "/workspace", attachmentsDir: "/tmp/attachments", } as ServerConfig["Service"], }); const threadId = ThreadId.make("thread-opencode-admission-race"); const providerSessionId = ProviderSessionId.make("session-opencode-admission-race"); const modelSelection = { instanceId: ProviderInstanceId.make("opencode-test"), model: "anthropic/claude-sonnet", options: [], }; const policy = runtimePolicy("full-access", { cwd: "/workspace" }); const runtime = yield* adapter.openSession({ threadId, providerSessionId, modelSelection, runtimePolicy: policy, }); const providerThread = yield* runtime.ensureThread({ threadId, modelSelection, runtimePolicy: policy, }); const now = yield* DateTime.now; const attemptId = RunAttemptId.make("attempt-opencode-admission-race"); const runId = RunId.make("run-opencode-admission-race"); const start = yield* runtime .startTurn({ appThread: { id: threadId, projectId: ProjectId.make("project-opencode-admission-race"), title: "race", providerInstanceId: modelSelection.instanceId, modelSelection, runtimeMode: "full-access", interactionMode: "default", branch: null, worktreePath: null, activeProviderThreadId: providerThread.id, lineage: { parentThreadId: null, relationshipToParent: null, rootThreadId: threadId }, forkedFrom: null, createdBy: "user", creationSource: "web", createdAt: now, updatedAt: now, archivedAt: null, settledOverride: null, settledAt: null, lastVisitedAt: null, deletedAt: null, }, threadId, runId, runOrdinal: 1, providerTurnOrdinal: 1, attemptId, rootNodeId: NodeId.make("node-opencode-admission-race"), providerThread, message: { createdBy: "user", creationSource: "web", messageId: MessageId.make("message-opencode-admission-race"), text: "hello", attachments: [], }, modelSelection, runtimePolicy: policy, }) .pipe(Effect.forkScoped); yield* Effect.promise(() => promptStarted.promise); yield* Effect.promise(() => nativeEvents.push({ type: "session.status", properties: { sessionID: "native-opencode-race", status: { type: "idle" } }, }), ); prompt.resolve(); yield* Fiber.join(start); assert.match(promptInputs[0]?.messageID ?? "", /^msg_[0-9a-f]{12}[0-9A-Za-z]{28}$/); yield* Effect.promise(() => nativeEvents.push({ type: "session.status", properties: { sessionID: "native-opencode-race", status: { type: "busy" } }, }), ); status = "busy"; yield* Effect.promise(() => nativeEvents.push({ type: "message.updated", properties: { sessionID: "native-opencode-race", info: { id: "stale-user-message", sessionID: "native-opencode-race", role: "user", time: { created: DateTime.toEpochMillis(now) }, }, }, }), ); assert.equal(statusCalls, 0, "an unrelated user message must not release prompt admission"); yield* Effect.promise(() => nativeEvents.push({ type: "session.status", properties: { sessionID: "native-opencode-race", status: { type: "idle" } }, }), ); yield* Effect.promise(() => nativeEvents.push({ type: "message.updated", properties: { sessionID: "native-opencode-race", info: { id: promptInputs[0]!.messageID!, sessionID: "native-opencode-race", role: "user", time: { created: DateTime.toEpochMillis(now) }, }, }, }), ); yield* Effect.promise(() => statusCalled.promise); const snapshot = yield* runtime.readThreadSnapshot({ providerThread }); assert.equal(snapshot.providerTurns.at(-1)?.status, "running"); const activeTurn = snapshot.providerTurns.at(-1)!; const steer = yield* runtime .steerTurn({ threadId, runId, providerThread, providerTurnId: activeTurn.id, message: { messageId: MessageId.make("message-opencode-steer-race"), text: "follow up", attachments: [], createdBy: "user", creationSource: "web", }, }) .pipe(Effect.forkScoped); yield* Effect.promise(() => steerPromptStarted.promise); assert.notEqual(promptInputs[1]?.messageID, promptInputs[0]?.messageID); steerPrompt.resolve(); yield* Fiber.join(steer); yield* Effect.promise(() => nativeEvents.push({ type: "session.status", properties: { sessionID: "native-opencode-race", status: { type: "busy" } }, }), ); yield* Effect.promise(() => nativeEvents.push({ type: "message.updated", properties: { sessionID: "native-opencode-race", info: { id: promptInputs[0]!.messageID!, sessionID: "native-opencode-race", role: "user", time: { created: DateTime.toEpochMillis(now) }, }, }, }), ); yield* Effect.promise(() => nativeEvents.push({ type: "session.status", properties: { sessionID: "native-opencode-race", status: { type: "idle" } }, }), ); yield* Effect.promise(() => nativeEvents.push({ type: "message.updated", properties: { sessionID: "native-opencode-race", info: { id: promptInputs[1]!.messageID!, sessionID: "native-opencode-race", role: "user", time: { created: DateTime.toEpochMillis(now) }, }, }, }), ); yield* Effect.promise(() => steerStatusCalled.promise); const pendingSteer = yield* runtime .steerTurn({ threadId, runId, providerThread, providerTurnId: activeTurn.id, message: { messageId: MessageId.make("message-opencode-stop-pending"), text: "pending when stopped", attachments: [], createdBy: "user", creationSource: "web", }, }) .pipe(Effect.exit, Effect.forkScoped); yield* Effect.promise(() => stopPromptStarted.promise); const interrupt = yield* runtime .interruptTurn({ providerThread, providerTurnId: activeTurn.id }) .pipe(Effect.forkScoped); yield* Effect.promise(() => abortCalled.promise); yield* Fiber.join(interrupt); assert.isTrue(Exit.isFailure(yield* Fiber.join(pendingSteer))); const promptWhileStopping = yield* Effect.exit( runtime.steerTurn({ threadId, runId, providerThread, providerTurnId: activeTurn.id, message: { messageId: MessageId.make("message-opencode-after-stop"), text: "must not pass the pending stop", attachments: [], createdBy: "user", creationSource: "web", }, }), ); assert.isTrue(Exit.isFailure(promptWhileStopping)); assert.equal(promptCalls, 3); yield* Effect.promise(() => nativeEvents.push({ type: "session.status", properties: { sessionID: "native-opencode-race", status: { type: "idle" } }, }), ); const interrupted = yield* runtime.readThreadSnapshot({ providerThread }); assert.equal(interrupted.providerTurns.at(-1)?.status, "interrupted"); }).pipe(Effect.provide(idAllocatorLayer), Effect.scoped), ); it.effect("interrupts an initial prompt while its SDK request is pending", () => Effect.gen(function* () { const idAllocator = yield* IdAllocatorV2; const nativeEvents = asyncEventStream(); const promptStarted = promiseGate(); const abortCalled = promiseGate(); let abortCalls = 0; const client = { event: { subscribe: async (_input: unknown, options: { signal?: AbortSignal }) => { options.signal?.addEventListener("abort", () => nativeEvents.close(), { once: true }); return { stream: nativeEvents.stream }; }, }, session: { create: async () => ({ data: { id: "native-opencode-initial-stop", time: { created: 1, updated: 1 } }, }), promptAsync: async (_input: unknown, options?: { readonly signal?: AbortSignal }) => { promptStarted.resolve(); await new Promise((_resolve, reject) => { const signal = options?.signal; if (signal?.aborted) { reject(signal.reason); return; } signal?.addEventListener("abort", () => reject(signal.reason), { once: true }); }); return { data: true }; }, status: async () => ({ data: { "native-opencode-initial-stop": { type: "idle" as const } }, }), messages: async () => ({ data: [] }), abort: async () => { abortCalls += 1; abortCalled.resolve(); return { data: true }; }, }, mcp: { add: async () => ({ data: true }) }, }; const adapter = makeOpenCodeAdapterV2({ instanceId: ProviderInstanceId.make("opencode-initial-stop-test"), settings: OPEN_CODE_TEST_SETTINGS, environment: {}, runtime: { connectToOpenCodeServer: () => Effect.succeed({ url: "http://test.invalid", external: true }), createOpenCodeSdkClient: () => client, } as unknown as OpenCodeRuntimeShape, idAllocator, serverConfig: { cwd: "/workspace", attachmentsDir: "/tmp/attachments", } as ServerConfig["Service"], }); const threadId = ThreadId.make("thread-opencode-initial-stop"); const providerSessionId = ProviderSessionId.make("session-opencode-initial-stop"); const modelSelection = { instanceId: ProviderInstanceId.make("opencode-initial-stop-test"), model: "anthropic/claude-sonnet", options: [], }; const policy = runtimePolicy("full-access", { cwd: "/workspace" }); const runtime = yield* adapter.openSession({ threadId, providerSessionId, modelSelection, runtimePolicy: policy, }); const providerThread = yield* runtime.ensureThread({ threadId, modelSelection, runtimePolicy: policy, }); const terminalEvents = yield* runtime.events.pipe( Stream.takeUntil((event) => event.type === "turn.terminal"), Stream.runCollect, Effect.forkScoped, ); const now = yield* DateTime.now; const start = yield* runtime .startTurn({ appThread: { id: threadId, projectId: ProjectId.make("project-opencode-initial-stop"), title: "initial stop", providerInstanceId: modelSelection.instanceId, modelSelection, runtimeMode: "full-access", interactionMode: "default", branch: null, worktreePath: null, activeProviderThreadId: providerThread.id, lineage: { parentThreadId: null, relationshipToParent: null, rootThreadId: threadId }, forkedFrom: null, createdBy: "user", creationSource: "web", createdAt: now, updatedAt: now, archivedAt: null, settledOverride: null, settledAt: null, lastVisitedAt: null, deletedAt: null, }, threadId, runId: RunId.make("run-opencode-initial-stop"), runOrdinal: 1, providerTurnOrdinal: 1, attemptId: RunAttemptId.make("attempt-opencode-initial-stop"), rootNodeId: NodeId.make("node-opencode-initial-stop"), providerThread, message: { createdBy: "user", creationSource: "web", messageId: MessageId.make("message-opencode-initial-stop"), text: "hello", attachments: [], }, modelSelection, runtimePolicy: policy, }) .pipe(Effect.forkScoped); yield* Effect.promise(() => promptStarted.promise); const running = yield* runtime.readThreadSnapshot({ providerThread }); const activeTurn = running.providerTurns.at(-1)!; const interrupt = yield* runtime .interruptTurn({ providerThread, providerTurnId: activeTurn.id }) .pipe(Effect.forkScoped); yield* Effect.promise(() => abortCalled.promise); yield* Fiber.join(start); yield* Fiber.join(interrupt); yield* Effect.promise(() => nativeEvents.push({ type: "session.status", properties: { sessionID: "native-opencode-initial-stop", status: { type: "idle" }, }, }), ); const events = Array.from(yield* Fiber.join(terminalEvents)); const terminals = events.filter((event) => event.type === "turn.terminal"); assert.equal(abortCalls, 1); assert.lengthOf(terminals, 1); assert.equal(terminals[0]?.status, "interrupted"); assert.isNull(terminals[0]?.failure ?? null); assert.isFalse( events.some( (event) => (event.type === "turn.terminal" && event.status === "failed") || (event.type === "provider_session.updated" && event.providerSession.status === "error"), ), ); }).pipe(Effect.provide(idAllocatorLayer), Effect.scoped), ); it("holds stale idle through prompt admission until the new user message is observed", () => { const admission = { admissionPending: true, admissionAccepted: false, admissionMessageObserved: false, idleDuringAdmission: false, }; assert.equal(advanceOpenCodePromptAdmission(admission, "idle"), "hold"); assert.equal(advanceOpenCodePromptAdmission(admission, "accepted"), "hold"); assert.isTrue(admission.admissionPending); assert.equal(advanceOpenCodePromptAdmission(admission, "user-message"), "reconcile-idle"); assert.isTrue(admission.admissionPending); }); it("releases admission only after prompt acceptance and new-turn evidence", () => { const admission = { admissionPending: true, admissionAccepted: false, admissionMessageObserved: false, idleDuringAdmission: false, }; assert.equal(advanceOpenCodePromptAdmission(admission, "busy"), "hold"); assert.equal(advanceOpenCodePromptAdmission(admission, "accepted"), "release"); assert.isFalse(admission.admissionPending); }); it("invalidates pending admission before aborting a turn", () => { const admission = { admissionGeneration: 4, admissionPending: true, admissionAccepted: false, admissionMessageObserved: false, idleDuringAdmission: false, }; cancelOpenCodePromptAdmission(admission, 5); assert.equal(admission.admissionGeneration, 5); assert.isFalse(admission.admissionPending); assert.equal(advanceOpenCodePromptAdmission(admission, "accepted"), "release"); }); it.effect("reconciles current idle and busy status replies after prompt admission", () => Effect.gen(function* () { for (const expected of ["idle", "busy"] as const) { const admission = { admissionGeneration: 4, admissionPending: true }; const status = yield* Deferred.make<"idle" | "busy" | "unknown">(); const fiber = yield* reconcileOpenCodePromptAdmissionStatus( admission, 4, Deferred.await(status), ).pipe(Effect.forkChild({ startImmediately: true })); yield* Deferred.succeed(status, expected); assert.equal(yield* Fiber.join(fiber), expected); assert.isFalse(admission.admissionPending); } }), ); it.effect("keeps admission pending after a transient status failure", () => Effect.gen(function* () { const admission = { admissionGeneration: 4, admissionPending: true }; assert.equal( yield* reconcileOpenCodePromptAdmissionStatus(admission, 4, Effect.succeed("unknown")), "unknown", ); assert.isTrue(admission.admissionPending); assert.equal( yield* reconcileOpenCodePromptAdmissionStatus(admission, 4, Effect.succeed("idle")), "idle", ); assert.isFalse(admission.admissionPending); }), ); it.effect("retries a transient status failure without another idle event", () => Effect.gen(function* () { const nativeSessionId = "native-opencode-status-retry"; const nativeEvents = asyncEventStream(); const promptRelease = promiseGate(); const promptCalls = yield* Queue.unbounded(); const statusCalls = yield* Queue.unbounded(); let statusCallCount = 0; const client = { event: { subscribe: async (_input: unknown, options: { signal?: AbortSignal }) => { options.signal?.addEventListener("abort", () => nativeEvents.close(), { once: true }); return { stream: nativeEvents.stream }; }, }, session: { create: async () => ({ data: { id: nativeSessionId, time: { created: 1, updated: 1 } }, }), promptAsync: async (input: { readonly messageID?: string }) => { Queue.offerUnsafe(promptCalls, input.messageID!); await promptRelease.promise; return { data: true }; }, status: async () => { statusCallCount += 1; Queue.offerUnsafe(statusCalls, statusCallCount); if (statusCallCount === 1) throw new Error("transient status failure"); return { data: { [nativeSessionId]: { type: "idle" as const } } }; }, messages: async () => ({ data: [] }), abort: async () => ({ data: true }), }, mcp: { add: async () => ({ data: true }) }, }; const harness = yield* makeOpenCodeRuntimeHarness("status-retry", nativeSessionId, client); const terminalEvents = yield* harness.runtime.events.pipe( Stream.takeUntil((event) => event.type === "turn.terminal"), Stream.runCollect, Effect.forkScoped, ); const start = yield* harness.startTurn().pipe(Effect.forkScoped); const admissionMessageId = yield* Queue.take(promptCalls); yield* Effect.promise(() => nativeEvents.push({ type: "session.status", properties: { sessionID: nativeSessionId, status: { type: "idle" } }, }), ); promptRelease.resolve(); yield* Fiber.join(start); const userMessage = { type: "message.updated", properties: { sessionID: nativeSessionId, info: { id: admissionMessageId, sessionID: nativeSessionId, role: "user", time: { created: DateTime.toEpochMillis(harness.now) }, }, }, }; yield* Effect.promise(() => nativeEvents.push(userMessage)); assert.equal(yield* Queue.take(statusCalls), 1); yield* Effect.promise(() => nativeEvents.push(userMessage)); assert.equal(statusCallCount, 1, "duplicate events must share the generation's retry worker"); yield* TestClock.adjust("250 millis"); assert.equal(yield* Queue.take(statusCalls), 2); const events = Array.from(yield* Fiber.join(terminalEvents)); const terminals = events.filter((event) => event.type === "turn.terminal"); assert.equal(statusCallCount, 2); assert.lengthOf(terminals, 1); assert.equal(terminals[0]?.status, "completed"); assert.isNull(terminals[0]?.failure ?? null); }).pipe(Effect.provide(idAllocatorLayer), Effect.scoped), ); it.effect("does not let a queued status retry adopt a newer steer generation", () => Effect.gen(function* () { const nativeSessionId = "native-opencode-stale-status-retry"; const nativeEvents = asyncEventStream(); const firstPromptRelease = promiseGate(); const promptCalls = yield* Queue.unbounded(); const statusCalls = yield* Queue.unbounded(); let promptCallCount = 0; let statusCallCount = 0; const client = { event: { subscribe: async (_input: unknown, options: { signal?: AbortSignal }) => { options.signal?.addEventListener("abort", () => nativeEvents.close(), { once: true }); return { stream: nativeEvents.stream }; }, }, session: { create: async () => ({ data: { id: nativeSessionId, time: { created: 1, updated: 1 } }, }), promptAsync: async (input: { readonly messageID?: string }) => { promptCallCount += 1; Queue.offerUnsafe(promptCalls, input.messageID!); if (promptCallCount === 1) { await firstPromptRelease.promise; } return { data: true }; }, status: async () => { statusCallCount += 1; Queue.offerUnsafe(statusCalls, statusCallCount); if (statusCallCount === 1) throw new Error("transient status failure"); return { data: { [nativeSessionId]: { type: "idle" as const } } }; }, messages: async () => ({ data: [] }), abort: async () => ({ data: true }), }, mcp: { add: async () => ({ data: true }) }, }; const harness = yield* makeOpenCodeRuntimeHarness( "stale-status-retry", nativeSessionId, client, ); const start = yield* harness.startTurn().pipe(Effect.forkScoped); const firstAdmissionMessageId = yield* Queue.take(promptCalls); yield* Effect.promise(() => nativeEvents.push({ type: "session.status", properties: { sessionID: nativeSessionId, status: { type: "idle" } }, }), ); firstPromptRelease.resolve(); yield* Fiber.join(start); yield* Effect.promise(() => nativeEvents.push({ type: "message.updated", properties: { sessionID: nativeSessionId, info: { id: firstAdmissionMessageId, sessionID: nativeSessionId, role: "user", time: { created: DateTime.toEpochMillis(harness.now) }, }, }, }), ); assert.equal(yield* Queue.take(statusCalls), 1); const running = yield* harness.runtime.readThreadSnapshot({ providerThread: harness.providerThread, }); const activeTurn = running.providerTurns.at(-1)!; yield* harness.runtime.steerTurn({ threadId: harness.threadId, runId: harness.runId, providerThread: harness.providerThread, providerTurnId: activeTurn.id, message: { messageId: MessageId.make("message-opencode-stale-status-retry-steer"), text: "new generation", attachments: [], createdBy: "user", creationSource: "web", }, }); yield* Queue.take(promptCalls); yield* TestClock.adjust("250 millis"); const afterRetry = yield* harness.runtime.readThreadSnapshot({ providerThread: harness.providerThread, }); assert.equal(statusCallCount, 1); assert.equal(afterRetry.providerTurns.at(-1)?.status, "running"); }).pipe(Effect.provide(idAllocatorLayer), Effect.scoped), ); it.effect("ignores a delayed status reply after steering starts a newer admission", () => Effect.gen(function* () { const admission = { admissionGeneration: 4, admissionPending: true }; const status = yield* Deferred.make<"idle" | "busy" | "unknown">(); const fiber = yield* reconcileOpenCodePromptAdmissionStatus( admission, 4, Deferred.await(status), ).pipe(Effect.forkChild({ startImmediately: true })); admission.admissionGeneration = 5; admission.admissionPending = true; yield* Deferred.succeed(status, "idle"); assert.equal(yield* Fiber.join(fiber), "stale"); assert.equal(admission.admissionGeneration, 5); assert.isTrue(admission.admissionPending); }), ); it.effect("does not revive admission when abort wins a pending status lookup", () => Effect.gen(function* () { const admission = { admissionGeneration: 4, admissionPending: true }; const status = yield* Deferred.make<"idle" | "busy" | "unknown">(); const fiber = yield* reconcileOpenCodePromptAdmissionStatus( admission, 4, Deferred.await(status), ).pipe(Effect.forkChild({ startImmediately: true })); cancelOpenCodePromptAdmission(admission, 5); yield* Deferred.succeed(status, "idle"); assert.equal(yield* Fiber.join(fiber), "stale"); assert.isFalse(admission.admissionPending); }), ); it.effect("logs bounded structural protocol diagnostics without native payload values", () => Effect.gen(function* () { const idAllocator = yield* IdAllocatorV2; const records: Array = []; const nativeEventLogger: EventNdjsonLogger = { filePath: "/tmp/provider-native.ndjson", write: (event) => Effect.sync(() => void records.push(event)), close: () => Effect.void, }; const logProtocolEvent = makeOpenCodeProtocolLogger({ nativeEventLogger, idAllocator, providerInstanceId: ProviderInstanceId.make("opencode-test"), providerSessionId: ProviderSessionId.make("provider-session-opencode-test"), threadId: ThreadId.make("thread-opencode-test"), }); const secret = "secret-opencode-prompt"; yield* logProtocolEvent({ direction: "outgoing", messageKind: "request", method: "session.prompt", payload: { prompt: secret, nested: { token: secret } }, }); const serialized = encodeUnknownJson(records); assert.notInclude(serialized, secret); assert.include(serialized, '"protocol":"opencode-sdk.sse"'); assert.include(serialized, '"method":"session.prompt"'); assert.include(serialized, '"fieldCount":2'); }).pipe(Effect.provide(idAllocatorLayer)), ); it("advertises the identity strengths exposed by the SDK boundary", () => { assert.equal(OpenCodeProviderCapabilitiesV2.identity.nativeThreadIds, "strong"); assert.equal(OpenCodeProviderCapabilitiesV2.identity.nativeTurnIds, "weak"); assert.equal(OpenCodeProviderCapabilitiesV2.identity.nativeItemIds, "strong"); assert.equal(OpenCodeProviderCapabilitiesV2.identity.nativeRequestIds, "strong"); assert.isTrue(OpenCodeProviderCapabilitiesV2.threads.canForkFromTurn); assert.isTrue(OpenCodeProviderCapabilitiesV2.turns.supportsActiveSteering); assert.equal(OpenCodeProviderCapabilitiesV2.turns.terminalStatusQuality, "strong"); assert.isFalse(OpenCodeProviderCapabilitiesV2.subagents.canCloseSubagents); }); it("maps native permission families to orchestration request kinds", () => { assert.equal(openCodePermissionRequestKind("bash"), "command"); assert.equal(openCodePermissionRequestKind("read"), "file-read"); assert.equal(openCodePermissionRequestKind("grep"), "file-read"); assert.equal(openCodePermissionRequestKind("external_directory"), "file-read"); assert.equal(openCodePermissionRequestKind("external_directory", "edit"), "file-change"); assert.equal(openCodePermissionRequestKind("edit"), "file-change"); assert.equal(openCodePermissionRequestKind("apply_patch"), "file-change"); assert.equal(openCodePermissionRequestKind("todowrite"), "command"); assert.equal(openCodePermissionRequestKind("custom", "todowrite"), "command"); }); it("maps OpenCode tools to semantic turn-item families", () => { assert.equal(openCodeToolProjectionKind("bash"), "command_execution"); assert.equal(openCodeToolProjectionKind("edit"), "file_change"); assert.equal(openCodeToolProjectionKind("read"), "file_search"); assert.equal(openCodeToolProjectionKind("lsp"), "file_search"); assert.equal(openCodeToolProjectionKind("websearch"), "web_search"); assert.equal(openCodeToolProjectionKind("codesearch"), "web_search"); assert.equal(openCodeToolProjectionKind("todowrite"), "dynamic_tool"); assert.equal(openCodeToolProjectionKind("custom_tool"), "dynamic_tool"); }); it("maps runtime modes to safe OpenCode permission rules", () => { const approvalRequired = openCodePermissionRules(runtimePolicy("approval-required")); assert.equal(permissionAction(approvalRequired, "read"), "allow"); assert.equal(permissionAction(approvalRequired, "edit"), "ask"); assert.equal(permissionAction(approvalRequired, "bash"), "ask"); assert.equal(permissionAction(approvalRequired, "doom_loop"), "ask"); assert.equal(permissionAction(approvalRequired, "unknown_plugin_tool"), "ask"); assert.equal(permissionAction(approvalRequired, "question"), "allow"); const autoAcceptEdits = openCodePermissionRules(runtimePolicy("auto-accept-edits")); assert.equal(permissionAction(autoAcceptEdits, "edit"), "allow"); assert.equal(permissionAction(autoAcceptEdits, "bash"), "ask"); const fullAccess = openCodePermissionRules(runtimePolicy("full-access")); assert.equal(permissionAction(fullAccess, "bash"), "allow"); assert.equal(permissionAction(fullAccess, "edit"), "allow"); const granularApproval = openCodePermissionRules( runtimePolicy("full-access", { approvalPolicy: { granular: { request_permissions: true } }, }), ); assert.equal(permissionAction(granularApproval, "bash"), "ask"); assert.equal(permissionAction(granularApproval, "read"), "allow"); const approvalRequiredWorkspaceWrite = openCodePermissionRules( runtimePolicy("approval-required", { sandboxPolicy: { type: "workspaceWrite", writableRoots: ["/tmp/opencode-workspace"], networkAccess: false, }, }), ); assert.equal(permissionAction(approvalRequiredWorkspaceWrite, "edit"), "ask"); }); it("enforces non-interactive sandbox policy through OpenCode permissions", () => { const readOnly = openCodePermissionRules( runtimePolicy("full-access", { approvalPolicy: "never", sandboxPolicy: { type: "readOnly", access: { type: "fullAccess" }, networkAccess: false, }, }), ); assert.equal(permissionAction(readOnly, "read"), "allow"); assert.equal(permissionAction(readOnly, "edit"), "deny"); assert.equal(permissionAction(readOnly, "bash"), "deny"); assert.equal(permissionAction(readOnly, "webfetch"), "deny"); assert.equal(permissionAction(readOnly, "doom_loop"), "deny"); assert.equal(permissionAction(readOnly, "unknown_plugin_tool"), "deny"); assert.equal(permissionAction(readOnly, "external_directory"), "allow"); const workspaceWrite = openCodePermissionRules( runtimePolicy("auto-accept-edits", { approvalPolicy: "never", sandboxPolicy: { type: "workspaceWrite", writableRoots: ["/tmp/opencode-workspace"], networkAccess: true, }, }), ); assert.equal(permissionAction(workspaceWrite, "edit"), "allow"); assert.equal(permissionAction(workspaceWrite, "bash"), "deny"); assert.equal(permissionAction(workspaceWrite, "webfetch"), "allow"); assert.deepInclude(workspaceWrite, { permission: "external_directory", pattern: "/tmp/opencode-workspace/*", action: "allow", }); }); it("preserves OpenCode's recursion guard on task-created child sessions", () => { const childRules = openCodeChildPermissionRules(runtimePolicy("full-access"), [ { permission: "task", pattern: "*", action: "deny" }, ]); assert.equal(permissionAction(childRules, "read"), "allow"); assert.equal(permissionAction(childRules, "bash"), "allow"); assert.equal(permissionAction(childRules, "task"), "deny"); const approvalRequiredPolicy = runtimePolicy("approval-required"); const parentRules = openCodePermissionRules(approvalRequiredPolicy); const childApprovalRules = openCodeChildPermissionRules(approvalRequiredPolicy, [ ...parentRules.filter((rule) => rule.action === "deny"), { permission: "task", pattern: "*", action: "deny" }, ]); assert.equal(permissionAction(childApprovalRules, "bash"), "ask"); assert.equal(permissionAction(childApprovalRules, "task"), "deny"); }); it("uses the next native user message as the exclusive fork and revert boundary", () => { const first = providerTurn({ id: "turn:first", ordinal: 1, nativeId: "msg-user-1" }); const synthetic = providerTurn({ id: "turn:synthetic", ordinal: 2, nativeId: null }); const third = providerTurn({ id: "turn:third", ordinal: 3, nativeId: "msg-user-3" }); assert.equal( openCodeBoundaryAfterProviderTurn([third, first, synthetic], first.id), "msg-user-3", ); assert.isUndefined(openCodeBoundaryAfterProviderTurn([first, synthetic, third], third.id)); }); });