import { historyResponseItems } from "@t3tools/provider-core/server/handoffBudget"; import { makeProviderTextDeltaCoalescer, type ProviderTextDeltaUpdate, } from "./ProviderTextDeltaCoalescer.ts"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { CommandId, CheckpointId, CodexSettings, EnvironmentId, MessageId, type ModelSelection, NodeId, type OrchestrationV2AppThread, type OrchestrationV2ProviderThread, type OrchestrationV2ProviderTurn, type OrchestrationV2RuntimeRequest, type OrchestrationV2TurnItem, ProjectId, type ProviderApprovalDecision, ProviderInstanceId, ProviderSessionId, ProviderThreadId, ProviderTurnId, RunAttemptId, RunId, ThreadId, TurnItemId, } from "@t3tools/contracts"; import { assert, describe, it } from "@effect/vitest"; import * as CodexClient from "effect-codex-app-server/client"; import * as CodexReplay from "effect-codex-app-server/replay"; import * as CodexError from "effect-codex-app-server/errors"; import type * as CodexSchema from "effect-codex-app-server/schema"; import * as DateTime from "effect/DateTime"; import * as Deferred from "effect/Deferred"; import * as Crypto from "effect/Crypto"; import * as Effect from "effect/Effect"; import * as Predicate from "effect/Predicate"; import * as FileSystem from "effect/FileSystem"; import { MCP_APP_OUTPUT_KEY, readMcpAppReference } from "@t3tools/shared/mcpApp"; import { resolveAttachmentPathById } from "../../attachmentStore.ts"; import * as Fiber from "effect/Fiber"; import * as Layer from "effect/Layer"; import * as PubSub from "effect/PubSub"; import * as Queue from "effect/Queue"; import * as Ref from "effect/Ref"; import * as Schema from "effect/Schema"; import * as Stream from "effect/Stream"; import * as Sink from "effect/Sink"; import * as Stdio from "effect/Stdio"; import * as Exit from "effect/Exit"; import * as Cause from "effect/Cause"; import * as PlatformError from "effect/PlatformError"; import { TestClock } from "effect/testing"; import { ChildProcess, ChildProcessSpawner } from "effect/process"; import packageJson from "../../../package.json" with { type: "json" }; import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions"; import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers"; import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; import * as EffectWorker from "../EffectWorker.ts"; import * as Orchestrator from "../Orchestrator.ts"; import * as ProviderReplayHarness from "../testkit/ProviderReplayHarness.ts"; import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter"; import type * as ProviderContinuationRequests from "@t3tools/provider-core/server/ProviderContinuationRequests"; import * as CodexAdapterV2 from "./CodexAdapterV2.ts"; import { withCodexReplayChildMetadata } from "./CodexAdapterV2.testkit.ts"; import * as CodexAdapterV2Testkit from "./CodexAdapterV2.testkit.ts"; const replayTranscriptJson = Schema.fromJsonString(CodexReplay.CodexAppServerReplayTranscript); const encodeReplayTranscriptJson = Schema.encodeEffect(replayTranscriptJson); const decodeReplayTranscriptJson = Schema.decodeUnknownEffect(replayTranscriptJson); const encodeStringJson = Schema.encodeEffect(Schema.fromJsonString(Schema.String)); const isProviderAdapterEventStreamError = Schema.is( ProviderAdapter.ProviderAdapterEventStreamError, ); describe("CodexAdapterV2 context usage", () => { it("uses the current context rather than cumulative processed tokens", () => { const usage = CodexAdapterV2.codexProviderTurnTokenUsage( { total: { totalTokens: 180_000, inputTokens: 160_000, cachedInputTokens: 20_000, outputTokens: 20_000, reasoningOutputTokens: 5_000, }, last: { totalTokens: 50_000, inputTokens: 45_000, cachedInputTokens: 10_000, outputTokens: 5_000, reasoningOutputTokens: 1_000, }, modelContextWindow: 200_000, }, "2026-08-29T00:00:00.000Z", ); assert.deepEqual(usage, { usedTokens: 50_000, maxTokens: 200_000, inputTokens: 45_000, cachedInputTokens: 10_000, outputTokens: 5_000, reasoningOutputTokens: 1_000, updatedAt: "2026-08-29T00:00:00.000Z", }); }); }); describe("CodexAdapterV2 assistant message streaming", () => { it.effect("flushes buffered text synchronously before item and turn completion", () => Effect.gen(function* () { const updates = yield* Ref.make>([]); const coalescer = yield* makeProviderTextDeltaCoalescer({ flushIntervalMs: 50, emit: (update) => Ref.update(updates, (current) => [...current, update]), }); yield* coalescer.append({ turnId: "turn-1", itemId: "message-1", delta: "item final" }); const completedText = yield* coalescer.complete({ turnId: "turn-1", itemId: "message-1", }); yield* coalescer.append({ turnId: "turn-1", itemId: "message-2", delta: "turn final" }); yield* coalescer.flushTurn("turn-1"); assert.equal(completedText, "item final"); assert.deepEqual(yield* Ref.get(updates), [ { turnId: "turn-1", itemId: "message-1", text: "item final", completed: true }, { turnId: "turn-1", itemId: "message-2", text: "turn final", completed: true }, ]); yield* Effect.yieldNow; yield* TestClock.adjust("50 millis"); yield* Effect.yieldNow; assert.equal((yield* Ref.get(updates)).length, 2); }), ); }); describe("CodexAdapterV2 runtime policy", () => { it.effect.each([undefined, false, true])( "selects native collaboration modes without prompt overrides with hasT3Mcp=%s", (hasT3Mcp) => Effect.gen(function* () { for (const interactionMode of ["plan", "default"] as const) { for (const reasoningEffort of [undefined, "high"] as const) { const params = yield* CodexAdapterV2.buildCodexTurnStartParams({ nativeThreadId: "native-mode-switch", codexInput: [{ type: "text", text: "test" }], runtimePolicy: { runtimeMode: "full-access", interactionMode, cwd: null }, modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4", ...(reasoningEffort === undefined ? {} : { options: [{ id: "reasoningEffort", value: reasoningEffort }] }), }, ...(hasT3Mcp === undefined ? {} : { hasT3Mcp }), }); assert.deepEqual(params.collaborationMode, { mode: interactionMode, settings: { model: "gpt-5.4", ...(reasoningEffort === undefined ? {} : { reasoning_effort: reasoningEffort }), }, }); assert.equal(params.effort, reasoningEffort); assert.equal(params.additionalContext !== undefined, hasT3Mcp === true); } } }), ); it.effect("derives concrete Codex turn policies from every T3 runtime mode", () => Effect.gen(function* () { const build = ( runtimeMode: "approval-required" | "auto-accept-edits" | "auto" | "full-access", ) => CodexAdapterV2.buildCodexTurnStartParams({ nativeThreadId: `native-${runtimeMode}`, codexInput: [{ type: "text", text: "test" }], runtimePolicy: { runtimeMode, interactionMode: "default", cwd: null, }, modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4", }, }); const approvalRequired = yield* build("approval-required"); const autoAcceptEdits = yield* build("auto-accept-edits"); const auto = yield* build("auto"); const fullAccess = yield* build("full-access"); assert.equal(approvalRequired.approvalPolicy, "untrusted"); assert.equal(approvalRequired.approvalsReviewer, "user"); assert.equal(approvalRequired.sandboxPolicy?.type, "readOnly"); assert.equal(autoAcceptEdits.approvalPolicy, "on-request"); assert.equal(autoAcceptEdits.approvalsReviewer, "user"); assert.equal(autoAcceptEdits.sandboxPolicy?.type, "workspaceWrite"); assert.equal(auto.approvalPolicy, "on-request"); assert.equal(auto.approvalsReviewer, "auto_review"); assert.equal(auto.sandboxPolicy?.type, "workspaceWrite"); assert.equal(fullAccess.approvalPolicy, "never"); assert.equal(fullAccess.approvalsReviewer, "user"); assert.equal(fullAccess.sandboxPolicy?.type, "dangerFullAccess"); }), ); it.effect("sends MCP app model context as untrusted Codex context", () => Effect.gen(function* () { const policy = { runtimeMode: "full-access", interactionMode: "default", cwd: null } as const; const modelSelection = { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" }; const appContext = [{ key: "mcp_app_todos_list_todos_item-1", text: "Filtered to overdue" }]; const alone = yield* CodexAdapterV2.buildCodexTurnStartParams({ nativeThreadId: "native-app-context", codexInput: [{ type: "text", text: "what's on my list?" }], runtimePolicy: policy, modelSelection, appContext, }); assert.deepEqual(alone.additionalContext, { "mcp_app_todos_list_todos_item-1": { kind: "untrusted", value: "Filtered to overdue" }, }); const withT3 = yield* CodexAdapterV2.buildCodexTurnStartParams({ nativeThreadId: "native-app-context", codexInput: [{ type: "text", text: "what's on my list?" }], runtimePolicy: policy, modelSelection, hasT3Mcp: true, appContext, }); assert.deepEqual(withT3.additionalContext?.["mcp_app_todos_list_todos_item-1"], { kind: "untrusted", value: "Filtered to overdue", }); assert.isDefined(withT3.additionalContext?.["t3_code_runtime"]); }), ); }); describe("CodexAdapterV2 native protocol logging", () => { it.effect("logs decoded app-server frames once with credentials redacted", () => Effect.gen(function* () { const writes: Array<{ readonly event: unknown; readonly threadId: ThreadId | null; }> = []; const logger: ProviderEventLoggers.EventNdjsonLogger = { filePath: "/tmp/events.log", write: (event, threadId) => Effect.sync(() => { writes.push({ event, threadId }); }), close: () => Effect.void, }; const threadId = ThreadId.make("thread-1"); const providerSessionId = ProviderSessionId.make("provider-session-1"); const protocolLogger = CodexAdapterV2.makeCodexAppServerProtocolLogger({ nativeEventLogger: logger, threadId, providerSessionId, }); assert.notEqual(protocolLogger, undefined); if (protocolLogger === undefined) { return; } yield* protocolLogger({ direction: "incoming", stage: "decoded", payload: { method: "thread/event", params: { id: "evt-1", http_headers: { Authorization: "Bearer secret-codex-token" }, usage: { inputTokens: 12, outputTokens: 8, totalTokens: 20 }, }, }, }); yield* protocolLogger({ direction: "incoming", stage: "raw", payload: '{"method":"thread/event","params":{"http_headers":{"Authorization":"Bearer secret-codex-token"}}}\n', }); assert.equal(writes.length, 1); assert.equal(writes[0]?.threadId, threadId); assert.deepEqual(writes[0]?.event, { provider: "codex", protocol: "codex.app-server", kind: "protocol", providerSessionId, event: { direction: "incoming", stage: "decoded", payload: { method: "thread/event", params: { id: "evt-1", http_headers: { Authorization: "[REDACTED]" }, usage: { inputTokens: 12, outputTokens: 8, totalTokens: 20 }, }, }, }, }); }), ); }); describe("CodexAdapterV2 rollback mapping", () => { it.effect("derives native rollback count from durable provider turns", () => Effect.gen(function* () { const now = yield* DateTime.now; const providerThreadId = ProviderThreadId.make("provider-thread-codex-rollback"); const providerThread: OrchestrationV2ProviderThread = { id: providerThreadId, driver: CodexAdapterV2.CODEX_DRIVER_KIND, providerInstanceId: ProviderInstanceId.make("codex"), providerSessionId: ProviderSessionId.make("provider-session-codex-rollback"), appThreadId: ThreadId.make("thread-codex-rollback"), ownerNodeId: null, nativeThreadRef: { driver: CodexAdapterV2.CODEX_DRIVER_KIND, nativeId: "native-thread-codex-rollback", strength: "strong", }, nativeConversationHeadRef: null, status: "idle", firstRunOrdinal: 1, lastRunOrdinal: 3, handoffIds: [], forkedFrom: null, createdAt: now, updatedAt: now, }; const providerTurn = ( id: string, ordinal: number, status: OrchestrationV2ProviderTurn["status"], ): OrchestrationV2ProviderTurn => ({ id: ProviderTurnId.make(id), providerThreadId, nodeId: NodeId.make(`node-${id}`), runAttemptId: RunAttemptId.make(`run-attempt-${id}`), nativeTurnRef: { driver: CodexAdapterV2.CODEX_DRIVER_KIND, nativeId: `native-${id}`, strength: "strong", }, ordinal, status, startedAt: now, completedAt: status === "running" || status === "pending" ? null : now, }); const firstTurn = providerTurn("provider-turn-first", 1, "completed"); const secondTurn = providerTurn("provider-turn-second", 2, "completed"); const runningTurn = providerTurn("provider-turn-running", 3, "running"); const interruptedTurn = providerTurn("provider-turn-interrupted", 4, "interrupted"); const goalCommandTurn = { ...providerTurn("provider-turn-goal-command", 5, "completed"), nativeTurnRef: null, }; const numTurns = yield* CodexAdapterV2.resolveCodexRollbackTurnCount({ providerThread, target: { type: "provider_turn", checkpointId: CheckpointId.make("checkpoint-first"), appRunOrdinal: 1, providerTurn: firstTurn, }, providerThreadTurns: [goalCommandTurn, interruptedTurn, runningTurn, secondTurn, firstTurn], }); assert.equal(numTurns, 2); }), ); }); describe("CodexAdapterV2 fork boundary", () => { const providerThreadId = ProviderThreadId.make("provider-thread-codex-fork-boundary"); const makeProviderThread = (now: DateTime.Utc): OrchestrationV2ProviderThread => ({ id: providerThreadId, driver: CodexAdapterV2.CODEX_DRIVER_KIND, providerInstanceId: ProviderInstanceId.make("codex"), providerSessionId: ProviderSessionId.make("provider-session-codex-fork-boundary"), appThreadId: ThreadId.make("thread-codex-fork-boundary"), ownerNodeId: null, nativeThreadRef: { driver: CodexAdapterV2.CODEX_DRIVER_KIND, nativeId: "native-thread-codex-fork-boundary", strength: "strong", }, nativeConversationHeadRef: null, status: "idle", firstRunOrdinal: 1, lastRunOrdinal: 2, handoffIds: [], forkedFrom: null, createdAt: now, updatedAt: now, }); const makeProviderTurn = ( id: string, ordinal: number, nativeId: string | null, now: DateTime.Utc, ): OrchestrationV2ProviderTurn => ({ id: ProviderTurnId.make(id), providerThreadId, nodeId: NodeId.make(`node-${id}`), runAttemptId: RunAttemptId.make(`run-attempt-${id}`), nativeTurnRef: nativeId === null ? { driver: CodexAdapterV2.CODEX_DRIVER_KIND, nativeId: null, strength: "none" } : { driver: CodexAdapterV2.CODEX_DRIVER_KIND, nativeId, strength: "strong" }, ordinal, status: "completed", startedAt: now, completedAt: now, }); it.effect("resolves the selected provider turn to an inclusive native fork boundary", () => Effect.gen(function* () { const now = yield* DateTime.now; const firstTurn = makeProviderTurn("provider-turn-first", 1, "native-turn-first", now); const secondTurn = makeProviderTurn("provider-turn-second", 2, "native-turn-second", now); const boundary = yield* CodexAdapterV2.resolveCodexForkBoundary({ sourceProviderThread: makeProviderThread(now), sourceProviderTurns: [firstTurn, secondTurn], providerTurnId: firstTurn.id, targetThreadId: ThreadId.make("thread-codex-fork-target"), }); assert.deepEqual(boundary, { lastTurnId: "native-turn-first", rollbackTurnCount: 0, }); }), ); it.effect("keeps the rollback-count fallback when the boundary turn lacks a native id", () => Effect.gen(function* () { const now = yield* DateTime.now; const firstTurn = makeProviderTurn("provider-turn-first", 1, null, now); const secondTurn = makeProviderTurn("provider-turn-second", 2, "native-turn-second", now); const boundary = yield* CodexAdapterV2.resolveCodexForkBoundary({ sourceProviderThread: makeProviderThread(now), sourceProviderTurns: [firstTurn, secondTurn], providerTurnId: firstTurn.id, targetThreadId: ThreadId.make("thread-codex-fork-target"), }); assert.deepEqual(boundary, { lastTurnId: undefined, rollbackTurnCount: 1 }); }), ); }); const DEFAULT_CODEX_SETTINGS = Schema.decodeSync(CodexSettings)({}); const CODEX_TEST_MODEL_SELECTION = { instanceId: CodexAdapterV2.CODEX_DEFAULT_INSTANCE_ID, model: "gpt-5.4", } satisfies ModelSelection; const CODEX_TEST_HIGH_EFFORT_MODEL_SELECTION = { instanceId: CodexAdapterV2.CODEX_DEFAULT_INSTANCE_ID, model: "gpt-6.1-sol", options: [{ id: "reasoningEffort", value: "high" }], } satisfies ModelSelection; const CODEX_TEST_RUNTIME_POLICY = ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({ runtimeMode: "full-access", interactionMode: "default", cwd: "/workspace", }); function makeCodexTestAppThread(input: { readonly threadId: ThreadId; readonly providerThread: OrchestrationV2ProviderThread; readonly now: DateTime.Utc; }): OrchestrationV2AppThread { return { createdBy: "user", creationSource: "web", id: input.threadId, projectId: ProjectId.make(`project-${input.threadId}`), title: "Codex continuation test", providerInstanceId: CodexAdapterV2.CODEX_DEFAULT_INSTANCE_ID, modelSelection: CODEX_TEST_MODEL_SELECTION, runtimeMode: "full-access", interactionMode: "default", branch: null, worktreePath: null, activeProviderThreadId: input.providerThread.id, lineage: { parentThreadId: null, relationshipToParent: null, rootThreadId: input.threadId, }, forkedFrom: null, createdAt: input.now, updatedAt: input.now, archivedAt: null, settledOverride: null, settledAt: null, lastVisitedAt: null, deletedAt: null, }; } function makeCodexTestTurnInput(input: { readonly threadId: ThreadId; readonly providerThread: OrchestrationV2ProviderThread; readonly now: DateTime.Utc; readonly attemptId: RunAttemptId; readonly text: string; }): ProviderAdapter.ProviderAdapterV2TurnInput { return { appThread: makeCodexTestAppThread(input), threadId: input.threadId, runId: RunId.make(`run-${input.attemptId}`), runOrdinal: 1, providerTurnOrdinal: 1, attemptId: input.attemptId, rootNodeId: NodeId.make(`node-${input.attemptId}`), providerThread: input.providerThread, message: { createdBy: "user", creationSource: "web", messageId: MessageId.make(`message-${input.attemptId}`), text: input.text, attachments: [], }, modelSelection: CODEX_TEST_MODEL_SELECTION, runtimePolicy: CODEX_TEST_RUNTIME_POLICY, }; } function makeCodexReplayTurn(input: { readonly id: string; readonly status: "inProgress" | "completed" | "interrupted" | "failed"; }): Record { const terminal = input.status === "completed" || input.status === "interrupted" || input.status === "failed"; return { id: input.id, items: [], itemsView: "notLoaded", status: input.status, error: null, startedAt: 1782622440, completedAt: terminal ? 1782622450 : null, durationMs: null, }; } function codexReplayPreamble(input: { readonly nativeThreadId: string; readonly nativeTurnId: string; readonly prompt: string; readonly sentPrompt?: string; }): Array { return [ { type: "expect_outbound", label: "initialize", frame: { id: 1, method: "initialize", params: { clientInfo: { name: "T3 Code", title: "T3 Code", version: packageJson.version }, capabilities: { experimentalApi: true, optOutNotificationMethods: ["turn/diff/updated"], extensions: { "io.modelcontextprotocol/ui": { mimeTypes: ["text/html;profile=mcp-app"] }, }, }, }, }, }, { type: "emit_inbound", label: "initialize", frame: { id: 1, result: { userAgent: "T3 Code/0.156.1", codexHome: "/tmp/codex-home", platformFamily: "unix", platformOs: "macos", }, }, }, { type: "expect_outbound", label: "initialized", frame: { method: "initialized" } }, { type: "expect_outbound", label: "thread/start", frame: { id: 2, method: "thread/start", params: { config: CodexAdapterV2.CODEX_THREAD_CONFIG }, }, }, { type: "emit_inbound", label: "thread/start", frame: { id: 2, result: { thread: { id: input.nativeThreadId, sessionId: input.nativeThreadId, projectId: null, forkedFromId: null, preview: "", ephemeral: false, modelProvider: "openai", createdAt: 1782622440, updatedAt: 1782622440, status: { type: "idle" }, path: `/tmp/${input.nativeThreadId}.jsonl`, cwd: "/workspace", cliVersion: "0.144.0", source: "vscode", threadSource: null, agentNickname: null, agentRole: null, gitInfo: null, name: null, turns: [], }, model: "gpt-5.4", modelProvider: "openai", serviceTier: null, cwd: "/workspace", instructionSources: [], approvalPolicy: "on-request", approvalsReviewer: "user", sandbox: { type: "workspaceWrite", writableRoots: [], networkAccess: false }, reasoningEffort: "medium", }, }, }, { type: "expect_outbound", label: "turn/start", frame: { id: 3, method: "turn/start", params: { threadId: input.nativeThreadId, input: [{ type: "text", text: input.sentPrompt ?? input.prompt }], cwd: "/workspace", model: "gpt-5.4", approvalPolicy: "never", approvalsReviewer: "user", sandboxPolicy: { type: "dangerFullAccess" }, summary: "detailed", collaborationMode: { mode: "default", settings: { model: "gpt-5.4" } }, }, }, }, { type: "emit_inbound", label: "turn/start", frame: { id: 3, result: { turn: makeCodexReplayTurn({ id: input.nativeTurnId, status: "inProgress" }) }, }, }, { type: "emit_inbound", label: "turn/started", frame: { method: "turn/started", params: { threadId: input.nativeThreadId, turn: makeCodexReplayTurn({ id: input.nativeTurnId, status: "inProgress" }), }, }, }, ]; } function makeCodexReplayTranscript(input: { readonly scenario: string; readonly entries: ReadonlyArray; }): CodexReplay.CodexAppServerReplayTranscript { return { provider: "codex", protocol: "codex.app-server", version: "0.144.0", scenario: input.scenario, entries: input.entries, }; } function withReplayRequestId( entry: CodexReplay.CodexAppServerReplayEntry, id: number, ): CodexReplay.CodexAppServerReplayEntry { return entry.type !== "runtime_exit" && Predicate.isObject(entry.frame) ? { ...entry, frame: { ...entry.frame, id } } : entry; } describe("CodexAdapterV2 process exit", () => { it.effect.each([ { exit: "numeric", active: false }, { exit: "SIGKILL", active: false }, { exit: "numeric", active: true }, { exit: "SIGKILL", active: true }, ] as const)("retires the shared session after $exit (active turn: $active)", ({ exit, active }) => Effect.gen(function* () { const scenario = `process-exit-${exit}-${active}`; const nativeThreadId = `native-${scenario}`; const nativeTurnId = `turn-${scenario}`; const input = yield* Queue.unbounded(); const emit = (frame: unknown) => Queue.offer(input, new TextEncoder().encode(JSON.stringify(frame) + "\n")); const outputEnded = yield* Deferred.make(); const turnStarted = yield* Deferred.make(); const turnCompleted = yield* Deferred.make(); const events: Array = []; const preamble = codexReplayPreamble({ nativeThreadId, nativeTurnId, prompt: "work" }); const terminationError = exit === "numeric" ? new CodexError.CodexAppServerProcessExitedError({ code: 17, pid: 52 }) : new CodexError.CodexAppServerTransportError({ operation: "read-process-exit-status", pid: 52, cause: PlatformError.systemError({ _tag: "Unknown", module: "ChildProcess", method: "exitCode", cause: new Error("Process terminated by SIGKILL"), }), }); const client = yield* CodexClient.make( Stdio.make({ args: Effect.succeed([]), stdin: Stream.fromQueue(input), stdout: () => Sink.forEach((chunk: string | Uint8Array) => Effect.gen(function* () { const frame = JSON.parse( typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk), ) as { readonly id?: number; readonly method: string }; const response = preamble.find( (entry) => entry.type === "emit_inbound" && entry.label === frame.method && Predicate.isObject(entry.frame) && "id" in entry.frame, ); if (response?.type === "emit_inbound") { yield* emit(response.frame); } }), ).pipe(Sink.ensuring(Deferred.succeed(outputEnded, undefined))), stderr: () => Sink.drain, }), {}, Effect.succeed(terminationError), ); const adapter = yield* CodexAdapterV2.makeCodexAdapterV2({ instanceId: CodexAdapterV2.CODEX_DEFAULT_INSTANCE_ID, settings: DEFAULT_CODEX_SETTINGS, environment: {}, clientFactory: { open: () => Effect.succeed(client) }, crypto: yield* Crypto.Crypto, fileSystem: yield* FileSystem.FileSystem, idAllocator: yield* IdAllocator.IdAllocatorV2, serverConfig: yield* ProviderReplayHarness.makeReplayServerConfig(scenario).pipe( Effect.orDie, ), }); const threadId = ThreadId.make(`thread-${scenario}`); const runtime = yield* adapter.openSession({ threadId, providerSessionId: ProviderSessionId.make(`session-${scenario}`), modelSelection: CODEX_TEST_MODEL_SELECTION, runtimePolicy: CODEX_TEST_RUNTIME_POLICY, }); const eventReader = yield* runtime.events.pipe( Stream.runForEach((event) => Effect.sync(() => events.push(event)).pipe( Effect.andThen( event.type === "provider_turn.updated" && event.providerTurn.status === "running" ? Deferred.succeed(turnStarted, undefined) : event.type === "turn.terminal" ? Deferred.succeed(turnCompleted, undefined) : Effect.void, ), ), ), Effect.forkScoped, ); const providerThread = yield* runtime.ensureThread({ threadId, modelSelection: CODEX_TEST_MODEL_SELECTION, runtimePolicy: CODEX_TEST_RUNTIME_POLICY, }); yield* runtime.startTurn( makeCodexTestTurnInput({ threadId, providerThread, now: yield* DateTime.now, attemptId: RunAttemptId.make(`attempt-${scenario}`), text: "work", }), ); yield* emit({ method: "turn/started", params: { threadId: nativeThreadId, turn: makeCodexReplayTurn({ id: nativeTurnId, status: "inProgress" }), }, }); yield* Deferred.await(turnStarted); if (!active) { yield* emit({ method: "turn/completed", params: { threadId: nativeThreadId, turn: makeCodexReplayTurn({ id: nativeTurnId, status: "completed" }), }, }); yield* Deferred.await(turnCompleted); assert.isTrue( events.some((event) => event.type === "turn.terminal" && event.status === "completed"), ); } yield* Queue.end(input); // Output closes only after the protocol records the process termination. yield* Deferred.await(outputEnded); const rpcError = yield* client.request("thread/list", {}).pipe(Effect.flip); assert.strictEqual(rpcError, terminationError); // Without the fix the stream never ends, so this waits until the project timeout. const readerExit = yield* Fiber.await(eventReader); // A failure, not a clean end, carries the Codex error into the session's lastError. assert.isTrue( Exit.isFailure(readerExit) && isProviderAdapterEventStreamError(Cause.squash(readerExit.cause)), "the session manager must receive process death so it can retire the shared process", ); }).pipe( Effect.scoped, Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ); }); describe("CodexAdapterV2 session initialize", () => { const openReplaySession = ( transcript: CodexReplay.CodexAppServerReplayTranscript, beforeEmitInbound?: CodexReplay.CodexAppServerReplayDriver["beforeEmitInbound"], ) => Effect.gen(function* () { const driver = yield* CodexReplay.makeReplayDriver( transcript, beforeEmitInbound === undefined ? {} : { beforeEmitInbound }, ); let initializeRequests = 0; const adapter = yield* CodexAdapterV2.makeCodexAdapterV2({ instanceId: CodexAdapterV2.CODEX_DEFAULT_INSTANCE_ID, settings: DEFAULT_CODEX_SETTINGS, environment: {}, clientFactory: { open: (openInput) => Layer.build(CodexReplay.layerReplayWithDriver(driver)).pipe( Effect.flatMap((context) => Effect.service(CodexClient.CodexAppServerClient).pipe(Effect.provide(context)), ), Effect.map( (client) => ({ ...client, request: (method, params) => Effect.sync(() => { if (method === "initialize") initializeRequests++; }).pipe(Effect.andThen(client.request(method, params))), }) satisfies CodexClient.CodexAppServerClient["Service"], ), Effect.mapError( (cause) => new ProviderAdapter.ProviderAdapterOpenSessionError({ driver: CodexAdapterV2.CODEX_DRIVER_KIND, providerSessionId: openInput.providerSessionId, cause, }), ), ), }, crypto: yield* Crypto.Crypto, fileSystem: yield* FileSystem.FileSystem, idAllocator: yield* IdAllocator.IdAllocatorV2, serverConfig: yield* ProviderReplayHarness.makeReplayServerConfig(transcript.scenario).pipe( Effect.orDie, ), }); const runtime = yield* adapter.openSession({ threadId: ThreadId.make(`thread-${transcript.scenario}`), providerSessionId: ProviderSessionId.make(`provider-session-${transcript.scenario}`), modelSelection: CODEX_TEST_MODEL_SELECTION, runtimePolicy: CODEX_TEST_RUNTIME_POLICY, }); return { ensureThread: (threadId: string) => runtime.ensureThread({ threadId: ThreadId.make(threadId), modelSelection: CODEX_TEST_MODEL_SELECTION, runtimePolicy: CODEX_TEST_RUNTIME_POLICY, }), initializeRequests: () => initializeRequests, }; }); const replayPreamble = (nativeThreadId: string) => codexReplayPreamble({ nativeThreadId, nativeTurnId: "unused", prompt: "unused" }); it.effect("sends one initialize when two threads start on a fresh session at once", () => Effect.gen(function* () { const initializeAwaitingResponse = yield* Deferred.make(); const releaseInitialize = yield* Deferred.make(); // Codex rejects a second initialize; the replay permits one handshake. const session = yield* openReplaySession( makeCodexReplayTranscript({ scenario: "concurrent-initialize", entries: [ ...replayPreamble("concurrent-first").slice(0, 5), ...replayPreamble("concurrent-second") .slice(3, 5) .map((entry) => withReplayRequestId(entry, 3)), ], }), (entry) => entry.label === "initialize" ? Deferred.succeed(initializeAwaitingResponse, undefined).pipe( Effect.andThen(Deferred.await(releaseInitialize)), ) : Effect.void, ); const first = yield* session .ensureThread("thread-concurrent-first") .pipe(Effect.forkChild({ startImmediately: true })); yield* Deferred.await(initializeAwaitingResponse); const second = yield* session .ensureThread("thread-concurrent-second") .pipe(Effect.forkChild({ startImmediately: true })); yield* Deferred.succeed(releaseInitialize, undefined); const providerThreads = [yield* Fiber.join(first), yield* Fiber.join(second)]; assert.equal(session.initializeRequests(), 1); assert.sameMembers( providerThreads.map((providerThread) => providerThread.nativeThreadRef?.nativeId), ["concurrent-first", "concurrent-second"], ); }).pipe( Effect.scoped, Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ); // regression: #16260 one-shot initializers raced; a failed handshake must be retried it.effect("retries initialize after a failed handshake", () => Effect.gen(function* () { const preamble = replayPreamble("initialize-retry"); const entries: Array = [ ...preamble.slice(0, 1), { type: "emit_inbound", label: "initialize", frame: { id: 1, error: { code: -32603, message: "Codex is not ready." } }, }, ...preamble.slice(0, 2).map((entry) => withReplayRequestId(entry, 2)), ...preamble.slice(2, 3), ...preamble.slice(3, 5).map((entry) => withReplayRequestId(entry, 3)), ]; const session = yield* openReplaySession( makeCodexReplayTranscript({ scenario: "initialize-retry", entries }), ); const failure = yield* session.ensureThread("thread-initialize-retry").pipe(Effect.flip); assert.equal(failure._tag, "ProviderAdapterEnsureThreadError"); const providerThread = yield* session.ensureThread("thread-initialize-retry"); assert.equal(providerThread.nativeThreadRef?.nativeId, "initialize-retry"); assert.equal(session.initializeRequests(), 2); }).pipe( Effect.scoped, Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ); it.effect("completes the handshake after a caller is interrupted mid-initialize", () => Effect.gen(function* () { const initializeAwaitingResponse = yield* Deferred.make(); const releaseInitialize = yield* Deferred.make(); const preamble = replayPreamble("initialize-interrupted"); const session = yield* openReplaySession( makeCodexReplayTranscript({ scenario: "initialize-interrupted", entries: [ ...preamble.slice(0, 2), ...preamble.slice(0, 1).map((entry) => withReplayRequestId(entry, 2)), { type: "emit_inbound", label: "initialize-rejected", frame: { id: 2, error: { code: -32600, message: "Already initialized" } }, }, ...preamble.slice(2, 3), ...preamble.slice(3, 5).map((entry) => withReplayRequestId(entry, 3)), ], }), (entry) => entry.label === "initialize" ? Deferred.succeed(initializeAwaitingResponse, undefined).pipe( Effect.andThen(Deferred.await(releaseInitialize)), ) : Effect.void, ); const interrupted = yield* session .ensureThread("thread-initialize-interrupted") .pipe(Effect.forkChild({ startImmediately: true })); yield* Deferred.await(initializeAwaitingResponse); yield* Fiber.interrupt(interrupted); yield* Deferred.succeed(releaseInitialize, undefined); const providerThread = yield* session.ensureThread("thread-initialize-interrupted"); assert.equal(providerThread.nativeThreadRef?.nativeId, "initialize-interrupted"); assert.equal(session.initializeRequests(), 2); }).pipe( Effect.scoped, Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ); }); describe("CodexAdapterV2 post-settle continuation", () => { // The harness notifies after every recorded event or continuation, so a wait // re-checks exactly when the state it reads changes. Subscribing before the // first check means a change that lands in between is not lost. const makeChangeSignal = Effect.gen(function* () { const changes = yield* PubSub.unbounded(); const until = (predicate: () => boolean): Effect.Effect => Effect.scoped( Effect.gen(function* () { const subscription = yield* PubSub.subscribe(changes); while (!predicate()) yield* PubSub.take(subscription); }), ); return { notify: PubSub.publish(changes, undefined), until }; }); const makeCodexReplayHarness = ( transcript: CodexReplay.CodexAppServerReplayTranscript, onEvent: (event: ProviderAdapter.ProviderAdapterV2Event) => Effect.Effect = () => Effect.void, onRequest: (method: string, params: unknown) => Effect.Effect = () => Effect.void, readChildMetadata?: Parameters[2], ) => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const idAllocator = yield* IdAllocator.IdAllocatorV2; const serverConfig = yield* ProviderReplayHarness.makeReplayServerConfig( transcript.scenario, ).pipe(Effect.orDie); const changes = yield* makeChangeSignal; const continuationRequests: Array = []; const clientFactory: CodexAdapterV2.CodexAppServerClientFactoryShape = { open: (openInput) => Layer.build(CodexReplay.layerReplay(transcript)).pipe( Effect.mapError( (cause) => new ProviderAdapter.ProviderAdapterOpenSessionError({ driver: CodexAdapterV2.CODEX_DRIVER_KIND, providerSessionId: openInput.providerSessionId, cause, }), ), Effect.flatMap((context) => Effect.service(CodexClient.CodexAppServerClient).pipe( Effect.map((client) => withCodexReplayChildMetadata(client, transcript, readChildMetadata), ), Effect.map( (client) => ({ ...client, request: (method, params) => onRequest(method, params).pipe( Effect.andThen(client.request(method, params)), ), }) satisfies CodexClient.CodexAppServerClient["Service"], ), Effect.provide(context), ), ), ), }; const adapter = yield* CodexAdapterV2.makeCodexAdapterV2({ instanceId: CodexAdapterV2.CODEX_DEFAULT_INSTANCE_ID, settings: DEFAULT_CODEX_SETTINGS, environment: {}, clientFactory, crypto: yield* Crypto.Crypto, fileSystem, idAllocator, serverConfig, continuationRequests: { offer: (request) => Effect.sync(() => { continuationRequests.push(request); }).pipe(Effect.andThen(changes.notify)), }, }); const threadId = ThreadId.make(`thread-${transcript.scenario}`); const runtime = yield* adapter.openSession({ threadId, providerSessionId: ProviderSessionId.make(`provider-session-${transcript.scenario}`), modelSelection: CODEX_TEST_MODEL_SELECTION, runtimePolicy: CODEX_TEST_RUNTIME_POLICY, }); const providerThread = yield* runtime.ensureThread({ threadId, modelSelection: CODEX_TEST_MODEL_SELECTION, runtimePolicy: CODEX_TEST_RUNTIME_POLICY, }); const events: Array = []; const firstTerminal = yield* Deferred.make(); yield* runtime.events.pipe( Stream.runForEach((event) => Effect.sync(() => { events.push(event); }).pipe( Effect.andThen( event.type === "turn.terminal" ? Deferred.succeed(firstTerminal, undefined) : Effect.void, ), Effect.andThen(changes.notify), Effect.andThen(onEvent(event)), ), ), Effect.forkScoped, ); if (runtime.hasPendingBackgroundWork === undefined) { return yield* Effect.die("Codex adapter runtime must expose hasPendingBackgroundWork."); } const hasPendingBackgroundWork = runtime.hasPendingBackgroundWork; const terminalEvents = () => events.filter( ( event, ): event is Extract => event.type === "turn.terminal", ); const subagentUpdates = () => events.filter( ( event, ): event is Extract< ProviderAdapter.ProviderAdapterV2Event, { type: "subagent.updated" } > => event.type === "subagent.updated", ); return { runtime, providerThread, threadId, serverConfig, events, continuationRequests, terminalEvents, subagentUpdates, hasPendingBackgroundWork, firstTerminal: Deferred.await(firstTerminal), until: changes.until, awaitTerminal: (count: number) => changes.until(() => terminalEvents().length >= count), }; }); it.effect.each(["supported", "unsupported", "invalid"] as const)( "delivers native history with %s app-server protocol", (response) => Effect.gen(function* () { const nativeThreadId = `inject-${response}`; const prompt = "Only the current request"; const history: ProviderAdapter.ProviderAdapterV2HistoricalContext = { context: "Historical conversation", messages: (["user", "assistant"] as const).map((role) => ({ role, text: role === "user" ? "Original request\n" + "界".repeat(300) : "Partial interrupted work", threadId: ThreadId.make("source"), runId: RunId.make("source-run"), itemId: TurnItemId.make(`source-${role}`), providerThreadId: null, kind: `${role}_message`, status: "interrupted", })), }; const items = historyResponseItems(history.messages, history.context); const preamble = codexReplayPreamble({ nativeThreadId, nativeTurnId: "current-turn", prompt, }); const transcript = makeCodexReplayTranscript({ scenario: `inject-${response}`, entries: [ ...preamble.slice(0, -3), { type: "expect_outbound", label: "inject", frame: { id: 3, method: "thread/inject_items", params: { threadId: nativeThreadId, items }, }, }, { type: "emit_inbound", label: "inject-result", frame: response === "supported" ? { id: 3, result: {} } : { id: 3, error: { code: response === "unsupported" ? -32601 : -32602, message: "Injection rejected", }, }, }, ...(response === "invalid" ? [] : preamble .slice(-3) .map((entry) => "frame" in entry && Predicate.isObject(entry.frame) && "id" in entry.frame ? { ...entry, frame: { ...entry.frame, id: 4 } } : entry, )), ], }); const requests: string[] = []; const harness = yield* makeCodexReplayHarness( transcript, () => Effect.void, (method) => Effect.sync(() => { requests.push(method); }), ); const injection = yield* harness.runtime.injectHistory!({ providerThread: harness.providerThread, ...history, }).pipe(Effect.result); if (response === "invalid") { assert.equal(injection._tag, "Failure"); if (injection._tag === "Failure") { assert.equal(injection.failure._tag, "ProviderAdapterProtocolError"); assert.propertyVal(injection.failure.cause, "code", -32602); assert.notProperty(injection.failure, "payload"); } assert.notInclude(requests, "turn/start"); return; } assert.equal(injection._tag, "Success"); if (injection._tag === "Success") assert.equal(injection.success, response === "supported"); yield* harness.runtime.startTurn( makeCodexTestTurnInput({ threadId: harness.threadId, providerThread: harness.providerThread, now: yield* DateTime.now, attemptId: RunAttemptId.make("inject-attempt"), text: prompt, }), ); assert.equal(requests.filter((method) => method === "turn/start").length, 1); assert.isBelow(requests.indexOf("thread/inject_items"), requests.indexOf("turn/start")); }).pipe( Effect.scoped, Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ); it.effect("adds the session MCP server without replacing launch-argument servers", () => Effect.gen(function* () { const scenario = "launch-mcp-servers"; yield* (yield* McpProviderSessions.McpProviderSessions).set({ environmentId: EnvironmentId.make("environment-codex-mcp"), threadId: ThreadId.make(`thread-${scenario}`), providerSessionId: `provider-session-${scenario}`, providerInstanceId: ProviderInstanceId.make("codex"), endpoint: "http://127.0.0.1:43123/mcp", authorizationHeader: "Bearer secret-codex-token", browserToolsAvailable: true, }); const requests: Array = []; yield* makeCodexReplayHarness( makeCodexReplayTranscript({ scenario, entries: codexReplayPreamble({ nativeThreadId: "launch-mcp-thread", nativeTurnId: "unused", prompt: "unused", }).slice(0, 5), }), () => Effect.void, (method, params) => Effect.sync(() => { if (method === "thread/start") requests.push(params); }), ); assert.deepEqual(requests, [ { cwd: "/workspace", model: "gpt-5.4", config: { "tools.update_plan.enabled": true, mcp_optional_startup_grace_ms: 10_000, "mcp_servers.t3-code": { url: "http://127.0.0.1:43123/mcp", tool_timeout_sec: 3_900, http_headers: { Authorization: "Bearer secret-codex-token" }, }, }, }, ]); }).pipe( Effect.scoped, Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ); // regression: #15839 launch-arg MCP servers survive the thread's t3-code server it.effect("replays historical and dotted MCP config while enforcing other thread config", () => Effect.gen(function* () { const recordedServer = { url: "http://127.0.0.1:41000/mcp", http_headers: { Authorization: "Bearer recorded-token" }, }; const currentConfig = { "tools.update_plan.enabled": true, "mcp_servers.t3-code": { url: "http://127.0.0.1:42000/mcp", http_headers: { Authorization: "Bearer current-token" }, }, "mcp_servers.extra.command": "cat", }; for (const recordedMcp of [ { mcp_servers: { "t3-code": recordedServer } }, { "mcp_servers.t3-code": recordedServer, "mcp_servers.extra.command": "echo" }, ]) { const entries = codexReplayPreamble({ nativeThreadId: "replayed-mcp-thread", nativeTurnId: "unused", prompt: "unused", }) .slice(3, 5) .map((entry) => withReplayRequestId(entry, 1)); entries[0] = { type: "expect_outbound", frame: { id: 1, method: "thread/start", params: { config: { "tools.update_plan.enabled": true, ...recordedMcp } }, }, }; entries.push({ ...entries[0], frame: { id: 2, method: "thread/start", params: { config: { "tools.update_plan.enabled": true, ...recordedMcp } }, }, }); const driver = yield* CodexReplay.makeReplayDriver( makeCodexReplayTranscript({ scenario: "mcp-config-normalization", entries, }), ); yield* Effect.gen(function* () { const client = yield* CodexClient.CodexAppServerClient; const response = yield* client.request("thread/start", { config: currentConfig }); assert.equal(response.thread.id, "replayed-mcp-thread"); assert.isNull((yield* Ref.get(driver.state)).failure); yield* client .request("thread/start", { config: { ...currentConfig, "tools.update_plan.enabled": false }, }) .pipe(Effect.flip); assert.instanceOf( (yield* Ref.get(driver.state)).failure, CodexReplay.CodexAppServerReplayFrameMismatchError, ); }).pipe(Effect.provide(CodexReplay.layerReplayWithDriver(driver))); } }).pipe(Effect.scoped), ); it.effect("waits for native start before interrupting an acknowledged queued turn", () => Effect.gen(function* () { const nativeThreadId = "early-stop-thread"; const nativeTurnId = "early-stop-turn"; const prompt = "Run a command."; const preamble = codexReplayPreamble({ nativeThreadId, nativeTurnId, prompt }); const transcript = makeCodexReplayTranscript({ scenario: "early-stop-await-native-start", entries: [ ...preamble.slice(0, -2), { type: "emit_inbound", label: "turn/start/queued", frame: { id: 3, result: { turn: { ...makeCodexReplayTurn({ id: nativeTurnId, status: "inProgress" }), startedAt: null, }, }, }, }, { type: "emit_inbound", label: "turn/started", afterMs: 1000, frame: { method: "turn/started", params: { threadId: nativeThreadId, turn: makeCodexReplayTurn({ id: nativeTurnId, status: "inProgress" }), }, }, }, { type: "expect_outbound", label: "turn/interrupt", frame: { id: 4, method: "turn/interrupt", params: { threadId: nativeThreadId, turnId: nativeTurnId }, }, }, { type: "emit_inbound", label: "turn/interrupt", frame: { id: 4, result: {} } }, { type: "emit_inbound", label: "turn/completed", frame: { method: "turn/completed", params: { threadId: nativeThreadId, turn: makeCodexReplayTurn({ id: nativeTurnId, status: "interrupted" }), }, }, }, ], }); let interruptSent = false; const harness = yield* makeCodexReplayHarness( transcript, () => Effect.void, (method) => Effect.sync(() => { if (method === "turn/interrupt") interruptSent = true; }), ); yield* harness.runtime.startTurn( makeCodexTestTurnInput({ threadId: harness.threadId, providerThread: harness.providerThread, now: yield* DateTime.now, attemptId: RunAttemptId.make("early-stop-attempt"), text: prompt, }), ); const providerTurnId = (yield* IdAllocator.IdAllocatorV2).derive.providerTurn({ driver: CodexAdapterV2.CODEX_DRIVER_KIND, nativeTurnId, }); const interrupt = yield* harness.runtime .interruptTurn({ providerThread: harness.providerThread, providerTurnId }) .pipe(Effect.forkScoped); yield* TestClock.adjust("500 millis"); assert.isFalse(interruptSent, "Stop must not reach Codex before native turn/started"); yield* TestClock.adjust("500 millis"); yield* Fiber.join(interrupt); yield* harness.firstTerminal; assert.equal(harness.terminalEvents()[0]?.status, "interrupted"); assert.lengthOf(harness.terminalEvents(), 1); assert.isFalse(yield* harness.hasPendingBackgroundWork); }).pipe( Effect.scoped, Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ); const assistantMessages = (events: ReadonlyArray) => events.filter( ( event, ): event is Extract => event.type === "message.updated" && event.message.role === "assistant", ); it.effect("keeps an asynchronous Codex question actionable after the turn completes", () => Effect.scoped( Effect.gen(function* () { const nativeThreadId = "async-question-thread"; const nativeTurnId = "async-question-turn"; const usage = { totalTokens: 15, inputTokens: 10, cachedInputTokens: 2, outputTokens: 5, reasoningOutputTokens: 1, }; const transcript = makeCodexReplayTranscript({ scenario: "async-question-and-billed-usage", entries: [ ...codexReplayPreamble({ nativeThreadId, nativeTurnId, prompt: "Continue while I decide.", }), { type: "emit_inbound", label: "question", frame: { method: "item/completed", params: { threadId: nativeThreadId, turnId: nativeTurnId, item: { type: "agentMessage", id: "async-question-item", text: "Which branch?", delivery: "async", questions: [{ title: "Which branch?", options: ["main", "dev"] }], }, }, }, }, { type: "emit_inbound", label: "usage", frame: { method: "thread/tokenUsage/updated", params: { threadId: nativeThreadId, turnId: nativeTurnId, tokenUsage: { total: usage, last: usage, modelContextWindow: 200_000 }, }, }, }, { type: "emit_inbound", label: "complete", frame: { method: "turn/completed", params: { threadId: nativeThreadId, turn: makeCodexReplayTurn({ id: nativeTurnId, status: "completed" }), }, }, }, ], }); const harness = yield* makeCodexReplayHarness(transcript); yield* harness.runtime.startTurn( makeCodexTestTurnInput({ threadId: harness.threadId, providerThread: harness.providerThread, now: yield* DateTime.now, attemptId: RunAttemptId.make("async-question-attempt"), text: "Continue while I decide.", }), ); yield* harness.firstTerminal; const requests = harness.events.flatMap((event) => event.type === "runtime_request.updated" ? [event.runtimeRequest] : [], ); assert.lengthOf(requests, 1); assert.equal(requests[0]?.status, "pending"); assert.deepEqual(requests[0]?.responseCapability, { type: "message" }); const questionItem = harness.events.find( (event) => event.type === "turn_item.updated" && event.turnItem.type === "user_input_request", ); assert.equal(questionItem?.type, "turn_item.updated"); if ( questionItem?.type === "turn_item.updated" && questionItem.turnItem.type === "user_input_request" ) { assert.deepEqual( questionItem.turnItem.questions[0]?.options.map((option) => option.label), ["main", "dev"], ); assert.equal(questionItem.turnItem.responseMode, "message"); } const questionNode = harness.events.find( (event) => event.type === "node.updated" && event.node.id === requests[0]?.nodeId, ); assert.equal( questionNode?.type === "node.updated" && questionNode.node.countsForRun, false, ); const contextReport = harness.events.find( (event) => event.type === "provider_turn.updated" && event.providerTurn.tokenUsage !== undefined, ); assert.equal(contextReport?.type, "provider_turn.updated"); if (contextReport?.type === "provider_turn.updated") { assert.equal( contextReport.providerTurn.runAttemptId, RunAttemptId.make("async-question-attempt"), ); assert.equal(contextReport.providerTurn.providerThreadId, harness.providerThread.id); assert.equal(contextReport.providerTurn.tokenUsage?.usedTokens, 15); assert.equal(contextReport.providerTurn.tokenUsage?.maxTokens, 200_000); } const completed = harness.events.find( (event) => event.type === "provider_turn.updated" && event.providerTurn.status === "completed", ); assert.equal(completed?.type, "provider_turn.updated"); if (completed?.type === "provider_turn.updated") { assert.deepEqual(completed.providerTurn.turnTokenUsage, { usageStatus: "complete", usageScope: "main_agent", hasSubagents: false, inputTokens: 10, cachedInputTokens: 2, outputTokens: 5, reasoningTokens: 1, }); } assert.isEmpty(assistantMessages(harness.events)); }).pipe( Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ), ); it.effect("stops asking for commands the user allowed for the session", () => Effect.scoped( Effect.gen(function* () { const nativeThreadId = "session-approval-thread"; const turnIds = [ "session-approval-turn-1", "session-approval-turn-2", "session-approval-turn-3", ]; const turnPolicies = { "approval-required": { approvalPolicy: "untrusted", approvalsReviewer: "user", sandboxPolicy: { type: "readOnly" }, }, "full-access": { approvalPolicy: "never", approvalsReviewer: "user", sandboxPolicy: { type: "dangerFullAccess" }, }, } as const; const turnModes = ["approval-required", "full-access", "approval-required"] as const; const turnText = (index: number) => `Review round ${index + 1}.`; const startTurn = (index: number): ReadonlyArray => [ { type: "expect_outbound", label: `turn ${index + 1} turn/start`, frame: { id: 3 + index, method: "turn/start", params: { threadId: nativeThreadId, input: [{ type: "text", text: turnText(index) }], cwd: "/workspace", model: "gpt-5.4", ...turnPolicies[turnModes[index]!], summary: "detailed", collaborationMode: { mode: "default", settings: { model: "gpt-5.4" } }, }, }, }, { type: "emit_inbound", label: `turn ${index + 1} turn/start`, frame: { id: 3 + index, result: { turn: makeCodexReplayTurn({ id: turnIds[index]!, status: "inProgress" }) }, }, }, turnEvent("turn/started", turnIds[index]!), ]; const approval = (input: { readonly id: number; readonly turnId: string; readonly kind: "commandExecution" | "fileChange"; readonly itemId: string; readonly decision: string; }): ReadonlyArray => [ { type: "emit_inbound", label: `${input.itemId} approval`, frame: { id: input.id, method: `item/${input.kind}/requestApproval`, params: { threadId: nativeThreadId, turnId: input.turnId, itemId: input.itemId, startedAtMs: 1782622441000, ...(input.kind === "commandExecution" ? { command: `cat ${input.itemId}` } : {}), }, }, }, { type: "expect_outbound", label: `${input.itemId} decision`, frame: { id: input.id, result: { decision: input.decision } }, }, ]; const turnEvent = ( method: "turn/started" | "turn/completed", turnId: string, ): CodexReplay.CodexAppServerReplayEntry => ({ type: "emit_inbound", label: `${turnId} ${method}`, frame: { method, params: { threadId: nativeThreadId, turn: makeCodexReplayTurn({ id: turnId, status: method === "turn/started" ? "inProgress" : "completed", }), }, }, }); const [patchRequest, patchDecision] = approval({ id: 102, turnId: turnIds[0]!, kind: "fileChange", itemId: "first-patch", decision: "acceptForSession", }); const transcript = makeCodexReplayTranscript({ scenario: "codex-session-approvals", entries: [ ...codexReplayPreamble({ nativeThreadId, nativeTurnId: turnIds[0]!, prompt: turnText(0), }).slice(0, -3), ...startTurn(0), ...approval({ id: 100, turnId: turnIds[0]!, kind: "commandExecution", itemId: "first-command", decision: "acceptForSession", }), patchRequest!, ...approval({ id: 101, turnId: turnIds[0]!, kind: "commandExecution", itemId: "second-command", decision: "acceptForSession", }), turnEvent("turn/completed", turnIds[0]!), ...startTurn(1), patchDecision!, turnEvent("turn/completed", turnIds[1]!), ...startTurn(2), ...approval({ id: 103, turnId: turnIds[2]!, kind: "commandExecution", itemId: "third-command", decision: "accept", }), ...approval({ id: 104, turnId: turnIds[2]!, kind: "fileChange", itemId: "second-patch", decision: "accept", }), turnEvent("turn/completed", turnIds[2]!), ], }); const harness = yield* makeCodexReplayHarness(transcript); const requests = () => [ ...new Map( harness.events.flatMap((event) => event.type === "runtime_request.updated" ? [[event.runtimeRequest.id, event.runtimeRequest] as const] : [], ), ).values(), ]; const answer = ( count: number, kind: "command" | "file-change", decision: ProviderApprovalDecision, ) => Effect.gen(function* () { yield* harness.until(() => requests().length === count); const request = requests()[count - 1]!; assert.equal(request.kind, kind); yield* harness.runtime.respondToRuntimeRequest({ requestId: request.id, decision }); }); const runTurn = (index: number) => Effect.gen(function* () { yield* harness.runtime.startTurn({ ...makeCodexTestTurnInput({ threadId: harness.threadId, providerThread: harness.providerThread, now: yield* DateTime.now, attemptId: RunAttemptId.make(`session-approvals-${index + 1}`), text: turnText(index), }), runOrdinal: index + 1, providerTurnOrdinal: index + 1, runtimePolicy: { ...CODEX_TEST_RUNTIME_POLICY, runtimeMode: turnModes[index]! }, }); }); yield* runTurn(0); yield* answer(1, "command", "acceptForSession"); yield* harness.until(() => requests().length === 2); yield* harness.awaitTerminal(1); yield* runTurn(1); yield* answer(2, "file-change", "acceptForSession"); yield* harness.awaitTerminal(2); yield* runTurn(2); yield* answer(3, "command", "accept"); yield* answer(4, "file-change", "accept"); yield* harness.awaitTerminal(3); assert.lengthOf(requests(), 4); }).pipe( Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ), ); it.effect("preserves T3 context on the wire and restores it after compaction", () => Effect.scoped( Effect.gen(function* () { const nativeThreadId = "context-thread"; const nativeTurnId = "context-turn"; const params = yield* CodexAdapterV2.buildCodexTurnStartParams({ nativeThreadId, codexInput: [{ type: "text", text: "work" }], runtimePolicy: CODEX_TEST_RUNTIME_POLICY, modelSelection: CODEX_TEST_MODEL_SELECTION, hasT3Mcp: true, }); assert.include( params.additionalContext?.t3_code_orchestration_01?.value ?? "", "delegate_task", ); const entries = codexReplayPreamble({ nativeThreadId, nativeTurnId, prompt: "work" }); const transcript = makeCodexReplayTranscript({ scenario: "restore-context", entries: [ ...entries.slice(0, 5), { type: "expect_outbound", label: "context turn", frame: { id: 3, method: "turn/start", params }, }, ...entries.slice(6), { type: "emit_inbound", label: "compacted", frame: { method: "item/completed", params: { threadId: nativeThreadId, turnId: nativeTurnId, item: { type: "contextCompaction", id: "compact-context" }, }, }, }, { type: "expect_outbound", label: "restore context", frame: { id: 4, method: "thread/inject_items", params: { threadId: nativeThreadId, items: Object.entries(params.additionalContext ?? {}).map(([key, entry]) => ({ type: "message", role: "developer", content: [{ type: "input_text", text: `<${key}>${entry.value}` }], })), }, }, }, { type: "emit_inbound", label: "restored", frame: { id: 4, result: {} } }, { type: "emit_inbound", label: "done", frame: { method: "turn/completed", params: { threadId: nativeThreadId, turn: makeCodexReplayTurn({ id: nativeTurnId, status: "completed" }), }, }, }, ], }); const harness = yield* makeCodexReplayHarness(transcript); yield* (yield* McpProviderSessions.McpProviderSessions).set({ environmentId: EnvironmentId.make("test"), threadId: harness.threadId, providerSessionId: "context-session", providerInstanceId: ProviderInstanceId.make("codex"), endpoint: "http://127.0.0.1:43123/mcp", authorizationHeader: "Bearer test", browserToolsAvailable: true, }); yield* harness.runtime.startTurn( makeCodexTestTurnInput({ threadId: harness.threadId, providerThread: harness.providerThread, now: yield* DateTime.now, attemptId: RunAttemptId.make("context-attempt"), text: "work", }), ); yield* harness.firstTerminal; assert.equal(harness.terminalEvents()[0]?.status, "completed"); }).pipe( Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ), ); it.effect("compacts Codex with the native RPC and completes the compaction turn", () => Effect.scoped( Effect.gen(function* () { const nativeThreadId = "compact-thread"; const nativeTurnId = "compact-turn"; const item = { type: "contextCompaction", id: "compact-item" }; const transcript = makeCodexReplayTranscript({ scenario: "native-compaction", entries: [ ...codexReplayPreamble({ nativeThreadId, nativeTurnId, prompt: "unused" }).slice(0, 5), { type: "expect_outbound", label: "compact", frame: { id: 3, method: "thread/compact/start", params: { threadId: nativeThreadId }, }, }, { type: "emit_inbound", label: "compact", frame: { id: 3, result: {} } }, { type: "emit_inbound", label: "start", frame: { method: "turn/started", params: { threadId: nativeThreadId, turn: makeCodexReplayTurn({ id: nativeTurnId, status: "inProgress" }), }, }, }, ...(["item/started", "item/completed"] as const).map((method) => ({ type: "emit_inbound" as const, label: method, frame: { method, params: { threadId: nativeThreadId, turnId: nativeTurnId, item } }, })), { type: "emit_inbound", label: "complete", frame: { method: "turn/completed", params: { threadId: nativeThreadId, turn: makeCodexReplayTurn({ id: nativeTurnId, status: "completed" }), }, }, }, ], }); const harness = yield* makeCodexReplayHarness(transcript); assert.isDefined(harness.runtime.compactThread); yield* harness.runtime.compactThread!( makeCodexTestTurnInput({ threadId: harness.threadId, providerThread: harness.providerThread, now: yield* DateTime.now, attemptId: RunAttemptId.make("compact-attempt"), text: "/compact", }), ); yield* harness.firstTerminal; const items = harness.events.flatMap((event) => event.type === "turn_item.updated" && event.turnItem.type === "compaction" ? [event.turnItem] : [], ); assert.deepEqual( items.map((entry) => entry.status), ["running", "completed"], ); assert.equal(items[0]?.id, items[1]?.id); assert.equal(harness.terminalEvents()[0]?.status, "completed"); }).pipe( Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ), ); it.effect.each(["failed", "interrupted", "Stop"] as const)( "settles unfinished compaction on %s without changing completed compaction", (outcome) => Effect.scoped( Effect.gen(function* () { const nativeThreadId = "compact-thread"; const nativeTurnId = "compact-turn"; const error = { message: "Remote compaction stream disconnected", codexErrorInfo: "other", additionalDetails: null, }; const transcript = makeCodexReplayTranscript({ scenario: `automatic-compaction-${outcome}`, entries: [ ...codexReplayPreamble({ nativeThreadId, nativeTurnId, prompt: "Continue." }), ...( [ { method: "item/started", id: "completed-item", startedAtMs: 1782622441000 }, { method: "item/completed", id: "completed-item", completedAtMs: 1782622442000 }, { method: "item/started", id: "unfinished-item", startedAtMs: 1782622443000 }, ] as const ).map(({ method, id, ...timestamps }) => ({ type: "emit_inbound" as const, label: `${method}/${id}`, frame: { method, params: { threadId: nativeThreadId, turnId: nativeTurnId, item: { type: "contextCompaction", id }, ...timestamps, }, }, })), ...(outcome === "Stop" ? [ { type: "expect_outbound" as const, label: "turn/interrupt", frame: { id: 4, method: "turn/interrupt", params: { threadId: nativeThreadId, turnId: nativeTurnId }, }, }, { type: "emit_inbound" as const, label: "turn/interrupt/failed", frame: { id: 4, error: { code: -32_000, message: "Interrupt failed" } }, }, ] : [ { type: "emit_inbound" as const, label: "turn/completed", frame: { method: "turn/completed", params: { threadId: nativeThreadId, turn: { ...makeCodexReplayTurn({ id: nativeTurnId, status: outcome }), error: outcome === "failed" ? error : null, }, }, }, }, ]), ], }); const compactionStarted = yield* Deferred.make(); const harness = yield* makeCodexReplayHarness(transcript, (event) => event.type === "turn_item.updated" && event.turnItem.nativeItemRef?.nativeId === "unfinished-item" && event.turnItem.status === "running" && event.turnItem.providerTurnId !== null ? Deferred.succeed(compactionStarted, event.turnItem.providerTurnId) : Effect.void, ); yield* TestClock.setTime(1782622445000); yield* harness.runtime.startTurn( makeCodexTestTurnInput({ threadId: harness.threadId, providerThread: harness.providerThread, now: yield* DateTime.now, attemptId: RunAttemptId.make(`compact-${outcome}-attempt`), text: "Continue.", }), ); if (outcome === "Stop") { const providerTurnId = yield* Deferred.await(compactionStarted); const interrupt = yield* harness.runtime .interruptTurn({ providerThread: harness.providerThread, providerTurnId }) .pipe(Effect.exit); assert.equal(interrupt._tag, "Failure"); } yield* harness.firstTerminal; const items = harness.events.flatMap((event) => event.type === "turn_item.updated" && event.turnItem.type === "compaction" ? [event.turnItem] : [], ); const completed = items.filter( (item) => item.nativeItemRef?.nativeId === "completed-item", ); const unfinished = items.filter( (item) => item.nativeItemRef?.nativeId === "unfinished-item", ); const status = outcome === "failed" ? "failed" : "interrupted"; assert.deepEqual( unfinished.map((item) => item.status), ["running", status], ); assert.equal(unfinished[0]?.id, unfinished[1]?.id); assert.deepEqual(unfinished[1]?.startedAt, DateTime.makeUnsafe(1782622443000)); assert.deepEqual( unfinished[1]?.completedAt, DateTime.makeUnsafe(outcome === "Stop" ? 1782622445000 : 1782622450000), ); assert.deepEqual( completed.map((item) => item.status), ["running", "completed"], ); assert.deepEqual(completed[1]?.startedAt, DateTime.makeUnsafe(1782622441000)); assert.deepEqual(completed[1]?.completedAt, DateTime.makeUnsafe(1782622442000)); assert.equal(harness.terminalEvents()[0]?.status, status); if (outcome === "failed") { assert.equal(harness.terminalEvents()[0]?.failure?.message, error.message); } }).pipe( Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ), ); it.effect("resumes a provider thread without requesting or decoding its history", () => Effect.scoped( Effect.gen(function* () { const scenario = "codex-resume-metadata"; const nativeThreadId = `native-${scenario}-thread`; const preamble = codexReplayPreamble({ nativeThreadId, nativeTurnId: "unused-turn", prompt: "unused-prompt", }).slice(0, 5); const transcript = makeCodexReplayTranscript({ scenario, entries: [ ...preamble, { type: "expect_outbound", label: "thread/resume", frame: { id: 3, method: "thread/resume", params: { threadId: nativeThreadId, excludeTurns: true, config: CodexAdapterV2.CODEX_THREAD_CONFIG, }, }, }, { type: "emit_inbound", label: "thread/resume", frame: { id: 3, result: { thread: { id: nativeThreadId, updatedAt: 1782622450 } } }, }, ], }); const harness = yield* makeCodexReplayHarness(transcript); const resumed = yield* harness.runtime.resumeThread({ providerThread: harness.providerThread, modelSelection: CODEX_TEST_MODEL_SELECTION, runtimePolicy: CODEX_TEST_RUNTIME_POLICY, }); assert.equal(resumed.nativeThreadRef?.nativeId, nativeThreadId); assert.equal(resumed.status, "idle"); assert.equal(DateTime.toEpochMillis(resumed.updatedAt), 1782622450000); }).pipe( Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ), ); // regression: #26863 Codex 0.165 also sends the session effort over WebSocket it.effect("starts a thread with the selected reasoning effort", () => Effect.scoped( Effect.gen(function* () { const scenario = "codex-start-reasoning-effort"; const preamble = codexReplayPreamble({ nativeThreadId: `native-${scenario}-thread`, nativeTurnId: "unused-turn", prompt: "unused-prompt", }).slice(0, 5); const effortThreadId = `native-${scenario}-effort-thread`; const effortThreadStarted = codexReplayPreamble({ nativeThreadId: effortThreadId, nativeTurnId: "unused-turn", prompt: "unused-prompt", }) .slice(4, 5) .map((entry) => entry.type === "runtime_exit" ? entry : { ...entry, frame: { ...(entry.frame as Record), id: 3 } }, ); const harness = yield* makeCodexReplayHarness( makeCodexReplayTranscript({ scenario, entries: [ ...preamble, { type: "expect_outbound", label: "thread/start", frame: { id: 3, method: "thread/start", params: { config: { ...CodexAdapterV2.CODEX_THREAD_CONFIG, model_reasoning_effort: "high", }, }, }, }, ...effortThreadStarted, ], }), ); const started = yield* harness.runtime.ensureThread({ threadId: ThreadId.make(`thread-${scenario}-effort`), modelSelection: CODEX_TEST_HIGH_EFFORT_MODEL_SELECTION, runtimePolicy: CODEX_TEST_RUNTIME_POLICY, }); assert.equal(started.nativeThreadRef?.nativeId, effortThreadId); }).pipe( Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ), ); // regression: #26863 Codex 0.165 also sends the session effort over WebSocket it.effect("resumes a thread with the selected reasoning effort", () => Effect.scoped( Effect.gen(function* () { const scenario = "codex-resume-reasoning-effort"; const nativeThreadId = `native-${scenario}-thread`; const harness = yield* makeCodexReplayHarness( makeCodexReplayTranscript({ scenario, entries: [ ...codexReplayPreamble({ nativeThreadId, nativeTurnId: "unused-turn", prompt: "unused-prompt", }).slice(0, 5), { type: "expect_outbound", label: "thread/resume", frame: { id: 3, method: "thread/resume", params: { threadId: nativeThreadId, excludeTurns: true, config: { ...CodexAdapterV2.CODEX_THREAD_CONFIG, model_reasoning_effort: "high", }, }, }, }, { type: "emit_inbound", label: "thread/resume", frame: { id: 3, result: { thread: { id: nativeThreadId, updatedAt: 1782622450 } } }, }, ], }), ); const resumed = yield* harness.runtime.resumeThread({ providerThread: harness.providerThread, modelSelection: CODEX_TEST_HIGH_EFFORT_MODEL_SELECTION, runtimePolicy: CODEX_TEST_RUNTIME_POLICY, }); assert.equal(resumed.nativeThreadRef?.nativeId, nativeThreadId); }).pipe( Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ), ); // regression: #15389 archived native Codex sessions resume via thread/unarchive it.effect.each( ( [ ["recovers archived-session errors", "session saved-thread is archived", 0, ""], ["preserves missing-thread errors", "thread not found", 3, "thread not found"], [ "propagates unarchive missing-thread errors", "session saved-thread is archived", 4, "thread not found", ], [ "does not retry archived errors twice", "session saved-thread is archived", 5, "session saved-thread is archived", ], ] as const ).map(([name, resumeError, failAt, finalError]) => ({ name, resumeError, failAt, finalError })), )("$name", ({ name, resumeError, failAt, finalError }) => Effect.scoped( Effect.gen(function* () { const nativeThreadId = "saved-thread"; const params = { threadId: nativeThreadId, excludeTurns: true, cwd: CODEX_TEST_RUNTIME_POLICY.cwd, model: CODEX_TEST_MODEL_SELECTION.model, config: CodexAdapterV2.CODEX_THREAD_CONFIG, }; const entries: Array = [ ...codexReplayPreamble({ nativeThreadId, nativeTurnId: "unused-turn", prompt: "unused-prompt", }).slice(0, 5), { type: "expect_outbound", label: "resume archived thread", frame: { id: 3, method: "thread/resume", params }, }, { type: "emit_inbound", label: "resume error", frame: { id: 3, error: { code: -32600, message: resumeError } }, }, ]; if (failAt !== 3) { entries.push( { type: "expect_outbound", label: "unarchive same thread", frame: { id: 4, method: "thread/unarchive", params: { threadId: nativeThreadId } }, }, { type: "emit_inbound", label: "unarchive result", frame: failAt === 4 ? { id: 4, error: { code: -32600, message: finalError } } : { id: 4, result: { thread: { turns: [{ type: "unknown-history-item" }] } } }, }, ); } if (failAt === 0 || failAt === 5) { entries.push( { type: "expect_outbound", label: "retry identical resume", frame: { id: 5, method: "thread/resume", params }, }, { type: "emit_inbound", label: "retry result", frame: failAt === 5 ? { id: 5, error: { code: -32600, message: finalError } } : { id: 5, result: { thread: { id: nativeThreadId, updatedAt: 1782622450, turns: [{ type: "unknown-history-item" }], }, }, }, }, ); } const harness = yield* makeCodexReplayHarness( makeCodexReplayTranscript({ scenario: name, entries }), ); const resume = harness.runtime.resumeThread({ providerThread: harness.providerThread, modelSelection: CODEX_TEST_MODEL_SELECTION, runtimePolicy: CODEX_TEST_RUNTIME_POLICY, }); if (failAt !== 0) { const error = yield* Effect.flip(resume); assert.equal(error._tag, "ProviderAdapterResumeThreadError"); assert.nestedPropertyVal(error, "cause.errorMessage", finalError); assert.nestedPropertyVal( error, "cause.method", failAt === 4 ? "thread/unarchive" : "thread/resume", ); assert.nestedPropertyVal(error, "cause.requestId", String(failAt)); return; } const resumed = yield* resume; assert.equal(resumed.id, harness.providerThread.id); assert.equal(resumed.nativeThreadRef?.nativeId, nativeThreadId); assert.deepEqual( resumed.nativeConversationHeadRef, harness.providerThread.nativeConversationHeadRef, ); assert.equal(resumed.status, "idle"); assert.equal(DateTime.toEpochMillis(resumed.updatedAt), 1782622450000); }).pipe( Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ), ); it.effect.each([ "recover", "completion-only", "exhaust", "stop", "stop-continued", "stop-starting", "started-first", "completed-first", "new-run", "steer", "start-fails", ] as const)("handles terminal Codex capacity failures when retries %s", (outcome) => Effect.scoped( Effect.gen(function* () { const scenario = `capacity-${outcome}`; const nativeThreadId = `native-${scenario}`; const nativeTurnId = (attempt: number) => `${nativeThreadId}-${attempt}`; const prompt = "Continue the task."; const capacityError = { message: "Selected model is at capacity. Please try a different model.", codexErrorInfo: "serverOverloaded", }; const failureEntries = (attempt: number): Array => [ { type: "emit_inbound", frame: { method: "error", params: { threadId: nativeThreadId, turnId: nativeTurnId(attempt), willRetry: false, error: capacityError, }, }, }, { type: "emit_inbound", frame: { method: "turn/completed", params: { threadId: nativeThreadId, turn: { ...makeCodexReplayTurn({ id: nativeTurnId(attempt), status: "failed" }), error: capacityError, }, }, }, }, ]; const entries = codexReplayPreamble({ nativeThreadId, nativeTurnId: nativeTurnId(0), prompt, }); entries.push( ...(outcome === "completion-only" ? failureEntries(0).slice(1) : failureEntries(0)), ); if (outcome === "completion-only") entries.push(...failureEntries(0).slice(1)); const retries = outcome === "exhaust" ? 5 : outcome === "stop" ? 0 : 1; const steeredText = "Use the smaller change."; for (let attempt = 1; attempt <= retries; attempt++) { const start = codexReplayPreamble({ nativeThreadId, nativeTurnId: nativeTurnId(attempt), prompt, }).slice(-3); if ( outcome === "started-first" || outcome === "stop-starting" || outcome === "completed-first" || outcome === "new-run" ) { start.splice(1, 0, ...start.splice(2, 1)); } for (const entry of start) { if (entry.type === "expect_outbound") { entries.push({ ...entry, frame: { id: 3 + attempt, method: "turn/start", params: { threadId: nativeThreadId, input: [], cwd: "/workspace", model: "gpt-5.4", approvalPolicy: "never", approvalsReviewer: "user", sandboxPolicy: { type: "dangerFullAccess" }, summary: "detailed", collaborationMode: { mode: "default", settings: { model: "gpt-5.4" } }, }, }, }); } else if (entry.type === "emit_inbound" && entry.label === "turn/start") { if (outcome === "completed-first" || outcome === "new-run") entries.push({ type: "emit_inbound", frame: { method: "turn/completed", params: { threadId: nativeThreadId, turn: makeCodexReplayTurn({ id: nativeTurnId(attempt), status: "completed" }), }, }, }); if (outcome === "new-run") { entries.push( ...codexReplayPreamble({ nativeThreadId, nativeTurnId: nativeTurnId(2), prompt: "Next task.", }) .slice(-3) .map((next, index) => (index < 2 ? withReplayRequestId(next, 5) : next)), ...failureEntries(2), ); } entries.push( outcome === "start-fails" ? { type: "emit_inbound", frame: { id: 3 + attempt, error: { code: -32603, message: "Start failed" }, }, } : withReplayRequestId(entry, 3 + attempt), ); } else if (outcome !== "start-fails") { entries.push( outcome === "stop-starting" && entry.type === "emit_inbound" ? { ...entry, afterMs: 1_000 } : entry, ); if (outcome === "stop-starting") { entries.push( { type: "expect_outbound", frame: { id: 5, method: "turn/interrupt", params: { threadId: nativeThreadId, turnId: nativeTurnId(attempt) }, }, }, { type: "emit_inbound", frame: { id: 5, result: {} } }, ); } } } if (outcome === "exhaust") entries.push(...failureEntries(attempt)); if ( outcome === "recover" || outcome === "completion-only" || outcome === "started-first" || outcome === "stop-starting" ) { entries.push({ type: "emit_inbound", frame: { method: "turn/completed", params: { threadId: nativeThreadId, turn: makeCodexReplayTurn({ id: nativeTurnId(attempt), status: outcome === "stop-starting" ? "interrupted" : "completed", }), }, }, }); } if (outcome === "stop-continued" || outcome === "steer") { const steering = outcome === "steer"; entries.push( { type: "expect_outbound", frame: { id: 5, method: steering ? "turn/steer" : "turn/interrupt", params: steering ? { threadId: nativeThreadId, expectedTurnId: nativeTurnId(attempt), input: [{ type: "text", text: steeredText }], } : { threadId: nativeThreadId, turnId: nativeTurnId(attempt) }, }, }, { type: "emit_inbound", frame: { id: 5, result: steering ? { turnId: nativeTurnId(attempt) } : {} }, }, { type: "emit_inbound", frame: { method: "turn/completed", params: { threadId: nativeThreadId, turn: makeCodexReplayTurn({ id: nativeTurnId(attempt), status: steering ? "completed" : "interrupted", }), }, }, }, ); } } const milestones = yield* Queue.unbounded(); const starts: Array = []; const retryStarted = yield* Deferred.make(); const harness = yield* makeCodexReplayHarness( makeCodexReplayTranscript({ scenario, entries }), (event) => event.type === "turn.terminal" || (event.type === "turn_item.updated" && event.turnItem.type === "error" && event.turnItem.status === "running") ? Queue.offer(milestones, event) : Effect.void, (method, params) => Effect.gen(function* () { if (method === "turn/start") { starts.push(params); if (starts.length === 2) yield* Deferred.succeed(retryStarted, undefined); } }), ); const turnInput = makeCodexTestTurnInput({ threadId: harness.threadId, providerThread: harness.providerThread, now: yield* DateTime.now, attemptId: RunAttemptId.make(scenario), text: prompt, }); yield* harness.runtime.startTurn(turnInput); for (let attempt = 1; attempt <= Math.max(retries, 1); attempt++) { const milestone = yield* Queue.take(milestones); assert.equal(milestone.type, "turn_item.updated"); if (milestone.type !== "turn_item.updated" || milestone.turnItem.type !== "error") return; const providerTurnId = milestone.turnItem.providerTurnId; assert.isNotNull(providerTurnId); if (providerTurnId === null) return; assert.deepEqual(milestone.turnItem.retry, { attempt, maxAttempts: 5, retryDelayMs: Math.min(2_000 * 2 ** (attempt - 1), 30_000), }); assert.isEmpty(harness.terminalEvents()); if (outcome === "stop") { yield* harness.runtime.interruptTurn({ providerThread: harness.providerThread, providerTurnId, }); } const steering = outcome === "steer" ? yield* harness.runtime .steerTurn({ ...turnInput, providerTurnId, message: { ...turnInput.message, text: steeredText }, }) .pipe(Effect.forkScoped) : undefined; yield* TestClock.adjust(Math.min(2_000 * 2 ** (attempt - 1), 30_000)); if (outcome === "stop-starting") { yield* Deferred.await(retryStarted); const stopping = yield* harness.runtime .interruptTurn({ providerThread: harness.providerThread, providerTurnId }) .pipe(Effect.forkScoped); yield* TestClock.adjust("1 second"); yield* Fiber.join(stopping); } if (steering !== undefined) yield* Fiber.join(steering); if (outcome === "stop-continued") { yield* harness.until(() => harness.events.some( (event) => event.type === "provider_turn.updated" && event.providerTurn.nativeTurnRef?.nativeId === nativeTurnId(1), ), ); yield* harness.runtime.interruptTurn({ providerThread: harness.providerThread, providerTurnId, }); } } yield* harness.awaitTerminal(1); if (outcome === "new-run") { assert.equal((yield* Queue.take(milestones)).type, "turn.terminal"); yield* harness.runtime.startTurn({ ...turnInput, attemptId: RunAttemptId.make("capacity-next-task"), message: { ...turnInput.message, text: "Next task." }, }); const nextRetry = yield* Queue.take(milestones); assert.equal(nextRetry.type, "turn_item.updated"); if (nextRetry.type !== "turn_item.updated" || nextRetry.turnItem.type !== "error") return; assert.equal(nextRetry.turnItem.retry?.attempt, 1); return; } const terminal = harness.terminalEvents()[0]; assert.equal( terminal?.status, outcome === "recover" || outcome === "completion-only" || outcome === "steer" || outcome === "started-first" || outcome === "completed-first" ? "completed" : outcome === "stop" || outcome === "stop-continued" || outcome === "stop-starting" ? "interrupted" : "failed", ); assert.lengthOf(starts, 1 + retries); assert.lengthOf(harness.terminalEvents(), 1); assert.lengthOf( harness.events.filter( (event) => event.type === "provider_turn.updated" && event.providerTurn.status === "running", ), outcome === "start-fails" ? 1 : 1 + retries, ); const retryItems = harness.events.flatMap((event) => event.type === "turn_item.updated" && event.turnItem.type === "error" ? [event.turnItem] : [], ); assert.isTrue( ["completed", "failed", "interrupted"].includes(retryItems.at(-1)?.status ?? ""), ); assert.lengthOf(new Set(retryItems.map((item) => item.id)), 1); if (outcome === "recover") { const fs = yield* FileSystem.FileSystem; const cwd = yield* fs.makeTempDirectoryScoped({ prefix: "t3-capacity-retry-" }); const localTranscript = yield* decodeReplayTranscriptJson( (yield* encodeReplayTranscriptJson( makeCodexReplayTranscript({ scenario, entries }), )).replaceAll(yield* encodeStringJson("/workspace"), yield* encodeStringJson(cwd)), ); const driver = yield* CodexReplay.makeReplayDriver(localTranscript); yield* Effect.gen(function* () { const orchestrator = yield* Orchestrator.OrchestratorV2; const worker = yield* EffectWorker.OrchestrationEffectWorkerV2; const threadId = ThreadId.make("thread:capacity-retry"); yield* orchestrator.dispatch({ type: "thread.create", commandId: CommandId.make("create-capacity-retry"), threadId, projectId: ProjectId.make("project:capacity-retry"), title: "Capacity retry", modelSelection: CODEX_TEST_MODEL_SELECTION, runtimeMode: "full-access", interactionMode: "default", branch: null, worktreePath: cwd, createdBy: "user", creationSource: "web", }); const retrying = yield* orchestrator.streamDomainEvents.pipe( Stream.filter( (event) => event.type === "turn-item.updated" && event.payload.type === "error" && event.payload.retry != null && event.payload.status === "running", ), Stream.runHead, Effect.forkChild({ startImmediately: true }), ); yield* orchestrator.dispatch({ type: "message.dispatch", commandId: CommandId.make("start-capacity-retry"), threadId, messageId: MessageId.make("message:capacity-retry"), text: prompt, attachments: [], createdBy: "user", creationSource: "web", dispatchMode: { type: "start_immediately" }, }); yield* worker.drain(); yield* Fiber.join(retrying); assert.equal( (yield* orchestrator.getThreadProjection(threadId)).runs.at(-1)?.status, "running", ); const completed = yield* orchestrator.streamDomainEvents.pipe( Stream.filter( (event) => event.type === "run.updated" && event.payload.status === "waiting", ), Stream.runHead, Effect.forkChild({ startImmediately: true }), ); yield* TestClock.adjust("2 seconds"); yield* Fiber.join(completed); yield* worker.drain(); const projection = yield* orchestrator.getThreadProjection(threadId); assert.equal(projection.runs.at(-1)?.status, "completed"); assert.deepEqual( projection.providerTurns.map((turn) => turn.status), ["failed", "completed"], ); assert.lengthOf( projection.messages.filter((message) => message.role === "user"), 1, ); assert.isNull((yield* Ref.get(driver.state)).failure); }).pipe( Effect.provide( ProviderReplayHarness.layerWithRegistry( { name: "codex-capacity-retry", runtimePolicyOverride: { cwd } }, CodexAdapterV2Testkit.layer({ transcript: localTranscript, driver }), { runEffectWorker: false }, ), ), ); } }).pipe( Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ), ); it.effect("resolves retryable app-server errors on resumed provider activity", () => Effect.scoped( Effect.gen(function* () { const scenario = "codex-provider-api-retry"; const nativeThreadId = `native-${scenario}-thread`; const nativeTurnId = `native-${scenario}-turn`; const transcript = makeCodexReplayTranscript({ scenario, entries: [ ...codexReplayPreamble({ nativeThreadId, nativeTurnId, prompt: "Open github.com.", }), { type: "emit_inbound", label: "error/retry", frame: { method: "error", params: { threadId: nativeThreadId, turnId: nativeTurnId, willRetry: true, error: { message: "Reconnecting... 2/5", additionalDetails: "The response stream disconnected.", codexErrorInfo: { responseStreamDisconnected: { httpStatusCode: 529 }, }, }, }, }, }, { type: "emit_inbound", label: "item/started/after-retry", frame: { method: "item/started", params: { threadId: nativeThreadId, turnId: nativeTurnId, startedAtMs: 1782622445000, item: { type: "commandExecution", id: "command-after-provider-retry", command: "pwd", cwd: "/workspace", processId: "42", source: "unifiedExecStartup", status: "inProgress", commandActions: [{ type: "unknown", command: "pwd" }], aggregatedOutput: null, exitCode: null, durationMs: null, }, }, }, }, { type: "emit_inbound", label: "item/completed/after-retry", frame: { method: "item/completed", params: { threadId: nativeThreadId, turnId: nativeTurnId, completedAtMs: 1782622445010, item: { type: "commandExecution", id: "command-after-provider-retry", command: "pwd", cwd: "/workspace", processId: "42", source: "unifiedExecStartup", status: "completed", commandActions: [{ type: "unknown", command: "pwd" }], aggregatedOutput: "/workspace\n", exitCode: 0, durationMs: 10, }, }, }, }, { type: "emit_inbound", label: "turn/completed", frame: { method: "turn/completed", params: { threadId: nativeThreadId, turn: makeCodexReplayTurn({ id: nativeTurnId, status: "completed" }), }, }, }, ], }); const harness = yield* makeCodexReplayHarness(transcript); const now = yield* DateTime.now; yield* harness.runtime.startTurn( makeCodexTestTurnInput({ threadId: harness.threadId, providerThread: harness.providerThread, now, attemptId: RunAttemptId.make("attempt-codex-provider-api-retry"), text: "Open github.com.", }), ); yield* harness.awaitTerminal(1); const retryItems = harness.events.flatMap((event) => event.type === "turn_item.updated" && event.turnItem.type === "error" && event.turnItem.retry !== undefined ? [event.turnItem] : [], ); assert.lengthOf(retryItems, 2); assert.equal(retryItems[0]?.status, "running"); assert.equal(retryItems[0]?.failure.code, "responseStreamDisconnected"); assert.deepEqual(retryItems[0]?.retry, { attempt: 2, maxAttempts: 5, retryDelayMs: null, }); assert.equal(retryItems[1]?.id, retryItems[0]?.id); assert.equal(retryItems[1]?.status, "completed"); assert.equal(retryItems[1]?.title, "Provider recovered"); const recoveredIndex = harness.events.findIndex( (event) => event.type === "turn_item.updated" && event.turnItem.type === "error" && event.turnItem.status === "completed", ); const resumedCommandIndex = harness.events.findIndex( (event) => event.type === "turn_item.updated" && event.turnItem.type === "command_execution" && event.turnItem.input === "pwd", ); const terminalIndex = harness.events.findIndex((event) => event.type === "turn.terminal"); assert.isAtLeast(recoveredIndex, 0); assert.isAbove(resumedCommandIndex, recoveredIndex); assert.isAbove(terminalIndex, resumedCommandIndex); }).pipe( Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ), ); it.effect.each(["completed", "interrupted"] as const)( "retains Codex reasoning parts when the turn is %s", (terminalStatus) => Effect.scoped( Effect.gen(function* () { const scenario = `codex-reasoning-${terminalStatus}`; const nativeThreadId = `native-${scenario}-thread`; const nativeTurnId = `native-${scenario}-turn`; const prompt = "Explain the check."; const transcript = makeCodexReplayTranscript({ scenario, entries: [ ...codexReplayPreamble({ nativeThreadId, nativeTurnId, prompt }), ...[ { method: "item/reasoning/summaryTextDelta", params: { itemId: "thought", summaryIndex: 0, delta: "Summary " }, }, { method: "item/reasoning/summaryTextDelta", params: { itemId: "thought", summaryIndex: 0, delta: "one" }, }, { method: "item/reasoning/summaryTextDelta", params: { itemId: "thought", summaryIndex: 1, delta: "Summary two" }, }, { method: "item/reasoning/textDelta", params: { itemId: "thought", contentIndex: 0, delta: "Raw trace" }, }, { method: "item/completed", params: { item: { type: "commandExecution", id: "after-thought", command: "pwd", cwd: "/workspace", processId: "42", source: "unifiedExecStartup", status: "completed", commandActions: [{ type: "unknown", command: "pwd" }], aggregatedOutput: "/workspace", exitCode: 0, durationMs: 1, }, }, }, ...(terminalStatus === "completed" ? [ { method: "item/completed", params: { item: { type: "reasoning", id: "thought", summary: ["Final summary one", "Summary two"], content: ["Raw trace"], }, }, }, { method: "item/completed", params: { item: { type: "reasoning", id: "completion-only", summary: ["Completion without deltas"], content: [], }, }, }, { method: "item/reasoning/textDelta", params: { itemId: "delta-only", contentIndex: 0, delta: "Retained when completion omits content", }, }, { method: "item/completed", params: { item: { type: "reasoning", id: "delta-only", summary: [], content: [] }, }, }, ] : []), ].map((event, index) => ({ type: "emit_inbound" as const, label: `reasoning-${index}`, frame: { method: event.method, params: { threadId: nativeThreadId, turnId: nativeTurnId, ...event.params }, }, })), { type: "emit_inbound", label: "turn/completed", frame: { method: "turn/completed", params: { threadId: nativeThreadId, turn: makeCodexReplayTurn({ id: nativeTurnId, status: terminalStatus }), }, }, }, ], }); const harness = yield* makeCodexReplayHarness(transcript); yield* harness.runtime.startTurn( makeCodexTestTurnInput({ threadId: harness.threadId, providerThread: harness.providerThread, now: yield* DateTime.now, attemptId: RunAttemptId.make(`attempt-${scenario}`), text: prompt, }), ); yield* harness.firstTerminal; const latest = new Map( harness.events.flatMap((event) => event.type === "turn_item.updated" && event.turnItem.type === "reasoning" ? [[event.turnItem.id, event.turnItem] as const] : [], ), ); assert.deepEqual( [...latest.values()].map((item) => item.text), terminalStatus === "completed" ? [ "Final summary one", "Summary two", "Raw trace", "Completion without deltas", "Retained when completion omits content", ] : ["Summary one", "Summary two", "Raw trace"], ); assert.isTrue([...latest.values()].every((item) => item.status === terminalStatus)); assert.isTrue([...latest.values()].every((item) => item.streaming === false)); assert.isTrue( [...latest.values()].every( (item) => item.runId !== null && item.providerTurnId !== null, ), ); assert.equal(new Set([...latest.values()].map((item) => item.ordinal)).size, latest.size); const command = harness.events.find( (event) => event.type === "turn_item.updated" && event.turnItem.type === "command_execution", ); assert.isDefined(command); if (command?.type === "turn_item.updated") { assert.isTrue( [...latest.values()] .slice(0, 3) .every((item) => item.ordinal < command.turnItem.ordinal), ); } assert.deepEqual(assistantMessages(harness.events), []); }).pipe( Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ), ); const finalAnswerTranscript = ( scenario: string, answers: ReadonlyArray<{ readonly id: string; readonly text: string; readonly phase?: "commentary" | "final_answer" | null; readonly omitPhase?: boolean; readonly streamed?: boolean; readonly completionDelayMs?: number; }>, ) => { const nativeThreadId = `native-${scenario}-thread`; const nativeTurnId = `native-${scenario}-turn`; const prompt = "Reply with the requested recovery marker."; return makeCodexReplayTranscript({ scenario, entries: [ ...codexReplayPreamble({ nativeThreadId, nativeTurnId, prompt }), ...answers.flatMap( (answer, index): ReadonlyArray => { const phase = answer.omitPhase ? {} : { phase: answer.phase === undefined ? ("final_answer" as const) : answer.phase }; const completed: CodexReplay.CodexAppServerReplayEntry = { type: "emit_inbound", label: `item/completed/${answer.id}`, ...(answer.completionDelayMs === undefined ? {} : { afterMs: answer.completionDelayMs }), frame: { method: "item/completed", params: { item: { type: "agentMessage", id: answer.id, text: answer.text, ...phase, memoryCitation: null, }, threadId: nativeThreadId, turnId: nativeTurnId, completedAtMs: 1782622441000 + index, }, }, }; if (!answer.streamed) { return [completed]; } return [ { type: "emit_inbound", label: `item/started/${answer.id}`, frame: { method: "item/started", params: { item: { type: "agentMessage", id: answer.id, text: "", ...phase, memoryCitation: null, }, threadId: nativeThreadId, turnId: nativeTurnId, startedAtMs: 1782622440500 + index, }, }, }, { type: "emit_inbound", label: `item/agentMessage/delta/${answer.id}`, frame: { method: "item/agentMessage/delta", params: { threadId: nativeThreadId, turnId: nativeTurnId, itemId: answer.id, delta: answer.text, }, }, }, completed, ]; }, ), { type: "emit_inbound", label: "turn/completed", frame: { method: "turn/completed", params: { threadId: nativeThreadId, turn: makeCodexReplayTurn({ id: nativeTurnId, status: "completed" }), }, }, }, ], }); }; it.effect("suppresses a trailing empty final answer after a non-empty final answer", () => Effect.scoped( Effect.gen(function* () { const transcript = finalAnswerTranscript("codex-redundant-empty-final", [ { id: "answer-non-empty", text: "CODEX_RECOVERY_OK" }, { id: "answer-empty", text: "" }, ]); const harness = yield* makeCodexReplayHarness(transcript); const now = yield* DateTime.now; yield* harness.runtime.startTurn( makeCodexTestTurnInput({ threadId: harness.threadId, providerThread: harness.providerThread, now, attemptId: RunAttemptId.make("attempt-codex-redundant-empty-final"), text: "Reply with the requested recovery marker.", }), ); yield* harness.awaitTerminal(1); assert.deepEqual( assistantMessages(harness.events).map((event) => event.message.text), ["CODEX_RECOVERY_OK"], ); }).pipe( Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ), ); const BG_SCENARIO = "codex-bg-exec-wake"; const BG_NATIVE_THREAD = "native-codex-bg-thread"; const BG_NATIVE_TURN = "native-codex-bg-turn"; const BG_COMMAND_ITEM = "call-codex-bg-command"; const BG_COMMAND = "sleep 20 && echo CODEX_BG_WAKE_DONE"; const BG_PROMPT = "Start the sleep in the background and reply STARTED."; const backgroundCommandItem = (status: "inProgress" | "completed"): Record => ({ type: "commandExecution", id: BG_COMMAND_ITEM, command: BG_COMMAND, cwd: "/workspace", processId: "4242", source: "unifiedExecStartup", status, commandActions: [{ type: "unknown", command: BG_COMMAND }], aggregatedOutput: status === "completed" ? "CODEX_BG_WAKE_DONE\n" : null, exitCode: status === "completed" ? 0 : null, durationMs: status === "completed" ? 25_000 : null, }); const backgroundExecTranscript = makeCodexReplayTranscript({ scenario: BG_SCENARIO, entries: [ ...codexReplayPreamble({ nativeThreadId: BG_NATIVE_THREAD, nativeTurnId: BG_NATIVE_TURN, prompt: BG_PROMPT, }), { type: "emit_inbound", label: "item/started/command", frame: { method: "item/started", params: { item: backgroundCommandItem("inProgress"), threadId: BG_NATIVE_THREAD, turnId: BG_NATIVE_TURN, startedAtMs: 1782622440500, }, }, }, { type: "emit_inbound", label: "item/completed/root-answer", frame: { method: "item/completed", params: { item: { type: "agentMessage", id: "root-answer-bg", text: "STARTED", phase: "final_answer", memoryCitation: null, }, threadId: BG_NATIVE_THREAD, turnId: BG_NATIVE_TURN, completedAtMs: 1782622441000, }, }, }, { type: "emit_inbound", label: "turn/completed", frame: { method: "turn/completed", params: { threadId: BG_NATIVE_THREAD, turn: makeCodexReplayTurn({ id: BG_NATIVE_TURN, status: "completed" }), }, }, }, { type: "emit_inbound", label: "item/completed/command-late", afterMs: 30_000, frame: { method: "item/completed", params: { item: backgroundCommandItem("completed"), threadId: BG_NATIVE_THREAD, turnId: BG_NATIVE_TURN, completedAtMs: 1782622465500, }, }, }, ], }); it.effect.each([false, true])( "keeps commands interrupted after native thread closure (persistent tool: %s)", (retainContext) => Effect.gen(function* () { const notification = (method: string, params: unknown) => ({ type: "emit_inbound" as const, label: method, frame: { method, params }, }); const monitor = (status: "inProgress" | "completed") => notification(status === "inProgress" ? "item/started" : "item/completed", { threadId: BG_NATIVE_THREAD, turnId: BG_NATIVE_TURN, item: { type: "dynamicToolCall", id: "persistent-monitor", namespace: "grok", tool: "monitor", status, arguments: { persistent: true, command: "tail -f" }, }, }); // Replies fence notification handling on the same protocol reader. const unsubscribe = (id: number, notifications: ReturnType[] = []) => [ { type: "expect_outbound" as const, label: `unsubscribe-${id}`, frame: { id, method: "thread/unsubscribe", params: { threadId: BG_NATIVE_THREAD } }, }, ...notifications, { type: "emit_inbound" as const, label: `unsubscribe-result-${id}`, frame: { id, result: { status: "notSubscribed" } }, }, ]; const closed = notification("thread/closed", { threadId: BG_NATIVE_THREAD }); const transcript = makeCodexReplayTranscript({ scenario: `codex-background-thread-closed-${retainContext}`, entries: [ ...backgroundExecTranscript.entries.slice(0, -2), ...(retainContext ? [monitor("inProgress")] : []), backgroundExecTranscript.entries.at(-2)!, ...unsubscribe(4), ...unsubscribe(5, [notification("thread/closed", { threadId: "unrelated-thread" })]), ...unsubscribe(6, [closed]), ...unsubscribe(7, [ closed, notification("item/completed", { threadId: BG_NATIVE_THREAD, turnId: BG_NATIVE_TURN, item: backgroundCommandItem("completed"), }), ...(retainContext ? [monitor("completed")] : []), ]), ], }); const interrupted = yield* Deferred.make(); const monitorCompleted = yield* Deferred.make(); const harness = yield* makeCodexReplayHarness(transcript, (event) => { if (event.type !== "turn_item.updated") return Effect.void; if ( event.turnItem.nativeItemRef?.nativeId === BG_COMMAND_ITEM && event.turnItem.status === "interrupted" ) { return Deferred.succeed(interrupted, undefined); } return event.turnItem.nativeItemRef?.nativeId === "persistent-monitor" && event.turnItem.status === "completed" ? Deferred.succeed(monitorCompleted, undefined) : Effect.void; }); yield* harness.runtime.startTurn( makeCodexTestTurnInput({ threadId: harness.threadId, providerThread: harness.providerThread, now: yield* DateTime.now, attemptId: RunAttemptId.make("attempt-codex-thread-closed"), text: BG_PROMPT, }), ); yield* harness.firstTerminal; const unload = harness.runtime.unloadThread!({ providerThread: harness.providerThread }); yield* unload; assert.isTrue(yield* harness.hasPendingBackgroundWork); yield* unload; assert.isTrue(yield* harness.hasPendingBackgroundWork); yield* unload; assert.equal(yield* harness.hasPendingBackgroundWork, retainContext); yield* Deferred.await(interrupted); yield* unload; if (retainContext) yield* Deferred.await(monitorCompleted); assert.deepEqual( { items: harness.events.flatMap((event) => event.type === "turn_item.updated" && event.turnItem.nativeItemRef?.nativeId === BG_COMMAND_ITEM ? [event.turnItem.status] : [], ), nodes: harness.events.flatMap((event) => event.type === "node.updated" && event.node.nativeItemRef?.nativeId === BG_COMMAND_ITEM ? [event.node.status] : [], ), roots: harness.terminalEvents().map((event) => event.status), continuations: harness.continuationRequests.length, }, { items: ["running", "interrupted"], nodes: ["running", "interrupted"], roots: ["completed"], continuations: 0, }, ); assert.isFalse(yield* harness.hasPendingBackgroundWork); assert.isFalse( yield* harness.runtime.hasPendingBackgroundWorkForThread!(harness.providerThread), ); }).pipe( Effect.scoped, Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ); const backgroundStopCases = [true, false, "still_running", "thread_unloaded"] as const; const makeBackgroundStopTranscript = (terminated: (typeof backgroundStopCases)[number]) => { const stillRunning = terminated === "still_running"; return makeCodexReplayTranscript({ scenario: `codex-bg-stop-${terminated}`, entries: [ ...backgroundExecTranscript.entries.slice(0, -1), { type: "expect_outbound", label: "terminate-background-command", frame: { id: 4, method: "thread/backgroundTerminals/terminate", params: { threadId: BG_NATIVE_THREAD, processId: "4242" }, }, }, { type: "emit_inbound", label: "terminate-background-command", frame: terminated === "thread_unloaded" ? { id: 4, error: { code: -32600, message: `thread not found: ${BG_NATIVE_THREAD}` }, } : { id: 4, result: { terminated: terminated === true } }, }, ...(terminated === false || stillRunning ? [ { type: "expect_outbound" as const, frame: { id: 5, method: "thread/backgroundTerminals/list", params: { threadId: BG_NATIVE_THREAD }, }, }, { type: "emit_inbound" as const, frame: { id: 5, result: { data: stillRunning ? [{ processId: "4242" }] : [], nextCursor: null }, }, }, ] : []), ...(stillRunning ? [ { type: "expect_outbound" as const, frame: { id: 6, method: "thread/backgroundTerminals/terminate", params: { threadId: BG_NATIVE_THREAD, processId: "4242" }, }, }, { type: "emit_inbound" as const, frame: { id: 6, result: { terminated: false } }, }, { type: "expect_outbound" as const, frame: { id: 7, method: "thread/backgroundTerminals/list", params: { threadId: BG_NATIVE_THREAD }, }, }, { type: "emit_inbound" as const, frame: { id: 7, result: { data: [{ processId: "4242" }], nextCursor: null } }, }, { type: "expect_outbound" as const, frame: { id: 8, method: "thread/backgroundTerminals/terminate", params: { threadId: BG_NATIVE_THREAD, processId: "4242" }, }, }, { type: "emit_inbound" as const, frame: { id: 8, result: { terminated: true } }, }, ] : []), backgroundExecTranscript.entries.at(-1)!, ], }); }; it.effect.each(backgroundStopCases)( "stops a command after root completion when termination returns %s", (terminated) => { const stillRunning = terminated === "still_running"; const transcript = makeBackgroundStopTranscript(terminated); return Effect.scoped( Effect.gen(function* () { const stopped = yield* Deferred.make(); const harness = yield* makeCodexReplayHarness(transcript, (event) => event.type === "turn_item.updated" && event.turnItem.type === "command_execution" && event.turnItem.status === "interrupted" ? Deferred.succeed(stopped, undefined) : Effect.void, ); const now = yield* DateTime.now; yield* harness.runtime.startTurn( makeCodexTestTurnInput({ threadId: harness.threadId, providerThread: harness.providerThread, now, attemptId: RunAttemptId.make("attempt-codex-bg-stop"), text: BG_PROMPT, }), ); yield* harness.firstTerminal; const terminal = harness.terminalEvents()[0]!; assert.equal(terminal.status, "completed"); assert.isTrue(yield* harness.hasPendingBackgroundWork); assert.isFalse( yield* harness.runtime.hasPendingBackgroundWorkForThread!({ ...harness.providerThread, id: ProviderThreadId.make("unrelated-provider-thread"), }), ); if (stillRunning) { const failed = yield* harness.runtime .interruptTurn({ providerThread: harness.providerThread, providerTurnId: terminal.providerTurnId, requestRuntimeRestart: true, }) .pipe(Effect.exit); assert.equal(failed._tag, "Failure"); assert.isTrue(yield* harness.hasPendingBackgroundWork); } yield* harness.runtime.interruptTurn({ providerThread: harness.providerThread, providerTurnId: stillRunning ? ProviderTurnId.make("later-completed-turn") : terminal.providerTurnId, requestRuntimeRestart: true, }); yield* Deferred.await(stopped); assert.isFalse(yield* harness.hasPendingBackgroundWork); assert.lengthOf(harness.terminalEvents(), 1); assert.equal(harness.terminalEvents()[0]?.status, "completed"); assert.lengthOf(harness.continuationRequests, 0); }).pipe( Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ); }, ); it.effect("stops a completed run's background command without a client-supplied run", () => { const transcript = makeBackgroundStopTranscript(true); return Effect.scoped( Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const cwd = yield* fs.makeTempDirectoryScoped({ prefix: "t3-bg-stop-workspace-" }); const localTranscript = yield* decodeReplayTranscriptJson( (yield* encodeReplayTranscriptJson(transcript)).replaceAll( yield* encodeStringJson("/workspace"), yield* encodeStringJson(cwd), ), ); const replayDriver = yield* CodexReplay.makeReplayDriver(localTranscript); const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; assert.equal( Number(yield* spawner.exitCode(ChildProcess.make("git", ["init", "--quiet"], { cwd }))), 0, ); assert.equal( Number( yield* spawner.exitCode( ChildProcess.make( "git", [ "-c", "user.name=Test", "-c", "user.email=test@example.com", "commit", "--allow-empty", "--quiet", "-m", "Initial commit", ], { cwd }, ), ), ), 0, ); yield* Effect.gen(function* () { const orchestrator = yield* Orchestrator.OrchestratorV2; const worker = yield* EffectWorker.OrchestrationEffectWorkerV2; const threadId = ThreadId.make("thread:background-stop"); yield* orchestrator.dispatch({ type: "thread.create", commandId: CommandId.make("create-background-stop"), threadId, projectId: ProjectId.make("project:background-stop"), title: "Background stop", modelSelection: CODEX_TEST_MODEL_SELECTION, runtimeMode: "full-access", interactionMode: "default", branch: null, worktreePath: cwd, createdBy: "user", creationSource: "web", }); const waiting = yield* orchestrator.streamDomainEvents.pipe( Stream.filter( (event) => event.type === "run.updated" && event.payload.status === "waiting", ), Stream.runHead, Effect.forkChild({ startImmediately: true }), ); yield* orchestrator.dispatch({ type: "message.dispatch", commandId: CommandId.make("start-background-stop"), threadId, messageId: MessageId.make("message:background-stop"), text: BG_PROMPT, attachments: [], createdBy: "user", creationSource: "web", dispatchMode: { type: "start_immediately" }, }); yield* worker.drain(); assert.isNull((yield* Ref.get(replayDriver.state)).failure); yield* Fiber.join(waiting); yield* worker.drain(); const projection = yield* orchestrator.getThreadProjection(threadId); const run = projection.runs.at(-1)!; assert.equal(run.status, "completed"); assert.equal( (yield* orchestrator.getThreadShell(threadId))?.pendingBackgroundTasks?.length, 1, ); const stopped = yield* orchestrator.streamDomainEvents.pipe( Stream.filter( (event) => event.type === "turn-item.updated" && event.payload.type === "command_execution" && event.payload.status === "interrupted", ), Stream.runHead, Effect.forkChild({ startImmediately: true }), ); yield* orchestrator.dispatch({ type: "thread.stop", commandId: CommandId.make("stop-background-command"), threadId, }); yield* worker.drain(); yield* Fiber.join(stopped); assert.equal( (yield* orchestrator.getThreadProjection(threadId)).runs.at(-1)?.status, "completed", ); assert.deepEqual( (yield* orchestrator.getThreadShell(threadId))?.pendingBackgroundTasks, [], ); }).pipe( Effect.provide( ProviderReplayHarness.layerWithRegistry( { name: "codex-background-stop", runtimePolicyOverride: { cwd } }, CodexAdapterV2Testkit.layer({ transcript: localTranscript, driver: replayDriver, }), { runEffectWorker: false }, ), ), ); }).pipe(Effect.provide(NodeServices.layer)), ); }); it.effect("Stop ends a background command no Codex process tracks any more", () => Effect.scoped( Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const cwd = yield* fs.makeTempDirectoryScoped({ prefix: "t3-bg-stale-workspace-" }); const staleTranscript = makeCodexReplayTranscript({ scenario: "codex-bg-stop-untracked", entries: [ ...backgroundExecTranscript.entries.slice(0, -1), { type: "runtime_exit", status: "success" }, ], }); const localTranscript = yield* decodeReplayTranscriptJson( (yield* encodeReplayTranscriptJson(staleTranscript)).replaceAll( yield* encodeStringJson("/workspace"), yield* encodeStringJson(cwd), ), ); const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; for (const args of [ ["init", "--quiet"], [ "-c", "user.name=Test", "-c", "user.email=test@example.com", "commit", "--allow-empty", "--quiet", "-m", "Initial commit", ], ]) { assert.equal(Number(yield* spawner.exitCode(ChildProcess.make("git", args, { cwd }))), 0); } yield* Effect.gen(function* () { const orchestrator = yield* Orchestrator.OrchestratorV2; const worker = yield* EffectWorker.OrchestrationEffectWorkerV2; const threadId = ThreadId.make("thread:background-stop-untracked"); yield* orchestrator.dispatch({ type: "thread.create", commandId: CommandId.make("create-background-stop-untracked"), threadId, projectId: ProjectId.make("project:background-stop-untracked"), title: "Background stop untracked", modelSelection: CODEX_TEST_MODEL_SELECTION, runtimeMode: "full-access", interactionMode: "default", branch: null, worktreePath: cwd, createdBy: "user", creationSource: "web", }); const settled = yield* orchestrator.streamDomainEvents.pipe( Stream.filter( (event) => event.type === "run.updated" && (event.payload.status === "waiting" || event.payload.status === "completed"), ), Stream.runHead, Effect.forkChild({ startImmediately: true }), ); yield* orchestrator.dispatch({ type: "message.dispatch", commandId: CommandId.make("start-background-stop-untracked"), threadId, messageId: MessageId.make("message:background-stop-untracked"), text: BG_PROMPT, attachments: [], createdBy: "user", creationSource: "web", dispatchMode: { type: "start_immediately" }, }); yield* worker.drain(); yield* Fiber.join(settled); yield* worker.drain(); const before = yield* orchestrator.getThreadShell(threadId); assert.deepEqual( before?.pendingBackgroundTasks?.map((task) => task.kind), ["command"], "the thread still shows the command the gone process never finished", ); const run = (yield* orchestrator.getThreadProjection(threadId)).runs.at(-1)!; yield* orchestrator.dispatch({ type: "run.interrupt", commandId: CommandId.make("stop-background-untracked"), threadId, runId: run.id, holdQueue: true, }); yield* worker.drain(); const projection = yield* orchestrator.getThreadProjection(threadId); assert.deepEqual( projection.turnItems.flatMap((item) => item.type === "command_execution" ? [item.status] : [], ), ["interrupted"], ); assert.equal(projection.runs.at(-1)?.status, "completed"); assert.deepEqual( (yield* orchestrator.getThreadShell(threadId))?.pendingBackgroundTasks, [], ); }).pipe( Effect.provide( ProviderReplayHarness.layerWithRegistry( { name: "codex-background-stop-untracked", runtimePolicyOverride: { cwd } }, CodexAdapterV2Testkit.layer({ transcript: localTranscript }), { runEffectWorker: false }, ), ), ); }).pipe(Effect.provide(NodeServices.layer)), ), ); const INTERRUPT_SCENARIO = "codex-interrupt-mid-command"; const INTERRUPT_NATIVE_THREAD = "native-codex-interrupt-thread"; const INTERRUPT_NATIVE_TURN = "native-codex-interrupt-turn"; const INTERRUPT_COMMAND_ITEM = "exec-codex-interrupt-command"; const INTERRUPT_COMMAND_ITEM_TWO = "exec-codex-interrupt-command-two"; const INTERRUPT_CHILD_COMMAND_ITEM = "exec-codex-interrupt-child-command"; const INTERRUPT_CHILD_NATIVE_THREAD = "native-codex-interrupt-child-thread"; const INTERRUPT_CHILD_NATIVE_TURN = "native-codex-interrupt-child-turn"; const INTERRUPT_LATE_CHILD_NATIVE_TURN = "native-codex-interrupt-late-child-turn"; const INTERRUPT_COMMAND = "bash -c 'sleep 30; echo SHOULD_NOT_FINISH_CMD_INTERRUPT_FIXTURE'"; const INTERRUPT_COMMAND_TWO = "bash -c 'sleep 20; echo SECOND_COMMAND'"; const INTERRUPT_PROMPT = "Run a long foreground command and wait until interrupted."; const interruptCommandItem = (status: "inProgress" | "completed"): Record => ({ type: "commandExecution", id: INTERRUPT_COMMAND_ITEM, command: INTERRUPT_COMMAND, cwd: "/workspace", processId: "57680", source: "unifiedExecStartup", status, commandActions: [{ type: "unknown", command: INTERRUPT_COMMAND }], aggregatedOutput: status === "completed" ? "SHOULD_NOT_FINISH_CMD_INTERRUPT_FIXTURE\n" : null, exitCode: status === "completed" ? 0 : null, durationMs: status === "completed" ? 30_000 : null, }); const interruptMidCommandTranscript = makeCodexReplayTranscript({ scenario: INTERRUPT_SCENARIO, entries: [ ...codexReplayPreamble({ nativeThreadId: INTERRUPT_NATIVE_THREAD, nativeTurnId: INTERRUPT_NATIVE_TURN, prompt: INTERRUPT_PROMPT, }), { type: "emit_inbound", label: "item/started/command", frame: { method: "item/started", params: { item: interruptCommandItem("inProgress"), threadId: INTERRUPT_NATIVE_THREAD, turnId: INTERRUPT_NATIVE_TURN, startedAtMs: 1782622440500, }, }, }, { type: "expect_outbound", label: "turn/interrupt", frame: { id: 4, method: "turn/interrupt", params: { threadId: INTERRUPT_NATIVE_THREAD, turnId: INTERRUPT_NATIVE_TURN, }, }, }, { type: "emit_inbound", label: "turn/interrupt", frame: { id: 4, result: {} }, }, { type: "emit_inbound", label: "item/started/command-two-after-interrupt-response", frame: { method: "item/started", params: { item: { ...interruptCommandItem("inProgress"), id: INTERRUPT_COMMAND_ITEM_TWO, command: INTERRUPT_COMMAND_TWO, processId: "57681", commandActions: [{ type: "unknown", command: INTERRUPT_COMMAND_TWO }], }, threadId: INTERRUPT_NATIVE_THREAD, turnId: INTERRUPT_NATIVE_TURN, startedAtMs: 1782622440600, }, }, }, { type: "emit_inbound", label: "turn/completed", frame: { method: "turn/completed", params: { threadId: INTERRUPT_NATIVE_THREAD, turn: makeCodexReplayTurn({ id: INTERRUPT_NATIVE_TURN, status: "interrupted", }), }, }, }, { type: "expect_outbound", label: "thread/backgroundTerminals/terminate/one", frame: { id: 5, method: "thread/backgroundTerminals/terminate", params: { threadId: INTERRUPT_NATIVE_THREAD, processId: "57680" }, }, }, { type: "emit_inbound", label: "thread/backgroundTerminals/terminate/one", frame: { id: 5, result: { terminated: false } }, }, { type: "expect_outbound", label: "thread/backgroundTerminals/list/after-false", frame: { id: 6, method: "thread/backgroundTerminals/list", params: { threadId: INTERRUPT_NATIVE_THREAD }, }, }, { type: "emit_inbound", label: "thread/backgroundTerminals/list/after-false", frame: { id: 6, result: { data: [], nextCursor: null } }, }, { type: "expect_outbound", label: "thread/backgroundTerminals/terminate/two", frame: { id: 7, method: "thread/backgroundTerminals/terminate", params: { threadId: INTERRUPT_NATIVE_THREAD, processId: "57681" }, }, }, { type: "emit_inbound", label: "thread/backgroundTerminals/terminate/two", frame: { id: 7, result: { terminated: true } }, }, { type: "emit_inbound", label: "item/completed/command-late", afterMs: 30_000, frame: { method: "item/completed", params: { item: interruptCommandItem("completed"), threadId: INTERRUPT_NATIVE_THREAD, turnId: INTERRUPT_NATIVE_TURN, completedAtMs: 1782622465500, }, }, }, ], }); it.effect("contains commands that start before and after the interrupt response", () => Effect.scoped( Effect.gen(function* () { const harness = yield* makeCodexReplayHarness(interruptMidCommandTranscript); const now = yield* DateTime.now; yield* harness.runtime.startTurn( makeCodexTestTurnInput({ threadId: harness.threadId, providerThread: harness.providerThread, now, attemptId: RunAttemptId.make("attempt-codex-interrupt-mid-command"), text: INTERRUPT_PROMPT, }), ); yield* harness.until(() => harness.events.some( (event) => event.type === "turn_item.updated" && event.turnItem.type === "command_execution" && event.turnItem.status === "running", ), ); const providerTurnId = harness.events.find( ( event, ): event is Extract< ProviderAdapter.ProviderAdapterV2Event, { type: "provider_turn.updated" } > => event.type === "provider_turn.updated", )?.providerTurn.id; assert.isDefined(providerTurnId); yield* harness.runtime.interruptTurn({ providerThread: harness.providerThread, providerTurnId, }); yield* harness.awaitTerminal(1); assert.equal(harness.terminalEvents()[0]?.status, "interrupted"); const terminalIndex = harness.events.findIndex((event) => event.type === "turn.terminal"); assert.isAtLeast(terminalIndex, 0); let lastCommandBeforeTerminal: | Extract | undefined; for (let index = 0; index < terminalIndex; index++) { const event = harness.events[index]; if (event?.type === "turn_item.updated" && event.turnItem.type === "command_execution") { lastCommandBeforeTerminal = event; } } assert.isDefined(lastCommandBeforeTerminal); assert.equal(lastCommandBeforeTerminal.turnItem.status, "interrupted"); assert.isNotNull(lastCommandBeforeTerminal.turnItem.completedAt); const interruptedCommandsBeforeTerminal = harness.events .slice(0, terminalIndex) .flatMap((event) => event.type === "turn_item.updated" && event.turnItem.type === "command_execution" && event.turnItem.status === "interrupted" ? [event.turnItem.input] : [], ) .sort(); assert.deepEqual( interruptedCommandsBeforeTerminal, [INTERRUPT_COMMAND, INTERRUPT_COMMAND_TWO].sort(), ); const interruptedCommandIndex = harness.events.findIndex( (event, index) => index < terminalIndex && event.type === "turn_item.updated" && event.turnItem.type === "command_execution" && event.turnItem.status === "interrupted", ); assert.isAbove( terminalIndex, interruptedCommandIndex, "command terminalization must precede turn.terminal", ); assert.isFalse(yield* harness.hasPendingBackgroundWork); assert.lengthOf(harness.continuationRequests, 0); yield* TestClock.adjust("30 seconds"); for (let attempt = 0; attempt < 100; attempt++) { yield* Effect.yieldNow; } assert.lengthOf(harness.continuationRequests, 0); assert.isFalse(yield* harness.hasPendingBackgroundWork); assert.lengthOf(harness.terminalEvents(), 1); const postTerminalCommandUpdates = harness.events.filter( (event, index) => index > terminalIndex && event.type === "turn_item.updated" && event.turnItem.type === "command_execution", ); assert.lengthOf( postTerminalCommandUpdates, 0, "late item/completed after interrupt must not project", ); const commandUpdates = harness.events.filter( ( event, ): event is Extract< ProviderAdapter.ProviderAdapterV2Event, { type: "turn_item.updated" } > => event.type === "turn_item.updated" && event.turnItem.type === "command_execution", ); assert.isAtLeast(commandUpdates.length, 2, "start + interrupt terminalization"); assert.equal(commandUpdates[commandUpdates.length - 1]?.turnItem.status, "interrupted"); }).pipe( Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ), ); const interruptSubagentCommandTranscript = makeCodexReplayTranscript({ scenario: "codex-interrupt-subagent-command", entries: [ ...codexReplayPreamble({ nativeThreadId: INTERRUPT_NATIVE_THREAD, nativeTurnId: INTERRUPT_NATIVE_TURN, prompt: INTERRUPT_PROMPT, }), { type: "emit_inbound", label: "item/completed/subAgentActivity-started", frame: { method: "item/completed", params: { item: { type: "subAgentActivity", id: "call-codex-interrupt-subagent", kind: "started", agentThreadId: INTERRUPT_CHILD_NATIVE_THREAD, agentPath: "/root/stop_hold", }, threadId: INTERRUPT_NATIVE_THREAD, turnId: INTERRUPT_NATIVE_TURN, completedAtMs: 1782622441000, }, }, }, { type: "emit_inbound", label: "turn/started/child", frame: { method: "turn/started", params: { threadId: INTERRUPT_CHILD_NATIVE_THREAD, turn: makeCodexReplayTurn({ id: INTERRUPT_CHILD_NATIVE_TURN, status: "inProgress", }), }, }, }, { type: "emit_inbound", label: "item/started/child-command", frame: { method: "item/started", params: { item: { ...interruptCommandItem("inProgress"), id: INTERRUPT_CHILD_COMMAND_ITEM, processId: "57682", }, threadId: INTERRUPT_CHILD_NATIVE_THREAD, turnId: INTERRUPT_CHILD_NATIVE_TURN, startedAtMs: 1782622441500, }, }, }, { type: "expect_outbound", label: "turn/interrupt/root", frame: { id: 4, method: "turn/interrupt", params: { threadId: INTERRUPT_NATIVE_THREAD, turnId: INTERRUPT_NATIVE_TURN, }, }, }, { type: "emit_inbound", label: "turn/interrupt/root", frame: { id: 4, result: {} }, }, { type: "expect_outbound", label: "turn/interrupt/child", frame: { id: 5, method: "turn/interrupt", params: { threadId: INTERRUPT_CHILD_NATIVE_THREAD, turnId: INTERRUPT_CHILD_NATIVE_TURN, }, }, }, { type: "emit_inbound", label: "turn/interrupt/child", frame: { id: 5, result: {} }, }, { type: "expect_outbound", label: "thread/backgroundTerminals/terminate/child", frame: { id: 6, method: "thread/backgroundTerminals/terminate", params: { threadId: INTERRUPT_CHILD_NATIVE_THREAD, processId: "57682" }, }, }, { type: "emit_inbound", label: "thread/backgroundTerminals/terminate/child", frame: { id: 6, result: { terminated: true } }, }, { type: "emit_inbound", label: "turn/completed/root", frame: { method: "turn/completed", params: { threadId: INTERRUPT_NATIVE_THREAD, turn: makeCodexReplayTurn({ id: INTERRUPT_NATIVE_TURN, status: "interrupted" }), }, }, }, { type: "emit_inbound", label: "turn/completed/child-completed-race", frame: { method: "turn/completed", params: { threadId: INTERRUPT_CHILD_NATIVE_THREAD, turn: makeCodexReplayTurn({ id: INTERRUPT_CHILD_NATIVE_TURN, status: "completed", }), }, }, }, { type: "runtime_exit", status: "success" }, ], }); const assertChildProviderTerminalBeforeRoot = ( events: ReadonlyArray, rootThreadId: ThreadId, ) => { const terminalIndex = events.findIndex((event) => event.type === "turn.terminal"); const childProviderTurnIndex = events.findIndex( (event) => event.type === "provider_turn.updated" && event.threadId !== rootThreadId && event.providerTurn.status === "interrupted", ); const childProviderThreadIndex = events.findIndex( (event) => event.type === "provider_thread.updated" && event.providerThread.appThreadId !== rootThreadId && event.providerThread.status === "idle", ); assert.isAtLeast(childProviderTurnIndex, 0, "child provider turn must terminalize"); assert.isAtLeast(childProviderThreadIndex, 0, "child provider thread must become idle"); assert.isAbove( terminalIndex, childProviderTurnIndex, "child provider turn must terminalize before the root run", ); assert.isAbove( terminalIndex, childProviderThreadIndex, "child provider thread must become idle before the root run", ); }; it.effect("contains descendant commands and keeps Stop authoritative", () => Effect.scoped( Effect.gen(function* () { const harness = yield* makeCodexReplayHarness(interruptSubagentCommandTranscript); const now = yield* DateTime.now; yield* harness.runtime.startTurn( makeCodexTestTurnInput({ threadId: harness.threadId, providerThread: harness.providerThread, now, attemptId: RunAttemptId.make("attempt-codex-interrupt-subagent-command"), text: INTERRUPT_PROMPT, }), ); yield* harness.until(() => harness.events.some( (event) => event.type === "turn_item.updated" && event.turnItem.type === "command_execution" && event.turnItem.nativeItemRef?.nativeId === INTERRUPT_CHILD_COMMAND_ITEM && event.turnItem.status === "running", ), ); const providerTurnId = harness.events.find( ( event, ): event is Extract< ProviderAdapter.ProviderAdapterV2Event, { type: "provider_turn.updated" } > => event.type === "provider_turn.updated" && event.threadId === harness.threadId, )?.providerTurn.id; assert.isDefined(providerTurnId); yield* harness.runtime.interruptTurn({ providerThread: harness.providerThread, providerTurnId, }); yield* harness.awaitTerminal(1); assert.equal(harness.terminalEvents()[0]?.status, "interrupted"); const childCommandUpdates = harness.events.filter( ( event, ): event is Extract< ProviderAdapter.ProviderAdapterV2Event, { type: "turn_item.updated" } > => event.type === "turn_item.updated" && event.turnItem.type === "command_execution" && event.turnItem.nativeItemRef?.nativeId === INTERRUPT_CHILD_COMMAND_ITEM, ); assert.equal(childCommandUpdates.at(-1)?.turnItem.status, "interrupted"); assert.equal(harness.subagentUpdates().at(-1)?.subagent.status, "interrupted"); assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId); assert.isFalse(yield* harness.hasPendingBackgroundWork); }).pipe( Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ), ); const rootCompletionIndex = interruptSubagentCommandTranscript.entries.findIndex( (entry) => entry.type === "emit_inbound" && entry.label === "turn/completed/root", ); const interruptLateSubagentTurnTranscript = makeCodexReplayTranscript({ scenario: "codex-interrupt-late-subagent-turn", entries: [ ...interruptSubagentCommandTranscript.entries.slice(0, rootCompletionIndex), { type: "emit_inbound", label: "turn/started/late-child", frame: { method: "turn/started", params: { threadId: INTERRUPT_CHILD_NATIVE_THREAD, turn: makeCodexReplayTurn({ id: INTERRUPT_LATE_CHILD_NATIVE_TURN, status: "inProgress", }), }, }, }, ...interruptSubagentCommandTranscript.entries.slice(rootCompletionIndex, -1), { type: "expect_outbound", label: "turn/interrupt/late-child", frame: { id: 7, method: "turn/interrupt", params: { threadId: INTERRUPT_CHILD_NATIVE_THREAD, turnId: INTERRUPT_LATE_CHILD_NATIVE_TURN, }, }, }, { type: "emit_inbound", label: "turn/interrupt/late-child", frame: { id: 7, result: {} }, }, { type: "runtime_exit", status: "success" }, ], }); it.effect("interrupts descendants that start after the initial Stop snapshot", () => Effect.scoped( Effect.gen(function* () { const harness = yield* makeCodexReplayHarness(interruptLateSubagentTurnTranscript); const now = yield* DateTime.now; yield* harness.runtime.startTurn( makeCodexTestTurnInput({ threadId: harness.threadId, providerThread: harness.providerThread, now, attemptId: RunAttemptId.make("attempt-codex-interrupt-late-subagent-turn"), text: INTERRUPT_PROMPT, }), ); yield* harness.until(() => harness.events.some( (event) => event.type === "turn_item.updated" && event.turnItem.type === "command_execution" && event.turnItem.nativeItemRef?.nativeId === INTERRUPT_CHILD_COMMAND_ITEM && event.turnItem.status === "running", ), ); const providerTurnId = harness.events.find( ( event, ): event is Extract< ProviderAdapter.ProviderAdapterV2Event, { type: "provider_turn.updated" } > => event.type === "provider_turn.updated" && event.threadId === harness.threadId, )?.providerTurn.id; assert.isDefined(providerTurnId); const interruptFiber = yield* harness.runtime .interruptTurn({ providerThread: harness.providerThread, providerTurnId }) .pipe(Effect.forkScoped); yield* harness.until(() => harness.events.some( (event) => event.type === "provider_turn.updated" && event.providerTurn.nativeTurnRef?.nativeId === INTERRUPT_LATE_CHILD_NATIVE_TURN, ), ); yield* Fiber.join(interruptFiber); yield* harness.awaitTerminal(1); const lateChildUpdates = harness.events.filter( ( event, ): event is Extract< ProviderAdapter.ProviderAdapterV2Event, { type: "provider_turn.updated" } > => event.type === "provider_turn.updated" && event.providerTurn.nativeTurnRef?.nativeId === INTERRUPT_LATE_CHILD_NATIVE_TURN, ); assert.equal(lateChildUpdates.at(-1)?.providerTurn.status, "interrupted"); assertChildProviderTerminalBeforeRoot(harness.events, harness.threadId); assert.isFalse(yield* harness.hasPendingBackgroundWork); }).pipe( Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ), ); it.effect.each([ { name: "usage", code: "usageLimitExceeded", notification: false, expectedClass: "usage_limit", }, { name: "replacement", code: "usageLimitExceeded", notification: true, expectedClass: "provider_error", }, { name: "late-reset", code: "usageLimitExceeded", notification: false, expectedClass: "usage_limit", }, { name: "matching-details", code: "usageLimitExceeded", notification: true, expectedClass: "usage_limit", }, { name: "deferred-reset", code: "usageLimitExceeded", notification: false, expectedClass: "usage_limit", }, { name: "retry", code: "usageLimitExceeded", notification: true, expectedClass: "usage_limit" }, { name: "exhausted-429", code: "responseTooManyFailedAttempts", errorInfo: { responseTooManyFailedAttempts: { httpStatusCode: 429 } }, notification: false, expectedClass: "usage_limit", }, { name: "exhausted-500", code: "responseTooManyFailedAttempts", errorInfo: { responseTooManyFailedAttempts: { httpStatusCode: 500 } }, notification: false, expectedClass: "provider_error", }, { name: "chatgpt-workspace-limit", code: "other", details: "subscription_sharing_workplace_individual_usage_limit_exceeded", notification: true, expectedClass: "provider_error", }, ] as const)("classifies Codex terminal failures from $name evidence", (scenario) => Effect.scoped( Effect.gen(function* () { const nativeThreadId = `native-limit-${scenario.name}`; const nativeTurnId = `turn-limit-${scenario.name}`; const message = "Provider stopped this request."; const errorInfo = "errorInfo" in scenario ? scenario.errorInfo : scenario.code; const resetAt = "2033-05-19T07:20:00.000Z"; const snapshot = { type: "emit_inbound" as const, label: "account/rateLimits/updated", frame: { method: "account/rateLimits/updated", params: { rateLimits: { limitId: "codex", primary: { usedPercent: 100, resetsAt: 2000100000, windowDurationMins: 300 }, }, }, }, }; const transcript = makeCodexReplayTranscript({ scenario: `codex-limit-${scenario.name}`, entries: [ ...codexReplayPreamble({ nativeThreadId, nativeTurnId, prompt: "Continue." }), ...(scenario.name === "deferred-reset" ? [snapshot] : []), ...(scenario.name === "deferred-reset" ? [ { type: "emit_inbound" as const, label: "item/completed/subAgentActivity-started", frame: { method: "item/completed", params: { threadId: nativeThreadId, turnId: nativeTurnId, item: { type: "subAgentActivity", id: "limit-child-spawn", kind: "started", agentThreadId: "native-limit-child", agentPath: "/root/limit_child", }, }, }, }, { type: "emit_inbound" as const, label: "turn/started/child", frame: { method: "turn/started", params: { threadId: "native-limit-child", turn: makeCodexReplayTurn({ id: "limit-child-turn", status: "inProgress", }), }, }, }, ] : []), ...(scenario.notification ? [ { type: "emit_inbound" as const, label: "error", frame: { method: "error", params: { threadId: nativeThreadId, turnId: nativeTurnId, willRetry: scenario.name === "retry", error: { message, codexErrorInfo: errorInfo, additionalDetails: "details" in scenario ? scenario.details : scenario.name === "matching-details" ? "Detailed provider allowance explanation." : null, }, }, }, }, ] : []), { type: "emit_inbound", label: "turn/completed", frame: { method: "turn/completed", params: { threadId: nativeThreadId, turn: { ...makeCodexReplayTurn({ id: nativeTurnId, status: "failed" }), error: { message: scenario.name === "replacement" ? "A different failure." : message, ...(scenario.notification && scenario.name !== "matching-details" && !("details" in scenario) ? {} : { codexErrorInfo: errorInfo }), }, }, }, }, }, ...(scenario.name === "late-reset" ? [snapshot] : []), ...(scenario.name === "deferred-reset" ? [ { ...snapshot, frame: { method: "account/rateLimits/updated", params: { rateLimits: { limitId: "codex", primary: { usedPercent: 100, resetsAt: 2000200000, windowDurationMins: 300, }, }, }, }, }, { type: "emit_inbound" as const, label: "turn/completed/child", frame: { method: "turn/completed", params: { threadId: "native-limit-child", turn: makeCodexReplayTurn({ id: "limit-child-turn", status: "completed", }), }, }, }, ] : []), ], }); const resetReceipt = yield* Deferred.make(); const harness = yield* makeCodexReplayHarness(transcript, (event) => event.type === "turn_item.updated" && event.turnItem.type === "error" && event.turnItem.failure.resetAt === resetAt ? Deferred.succeed(resetReceipt, undefined) : Effect.void, ); yield* harness.runtime.startTurn( makeCodexTestTurnInput({ threadId: harness.threadId, providerThread: harness.providerThread, now: yield* DateTime.now, text: "Continue.", attemptId: RunAttemptId.make(`attempt-limit-${scenario.name}`), }), ); yield* harness.firstTerminal; const terminal = harness.terminalEvents()[0]; assert.equal(terminal?.status, "failed"); if (terminal?.status !== "failed") return; assert.equal(terminal.failure.class, scenario.expectedClass); assert.equal(terminal.threadDisposition, "reusable"); if (scenario.name === "deferred-reset") assert.equal(terminal.failure.resetAt, resetAt); if (scenario.name === "matching-details") assert.equal(terminal.failure.message, "Detailed provider allowance explanation."); if (scenario.name === "late-reset") { yield* Deferred.await(resetReceipt); const item = harness.events.find( (event) => event.type === "turn_item.updated" && event.turnItem.type === "error" && event.turnItem.failure.resetAt === resetAt, ); assert.isDefined(item); } if (scenario.name === "retry") assert.equal(terminal.retry?.attempt, 1); if (scenario.name === "chatgpt-workspace-limit") { assert.equal(terminal.failure.code, scenario.details); assert.include(terminal.failure.message, "workspace admin"); } }).pipe( Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ), ); // regression: #17501 Codex 429 retry exhaustion classifies as a usage limit it.effect.each([ { notificationStatus: 500, completionStatus: 429, willRetry: false, expectedClass: "usage_limit", }, { notificationStatus: 429, completionStatus: 500, willRetry: true, expectedClass: "provider_error", }, { notificationStatus: 429, completionStatus: null, willRetry: false, expectedClass: "usage_limit", }, { notificationStatus: 429, completionStatus: null, willRetry: true, expectedClass: "usage_limit", }, ] as const)( "honors completion HTTP status from $notificationStatus to $completionStatus (retry: $willRetry)", ({ notificationStatus, completionStatus, expectedClass, willRetry }) => Effect.scoped( Effect.gen(function* () { const name = `${notificationStatus}-${completionStatus}-${willRetry}`; const nativeThreadId = `native-exhaustion-status-${name}`; const nativeTurnId = `turn-exhaustion-status-${name}`; const message = "Provider stopped this request."; const additionalDetails = "Detailed notification evidence."; const transcript = makeCodexReplayTranscript({ scenario: `codex-exhaustion-status-${name}`, entries: [ ...codexReplayPreamble({ nativeThreadId, nativeTurnId, prompt: "Continue." }), { type: "emit_inbound", label: "error", frame: { method: "error", params: { threadId: nativeThreadId, turnId: nativeTurnId, willRetry, error: { message, codexErrorInfo: { responseTooManyFailedAttempts: { httpStatusCode: notificationStatus }, }, additionalDetails, }, }, }, }, { type: "emit_inbound", label: "turn/completed", frame: { method: "turn/completed", params: { threadId: nativeThreadId, turn: { ...makeCodexReplayTurn({ id: nativeTurnId, status: "failed" }), error: { message, codexErrorInfo: { responseTooManyFailedAttempts: { httpStatusCode: completionStatus }, }, }, }, }, }, }, ], }); const harness = yield* makeCodexReplayHarness(transcript); yield* harness.runtime.startTurn( makeCodexTestTurnInput({ threadId: harness.threadId, providerThread: harness.providerThread, now: yield* DateTime.now, text: "Continue.", attemptId: RunAttemptId.make(`attempt-exhaustion-status-${name}`), }), ); yield* harness.firstTerminal; const terminal = harness.terminalEvents()[0]; assert.equal(terminal?.status, "failed"); if (terminal?.status !== "failed") return; assert.equal(terminal.failure.class, expectedClass); assert.equal(terminal.failure.code, "responseTooManyFailedAttempts"); assert.equal(terminal.threadDisposition, "reusable"); const notificationClass = notificationStatus === 429 ? "usage_limit" : "provider_error"; assert.equal( terminal.failure.message, expectedClass === notificationClass ? additionalDetails : message, ); if (willRetry) assert.equal(terminal.retry?.attempt, 1); }).pipe( Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ), ); const FAILED_SCENARIO = "codex-failed-mid-command"; const FAILED_NATIVE_THREAD = "native-codex-failed-thread"; const FAILED_NATIVE_TURN = "native-codex-failed-turn"; const FAILED_COMMAND_ITEM = "exec-codex-failed-command"; const FAILED_COMMAND = "sleep 30"; const FAILED_PROMPT = "Run a command that will be abandoned when the turn fails."; const failedMidCommandTranscript = makeCodexReplayTranscript({ scenario: FAILED_SCENARIO, entries: [ ...codexReplayPreamble({ nativeThreadId: FAILED_NATIVE_THREAD, nativeTurnId: FAILED_NATIVE_TURN, prompt: FAILED_PROMPT, }), { type: "emit_inbound", label: "item/started/command", frame: { method: "item/started", params: { item: { type: "commandExecution", id: FAILED_COMMAND_ITEM, command: FAILED_COMMAND, cwd: "/workspace", processId: "99", source: "unifiedExecStartup", status: "inProgress", commandActions: [{ type: "unknown", command: FAILED_COMMAND }], aggregatedOutput: null, exitCode: null, durationMs: null, }, threadId: FAILED_NATIVE_THREAD, turnId: FAILED_NATIVE_TURN, startedAtMs: 1782622440500, }, }, }, { type: "emit_inbound", label: "turn/completed", frame: { method: "turn/completed", params: { threadId: FAILED_NATIVE_THREAD, turn: { ...makeCodexReplayTurn({ id: FAILED_NATIVE_TURN, status: "failed", }), error: { message: "provider failed mid-command" }, }, }, }, }, ], }); it.effect("terminalizes running command items before turn.terminal on failed turns", () => Effect.scoped( Effect.gen(function* () { const harness = yield* makeCodexReplayHarness(failedMidCommandTranscript); const now = yield* DateTime.now; yield* harness.runtime.startTurn( makeCodexTestTurnInput({ threadId: harness.threadId, providerThread: harness.providerThread, now, attemptId: RunAttemptId.make("attempt-codex-failed-mid-command"), text: FAILED_PROMPT, }), ); yield* harness.awaitTerminal(1); assert.equal(harness.terminalEvents()[0]?.status, "failed"); const terminalIndex = harness.events.findIndex((event) => event.type === "turn.terminal"); const failedCommandIndex = harness.events.findIndex( (event, index) => index < terminalIndex && event.type === "turn_item.updated" && event.turnItem.type === "command_execution" && event.turnItem.status === "failed", ); assert.isAtLeast(failedCommandIndex, 0); assert.isAbove( terminalIndex, failedCommandIndex, "failed-turn command terminalization must precede turn.terminal", ); assert.isFalse(yield* harness.hasPendingBackgroundWork); assert.lengthOf(harness.continuationRequests, 0); }).pipe( Effect.provide( Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer), ), ), ), ); const MCP_APP_SCENARIO = "codex-mcp-app-capture"; const MCP_APP_NATIVE_THREAD = "native-codex-mcp-app-thread"; const MCP_APP_NATIVE_TURN = "native-codex-mcp-app-turn"; const MCP_APP_ITEM = "mcp-weather-call"; const MCP_APP_PROMPT = "Show the weather in Oslo."; const MCP_APP_RESOURCE = "ui://weather/dashboard"; const MCP_APP_HTML = "

Weather

"; const mcpAppToolItem = (status: "inProgress" | "completed") => ({ type: "mcpToolCall", id: MCP_APP_ITEM, server: "weather", tool: "get_weather", status, arguments: { city: "Oslo" }, mcpAppResourceUri: MCP_APP_RESOURCE, ...(status === "completed" ? { result: { content: [{ type: "text", text: "Sunny" }], structuredContent: { temp: 21 } } } : {}), }); const mcpAppTranscript = makeCodexReplayTranscript({ scenario: MCP_APP_SCENARIO, entries: [ ...codexReplayPreamble({ nativeThreadId: MCP_APP_NATIVE_THREAD, nativeTurnId: MCP_APP_NATIVE_TURN, prompt: MCP_APP_PROMPT, }), { type: "emit_inbound", label: "item/started/app-tool", frame: { method: "item/started", params: { item: mcpAppToolItem("inProgress"), threadId: MCP_APP_NATIVE_THREAD, turnId: MCP_APP_NATIVE_TURN, startedAtMs: 1782622440500, }, }, }, { type: "emit_inbound", label: "item/completed/app-tool", frame: { method: "item/completed", params: { item: mcpAppToolItem("completed"), threadId: MCP_APP_NATIVE_THREAD, turnId: MCP_APP_NATIVE_TURN, completedAtMs: 1782622441500, }, }, }, { type: "emit_inbound", label: "turn/completed", frame: { method: "turn/completed", params: { threadId: MCP_APP_NATIVE_THREAD, turn: makeCodexReplayTurn({ id: MCP_APP_NATIVE_TURN, status: "completed" }), }, }, }, { type: "expect_outbound", label: "mcpServer/resource/read", frame: { id: 4, method: "mcpServer/resource/read", params: { threadId: MCP_APP_NATIVE_THREAD, server: "weather", uri: MCP_APP_RESOURCE }, }, }, { type: "emit_inbound", label: "mcpServer/resource/read", frame: { id: 4, result: { contents: [ { uri: MCP_APP_RESOURCE, mimeType: "text/html;profile=mcp-app", text: MCP_APP_HTML, _meta: { ui: { csp: { connectDomains: ["https://api.weather.test"] } } }, }, ], }, }, }, ], }); it.effect("captures an MCP app's resource and holds the turn open until it lands", () => Effect.scoped( Effect.gen(function* () { const appCaptured = yield* Deferred.make(); const harness = yield* makeCodexReplayHarness(mcpAppTranscript, (event) => event.type === "turn_item.updated" && event.turnItem.type === "dynamic_tool" && event.turnItem.status === "completed" ? Deferred.succeed(appCaptured, event.turnItem) : Effect.void, ); const now = yield* DateTime.now; yield* harness.runtime.startTurn( makeCodexTestTurnInput({ threadId: harness.threadId, providerThread: harness.providerThread, now, attemptId: RunAttemptId.make("attempt-codex-mcp-app"), text: MCP_APP_PROMPT, }), ); const item = yield* Deferred.await(appCaptured); assert.equal(harness.terminalEvents()[0]?.status, "completed"); const completedRows = harness.events.filter( (event) => event.type === "turn_item.updated" && event.turnItem.type === "dynamic_tool" && event.turnItem.status === "completed", ); assert.lengthOf(completedRows, 1); assert.equal(item.type, "dynamic_tool"); const output = item.type === "dynamic_tool" ? (item.output as Record) : {}; const reference = readMcpAppReference(output[MCP_APP_OUTPUT_KEY]); assert.deepEqual( { ...reference, attachmentId: undefined }, { attachmentId: undefined, server: "weather", tool: "get_weather", resourceUri: MCP_APP_RESOURCE, csp: { connectDomains: ["https://api.weather.test"] }, }, ); assert.deepEqual(output.result, { content: [{ type: "text", text: "Sunny" }], structuredContent: { temp: 21 }, }); const fileSystem = yield* FileSystem.FileSystem; const stored = yield* fileSystem.readFileString( resolveAttachmentPathById({ attachmentsDir: harness.serverConfig.attachmentsDir, attachmentId: reference!.attachmentId, })!, ); assert.include(stored, "connect-src https://api.weather.test"); assert.isBelow(stored.indexOf("Content-Security-Policy"), stored.indexOf("