From 258980f7dc5380e64cc0a610c7dffe3576acb1ea Mon Sep 17 00:00:00 2001 From: Jake Leventhal Date: Thu, 29 Oct 2026 10:29:15 -0400 Subject: [PATCH 01/13] feat(fx): sub-agent threads show the child's steps and its full reply fx clips every ACP tool result to a 200-byte preview and streams no sub-agent frames, so an fx sub-agent's thread held only its task and a cut-off reply. T3 now opts into fx's libfx hooks on session/new, load and resume, and uses them only when fx confirms hooksVersion 1. Older fx keeps the ACP-only view. A dispatched child's tool calls and its narration between them become steps in its existing child thread, and its reply comes whole from the root call's tool.end or tool.result. Delegation phases drive the child's status. Unused events, including model.start request content, are dropped at decode and nothing retains them. Child events are followed only while their call runs, so a reload, which replays finished calls without their steps, leaves projected threads alone. The new fx_subagent_hooks fixture is a live fx 0.0.15 recording, and an adapter test plays fx's recorded load replay. Existing fx fixtures now expect the opt-in and cover fx not confirming it. Co-Authored-By: Claude Opus 5.5 --- .../scripts/record-fx-acp-replay-fixture.ts | 17 +- .../Adapters/AcpAdapterV2.testkit.ts | 2 + .../Adapters/FxAdapterV2.test.ts | 196 +++++++- .../Adapters/FxAdapterV2.testkit.ts | 2 + .../fx_early_stop/fx_transcript.ndjson | 2 +- .../testkit/fixtures/fx_subagent/fx_output.ts | 5 +- .../fixtures/fx_subagent/fx_transcript.ndjson | 2 +- .../fixtures/fx_subagent_hooks/fx_output.ts | 68 +++ .../fx_subagent_hooks/fx_transcript.ndjson | 151 ++++++ .../fixtures/fx_subagent_hooks/input.ts | 48 ++ .../testkit/fixtures/index.ts | 14 + .../message_steering/fx_transcript.ndjson | 2 +- .../fixtures/multi_turn/fx_transcript.ndjson | 2 +- .../fixtures/queued_turn/fx_transcript.ndjson | 2 +- .../fixtures/simple/fx_transcript.ndjson | 2 +- .../fx_transcript.ndjson | 2 +- .../fx_transcript.ndjson | 2 +- .../turn_interrupt/fx_transcript.ndjson | 2 +- docs/user/providers-fx.md | 4 +- .../src/server/AcpSessionRuntime.ts | 8 + packages/provider-fx/src/server/acpSupport.ts | 2 + packages/provider-fx/src/server/adapter.ts | 82 +-- .../provider-fx/src/server/subagents.test.ts | 250 ++++++++++ packages/provider-fx/src/server/subagents.ts | 468 ++++++++++++++++++ packages/provider-fx/src/testing.ts | 1 + 25 files changed, 1239 insertions(+), 97 deletions(-) create mode 100644 apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent_hooks/fx_output.ts create mode 100644 apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent_hooks/fx_transcript.ndjson create mode 100644 apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent_hooks/input.ts create mode 100644 packages/provider-fx/src/server/subagents.test.ts create mode 100644 packages/provider-fx/src/server/subagents.ts diff --git a/apps/server/scripts/record-fx-acp-replay-fixture.ts b/apps/server/scripts/record-fx-acp-replay-fixture.ts index 73ab4023f732..3c8bcf4871b2 100644 --- a/apps/server/scripts/record-fx-acp-replay-fixture.ts +++ b/apps/server/scripts/record-fx-acp-replay-fixture.ts @@ -223,8 +223,23 @@ function normalizeOutboundFrame(frame: Record, runtimeInstructi } } +/** A model call's request content: up to 64 KiB per hook that T3 never reads. */ +const HOOK_MODEL_REQUEST_FIELDS = new Set(["messages", "instructions", "tools", "toolsJson"]); + function normalizeInboundFrame(frame: Record): Record { const params = isRecord(frame.params) ? frame.params : undefined; + if (frame.method === "libfx/hook" && params?.type === "model.start" && isRecord(params.data)) { + const data = params.data; + return { + ...frame, + params: { + ...params, + data: Object.fromEntries( + Object.entries(data).filter(([key]) => !HOOK_MODEL_REQUEST_FIELDS.has(key)), + ), + }, + }; + } const update = isRecord(params?.update) ? params.update : undefined; if ( update?.sessionUpdate === "available_commands_update" && @@ -461,7 +476,7 @@ const recordScenario = Effect.fn("recordFxScenario")(function* (fixtureName: str generatedBy: "live-fx-recorder", fxVersion: initializeAgent.version ?? "unknown", normalization: - "Session ids are fx-session-NN, the workspace is , HOME is /home/fx-replay and the recording user is fx-replay. T3-owned prompt text, MCP servers and initialize params other than clientCapabilities and _meta are . User skill names, emails and token-shaped strings are removed. Timestamps are kept as recorded.", + "Session ids are fx-session-NN, the workspace is , HOME is /home/fx-replay and the recording user is fx-replay. T3-owned prompt text, MCP servers and initialize params other than clientCapabilities and _meta are . User skill names, emails and token-shaped strings are removed. libfx/hook model.start events keep their ids and purpose but not their request content. Timestamps are kept as recorded.", droppedFrames, }, entries: [ diff --git a/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.testkit.ts b/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.testkit.ts index d043ac09a6ae..1c2d9d8f889d 100644 --- a/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.testkit.ts +++ b/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.testkit.ts @@ -196,6 +196,7 @@ export function makeAcpReplayRuntime(input: { readonly cancelMeta?: AcpSessionRuntime.AcpSessionRuntimeOptions["cancelMeta"]; readonly cancelBehavior?: AcpSessionRuntime.AcpSessionRuntimeOptions["cancelBehavior"]; readonly initializeMeta?: AcpSessionRuntime.AcpSessionRuntimeOptions["initializeMeta"]; + readonly sessionMeta?: AcpSessionRuntime.AcpSessionRuntimeOptions["sessionMeta"]; readonly replayGate?: ProviderReplayGate; }): ( runtimeInput: AcpAdapterV2RuntimeInput, @@ -238,6 +239,7 @@ export function makeAcpReplayRuntime(input: { ...(input.cancelMeta === undefined ? {} : { cancelMeta: input.cancelMeta }), ...(input.cancelBehavior === undefined ? {} : { cancelBehavior: input.cancelBehavior }), ...(input.initializeMeta === undefined ? {} : { initializeMeta: input.initializeMeta }), + ...(input.sessionMeta === undefined ? {} : { sessionMeta: input.sessionMeta }), ...(input.replayGate === undefined ? {} : { transformStdout: holdGatedReplayLines(input.transcript, input.replayGate) }), diff --git a/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts index b6e78f8115d6..844919e40e57 100644 --- a/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts @@ -1,5 +1,5 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; -import { describe, it } from "@effect/vitest"; +import { assert, describe, it } from "@effect/vitest"; import { MessageId, NodeId, @@ -13,7 +13,11 @@ import { } from "@t3tools/contracts"; import { ACP_PROTOCOL } from "@t3tools/provider-acp/server/adapter"; import { FxSettings } from "@t3tools/provider-fx/settings"; -import { FX_DRIVER_KIND, makeFxAdapterV2 } from "@t3tools/provider-fx/testing"; +import { + FX_DRIVER_KIND, + FX_HOOKS_SESSION_META, + makeFxAdapterV2, +} from "@t3tools/provider-fx/testing"; import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions"; import { ProviderAdapterV2RuntimePolicy } from "@t3tools/provider-core/server/ProviderAdapter"; @@ -115,6 +119,64 @@ const fxSession = (input: { modes: { currentModeId: "ask", availableModes: input.modes.map((id) => ({ id, name: id })) }, }); +const update = (sessionId: string, sessionUpdate: Record): Frame => ({ + type: "emit_inbound", + label: `session/update:${String(sessionUpdate.sessionUpdate)}`, + frame: { + kind: "notification", + method: "session/update", + params: { sessionId, update: sessionUpdate }, + }, +}); +/** A `libfx/hook` event as fx 0.0.15 sends it once a session opts in. */ +const hook = (sessionId: string, type: string, data: Record): Frame => ({ + type: "emit_inbound", + label: `libfx/hook:${type}`, + frame: { + kind: "notification", + method: "libfx/hook", + params: { + version: 1, + sessionId, + rootSessionId: SESSION_ID, + parentSessionId: sessionId === SESSION_ID ? null : SESSION_ID, + sourceSessionId: null, + workId: null, + turnId: "turn-1", + executionId: `execution-${sessionId}`, + stepIndex: 1, + timestamp: 1_793_283_383_907, + type, + data, + contentCaptured: true, + contentTruncated: false, + }, + }, +}); +const subagentReply = (reply: string) => + JSON.stringify({ ok: true, result: reply, error_code: null }); +const subagentCall = (callId: string, task: string) => + update(SESSION_ID, { + sessionUpdate: "tool_call", + toolCallId: callId, + name: "subagent", + title: `Subagent · ${task}`, + kind: "other", + status: "pending", + rawInput: { action: "run", task }, + _meta: { fx: { toolCall: { internal: false } } }, + }); +/** fx's 200-byte ACP preview of a finished `subagent` call. */ +const subagentPreview = (callId: string, reply: string) => + update(SESSION_ID, { + sessionUpdate: "tool_call_update", + toolCallId: callId, + status: "completed", + content: [ + { type: "content", content: { type: "text", text: subagentReply(reply).slice(0, 199) } }, + ], + }); + const initialize: ReadonlyArray = [ outbound("initialize"), answer("initialize", { @@ -184,6 +246,7 @@ const openFxSession = Effect.fn("openFxSession")(function* (input: { ), childProcessSpawner, fileSystem, + sessionMeta: FX_HOOKS_SESSION_META, }), }); const threadId = ThreadId.make(`thread-fx-${input.scenario}`); @@ -199,7 +262,7 @@ const openFxSession = Effect.fn("openFxSession")(function* (input: { interactionMode: "default", cwd: replayDir, }); - yield* Effect.gen(function* () { + const events = yield* Effect.gen(function* () { const session = yield* adapter.openSession({ threadId, providerSessionId: ProviderSessionId.make(`provider-session-fx-${input.scenario}`), @@ -207,7 +270,7 @@ const openFxSession = Effect.fn("openFxSession")(function* (input: { runtimePolicy, ...(input.loadSessionId === undefined ? {} : { initialNativeThreadId: input.loadSessionId }), }); - if (input.turnModel === undefined) return; + if (input.turnModel === undefined) return []; const providerThread = yield* session.ensureThread({ threadId, modelSelection, runtimePolicy }); const now = yield* DateTime.now; const turnSelection = selectionOn(input.turnModel); @@ -252,12 +315,13 @@ const openFxSession = Effect.fn("openFxSession")(function* (input: { modelSelection: turnSelection, runtimePolicy, }); - yield* session.events.pipe( + return yield* session.events.pipe( Stream.takeUntil((event) => event.type === "turn.terminal"), - Stream.runDrain, + Stream.runCollect, ); }).pipe(Effect.scoped); yield* makeAcpReplayCompletenessAssertion(fileSystem, statusPath, transcript); + return events; }); describe("FxAdapterV2", () => { @@ -273,7 +337,11 @@ describe("FxAdapterV2", () => { runtimeMode: "auto", frames: [ ...initialize, - outbound("session/new", { cwd: "", mcpServers: "" }), + outbound("session/new", { + cwd: "", + mcpServers: "", + _meta: FX_HOOKS_SESSION_META, + }), answer("session/new", fxSession({ model: FX_DEFAULT_MODEL, modes })), ...setConfig( "mode", @@ -365,4 +433,118 @@ describe("FxAdapterV2", () => { ], }).pipe(Effect.provide(testLayer), Effect.scoped), ); + // A restart reloads the session: fx replays the finished call from history + // with its 200-byte preview and none of the child's steps, which T3 already + // holds. The reload confirms hooks, so the next sub-agent shows its steps. + it.effect("leaves a reloaded sub-agent's thread alone and follows the next one", () => + Effect.gen(function* () { + const earlierReply = `package.json names fx-hooks-fixture. ${"It has five scripts. ".repeat(12)}`; + const reply = `tsconfig.json targets ES2023. ${"Strict mode is on. ".repeat(12)}`; + const childSessionId = "fx-session-04"; + const events = yield* openFxSession({ + scenario: "reload-subagent", + runtimeMode: "approval-required", + loadSessionId: SESSION_ID, + turnModel: "default", + frames: [ + ...initialize, + outbound("session/load", { + sessionId: SESSION_ID, + cwd: "", + mcpServers: "", + _meta: FX_HOOKS_SESSION_META, + }), + update(SESSION_ID, { + sessionUpdate: "user_message_chunk", + content: { type: "text", text: "Spawn a subagent to read package.json" }, + }), + subagentCall("call_earlier", "Read package.json"), + subagentPreview("call_earlier", earlierReply), + update(SESSION_ID, { + sessionUpdate: "agent_message_chunk", + content: { type: "text", text: "The subagent read package.json." }, + }), + answer("session/load", { + ...fxSession({ model: FX_DEFAULT_MODEL, modes: FX_MAIN_MODES }), + _meta: { fx: { hooksVersion: 1 } }, + }), + ...setConfig("mode", "ask", fxConfig({ model: FX_DEFAULT_MODEL, modes: FX_MAIN_MODES })), + outbound("session/prompt"), + subagentCall("call_next", "Read tsconfig.json"), + hook(SESSION_ID, "delegation", { + callId: "call_next", + childSessionId, + workId: "work-1", + operationId: null, + phase: "dispatched", + }), + hook(childSessionId, "tool.start", { + call: { + callId: "call_read", + name: "read_file", + provenance: "fx_local", + argumentsJson: '{"path":"tsconfig.json"}', + }, + rerun: false, + }), + hook(childSessionId, "tool.end", { + call: { + callId: "call_read", + name: "read_file", + provenance: "fx_local", + argumentsJson: '{"path":"tsconfig.json"}', + }, + output: '{"compilerOptions":{"target":"ES2023"}}', + status: "success", + error: null, + }), + hook(SESSION_ID, "delegation", { + callId: "call_next", + childSessionId, + workId: "work-1", + operationId: null, + phase: "completed", + }), + subagentPreview("call_next", reply), + hook(SESSION_ID, "tool.end", { + call: { callId: "call_next", name: "subagent", provenance: "model" }, + output: subagentReply(reply), + status: "success", + error: null, + }), + answer("session/prompt", { stopReason: "end_turn" }), + ], + }); + + const subagents = events.flatMap((event) => + event.type === "subagent.updated" ? [event.subagent] : [], + ); + assert.deepEqual( + [...new Set(subagents.map((subagent) => subagent.nativeTaskRef?.nativeId))], + ["call_next"], + "the reload projects nothing for the earlier sub-agent", + ); + assert.lengthOf( + events.filter((event) => event.type === "app_thread.created"), + 1, + ); + const settled = subagents.at(-1); + assert.equal(settled?.status, "completed"); + assert.equal(settled?.result, reply); + const childItems = events.flatMap((event) => + event.type === "turn_item.updated" && event.turnItem.threadId === settled?.childThreadId + ? [event.turnItem] + : [], + ); + assert.deepInclude(childItems.at(-1), { type: "assistant_message", text: reply }); + assert.isTrue( + childItems.some( + (item) => + item.type === "dynamic_tool" && + item.status === "completed" && + item.title === "Read tsconfig.json", + ), + ); + }).pipe(Effect.provide(testLayer), Effect.scoped), + ); }); diff --git a/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.testkit.ts b/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.testkit.ts index 2161c30a4f51..e41a12bf0dad 100644 --- a/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.testkit.ts +++ b/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.testkit.ts @@ -3,6 +3,7 @@ import { FxSettings } from "@t3tools/provider-fx/settings"; import { FX_ACP_CANCEL_BEHAVIOR, FX_DRIVER_KIND, + FX_HOOKS_SESSION_META, makeFxAdapterV2, } from "@t3tools/provider-fx/testing"; import { ProviderInstanceId } from "@t3tools/contracts"; @@ -64,6 +65,7 @@ function layerFxProviderAdapterRegistryReplay( childProcessSpawner, fileSystem, cancelBehavior: FX_ACP_CANCEL_BEHAVIOR, + sessionMeta: FX_HOOKS_SESSION_META, ...(replayGate === undefined ? {} : { replayGate }), })(runtimeInput), assertComplete: makeAcpReplayCompletenessAssertion(fileSystem, statusPath, transcript), diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/fx_early_stop/fx_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/fx_early_stop/fx_transcript.ndjson index 5c684b21bc32..f69de75a41da 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/fx_early_stop/fx_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/fx_early_stop/fx_transcript.ndjson @@ -1,7 +1,7 @@ {"type":"transcript_start","provider":"fx","protocol":"acp.ndjson-jsonrpc","version":"1","scenario":"fx_early_stop","metadata":{"generatedBy":"live-fx-recorder","fxVersion":"0.0.13","normalization":"Session ids are fx-session-NN, the workspace is , HOME is /home/fx-replay and the recording user is fx-replay. T3-owned prompt text, MCP servers and initialize params other than clientCapabilities and _meta are . User skill names, emails and token-shaped strings are removed. Timestamps are kept as recorded.","droppedFrames":0}} {"type":"expect_outbound","label":"request:initialize","frame":{"kind":"request","method":"initialize","params":{"protocolVersion":2,"info":"","capabilities":"","clientCapabilities":{"fs":{"readTextFile":false,"writeTextFile":false},"terminal":false,"session":{"notices":{}},"elicitation":{"form":{}}},"clientInfo":""}}} {"type":"emit_inbound","label":"response:initialize","frame":{"kind":"response","method":"initialize","result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"promptCapabilities":{"image":true,"audio":false,"embeddedContext":true},"mcpCapabilities":{"http":true,"sse":true,"acp":true},"sessionCapabilities":{"list":{},"resume":{},"close":{},"systemPrompt":{}},"_meta":{"fx":{"steering":true}}},"agentInfo":{"name":"fx","title":"fx","version":"0.0.13"},"authMethods":[]}}} -{"type":"expect_outbound","label":"request:session/new","frame":{"kind":"request","method":"session/new","params":{"cwd":"","mcpServers":""}}} +{"type":"expect_outbound","label":"request:session/new","frame":{"kind":"request","method":"session/new","params":{"cwd":"","mcpServers":"","_meta":{"fx":{"hooks":{"captureContent":true}}}}}} {"type":"emit_inbound","label":"response:session/new","frame":{"kind":"response","method":"session/new","result":{"sessionId":"fx-session-01","configOptions":[{"id":"provider","name":"Provider","category":"model","type":"select","currentValue":"gateway","options":[{"value":"gateway","name":"Vercel AI Gateway"},{"value":"codex","name":"Codex subscription"},{"value":"grok","name":"Grok subscription"}]},{"id":"model","name":"Model","category":"model","type":"select","currentValue":"anthropic/claude-opus-5.5","options":[{"value":"anthropic/claude-sonnet-5.5","name":"anthropic/claude-sonnet-5.5"},{"value":"anthropic/claude-opus-5.5","name":"anthropic/claude-opus-5.5"},{"value":"anthropic/claude-opus-5.5-fast","name":"anthropic/claude-opus-5.5-fast"},{"value":"anthropic/claude-opus-5","name":"anthropic/claude-opus-5"},{"value":"anthropic/claude-opus-5-fast","name":"anthropic/claude-opus-5-fast"},{"value":"anthropic/claude-sonnet-5","name":"anthropic/claude-sonnet-5"},{"value":"anthropic/claude-opus-4.8","name":"anthropic/claude-opus-4.8"},{"value":"anthropic/claude-opus-4.8-fast","name":"anthropic/claude-opus-4.8-fast"},{"value":"anthropic/claude-opus-4.7","name":"anthropic/claude-opus-4.7"},{"value":"anthropic/claude-sonnet-4.6","name":"anthropic/claude-sonnet-4.6"},{"value":"anthropic/claude-opus-4.6","name":"anthropic/claude-opus-4.6"},{"value":"anthropic/claude-opus-4.5","name":"anthropic/claude-opus-4.5"},{"value":"anthropic/claude-sonnet-4.5","name":"anthropic/claude-sonnet-4.5"},{"value":"anthropic/claude-opus-4","name":"anthropic/claude-opus-4"},{"value":"anthropic/claude-sonnet-4","name":"anthropic/claude-sonnet-4"},{"value":"openai/gpt-5.6-luna","name":"openai/gpt-5.6-luna"},{"value":"openai/gpt-5.6-luna-fast","name":"openai/gpt-5.6-luna-fast"},{"value":"openai/gpt-5.6-sol","name":"openai/gpt-5.6-sol"},{"value":"openai/gpt-5.6-sol-fast","name":"openai/gpt-5.6-sol-fast"},{"value":"openai/gpt-5.6-terra","name":"openai/gpt-5.6-terra"},{"value":"openai/gpt-5.6-terra-fast","name":"openai/gpt-5.6-terra-fast"},{"value":"openai/gpt-5.5","name":"openai/gpt-5.5"},{"value":"openai/gpt-5.5-fast","name":"openai/gpt-5.5-fast"},{"value":"openai/gpt-5.5-pro","name":"openai/gpt-5.5-pro"},{"value":"openai/gpt-5.4-nano","name":"openai/gpt-5.4-nano"},{"value":"openai/gpt-5.4","name":"openai/gpt-5.4"},{"value":"openai/gpt-5.4-fast","name":"openai/gpt-5.4-fast"},{"value":"openai/gpt-5.4-pro","name":"openai/gpt-5.4-pro"},{"value":"openai/gpt-5.3-codex","name":"openai/gpt-5.3-codex"},{"value":"openai/gpt-5.3-codex-fast","name":"openai/gpt-5.3-codex-fast"},{"value":"openai/gpt-5.2","name":"openai/gpt-5.2"},{"value":"openai/gpt-5.2-fast","name":"openai/gpt-5.2-fast"},{"value":"openai/gpt-5.2-pro","name":"openai/gpt-5.2-pro"},{"value":"openai/gpt-5.1-thinking","name":"openai/gpt-5.1-thinking"},{"value":"openai/gpt-5.1-thinking-fast","name":"openai/gpt-5.1-thinking-fast"},{"value":"openai/gpt-5-pro","name":"openai/gpt-5-pro"},{"value":"openai/gpt-5","name":"openai/gpt-5"},{"value":"openai/gpt-5-fast","name":"openai/gpt-5-fast"},{"value":"openai/gpt-5-nano","name":"openai/gpt-5-nano"},{"value":"openai/o3-pro","name":"openai/o3-pro"},{"value":"openai/o3","name":"openai/o3"},{"value":"openai/o3-fast","name":"openai/o3-fast"},{"value":"openai/o1","name":"openai/o1"},{"value":"deepseek/deepseek-v4-pro-0813","name":"deepseek/deepseek-v4-pro-0813"},{"value":"deepseek/deepseek-v4-pro","name":"deepseek/deepseek-v4-pro"},{"value":"spacexai/grok-4.7","name":"spacexai/grok-4.7"},{"value":"spacexai/grok-4.6","name":"spacexai/grok-4.6"},{"value":"spacexai/grok-4.5","name":"spacexai/grok-4.5"},{"value":"spacexai/grok-4.3","name":"spacexai/grok-4.3"},{"value":"spacexai/grok-4.20-multi-agent","name":"spacexai/grok-4.20-multi-agent"},{"value":"spacexai/grok-4.20-non-reasoning","name":"spacexai/grok-4.20-non-reasoning"},{"value":"spacexai/grok-4.20-reasoning","name":"spacexai/grok-4.20-reasoning"},{"value":"spacexai/grok-4.1-fast-non-reasoning","name":"spacexai/grok-4.1-fast-non-reasoning"},{"value":"spacexai/grok-4.1-fast-reasoning","name":"spacexai/grok-4.1-fast-reasoning"},{"value":"amazon/nova-pro","name":"amazon/nova-pro"},{"value":"anthropic/claude-fable-5.1","name":"anthropic/claude-fable-5.1"},{"value":"anthropic/claude-fable-5","name":"anthropic/claude-fable-5"},{"value":"openai/gpt-6.1-sol","name":"openai/gpt-6.1-sol"},{"value":"openai/gpt-6.1-sol-fast","name":"openai/gpt-6.1-sol-fast"},{"value":"openai/gpt-6-luna","name":"openai/gpt-6-luna"},{"value":"openai/gpt-6-luna-fast","name":"openai/gpt-6-luna-fast"},{"value":"openai/gpt-6-sol","name":"openai/gpt-6-sol"},{"value":"openai/gpt-6-sol-fast","name":"openai/gpt-6-sol-fast"},{"value":"openai/gpt-6-astra","name":"openai/gpt-6-astra"},{"value":"openai/gpt-6-astra-fast","name":"openai/gpt-6-astra-fast"},{"value":"openai/gpt-oss-safeguard-120b","name":"openai/gpt-oss-safeguard-120b"},{"value":"openai/gpt-oss-safeguard-20b","name":"openai/gpt-oss-safeguard-20b"},{"value":"openai/gpt-oss-120b","name":"openai/gpt-oss-120b"},{"value":"openai/gpt-oss-20b","name":"openai/gpt-oss-20b"},{"value":"openai/gpt-4.1","name":"openai/gpt-4.1"},{"value":"openai/gpt-4.1-fast","name":"openai/gpt-4.1-fast"},{"value":"openai/gpt-4.1-nano","name":"openai/gpt-4.1-nano"},{"value":"openai/gpt-4.1-nano-fast","name":"openai/gpt-4.1-nano-fast"},{"value":"openai/gpt-4o","name":"openai/gpt-4o"},{"value":"openai/gpt-4o-fast","name":"openai/gpt-4o-fast"},{"value":"openai/gpt-4-turbo","name":"openai/gpt-4-turbo"},{"value":"openai/gpt-3.5-turbo","name":"openai/gpt-3.5-turbo"},{"value":"google/gemma-4-26b-a4b-it","name":"google/gemma-4-26b-a4b-it"},{"value":"deepseek/deepseek-v3.2","name":"deepseek/deepseek-v3.2"},{"value":"deepseek/deepseek-v3.2-thinking","name":"deepseek/deepseek-v3.2-thinking"},{"value":"deepseek/deepseek-r1","name":"deepseek/deepseek-r1"},{"value":"meta/llama-4-maverick","name":"meta/llama-4-maverick"},{"value":"meta/llama-4-scout","name":"meta/llama-4-scout"},{"value":"meta/llama-3.3-70b","name":"meta/llama-3.3-70b"},{"value":"meta/llama-3.1-70b","name":"meta/llama-3.1-70b"},{"value":"meta/llama-3.1-8b","name":"meta/llama-3.1-8b"},{"value":"alibaba/qwen3.8-2.4t-a95b","name":"alibaba/qwen3.8-2.4t-a95b"},{"value":"alibaba/qwen3.8-max","name":"alibaba/qwen3.8-max"},{"value":"alibaba/qwen3-next-80b-a3b-instruct","name":"alibaba/qwen3-next-80b-a3b-instruct"},{"value":"alibaba/qwen3-next-80b-a3b-thinking","name":"alibaba/qwen3-next-80b-a3b-thinking"},{"value":"alibaba/qwen3-coder-30b-a3b","name":"alibaba/qwen3-coder-30b-a3b"},{"value":"alibaba/qwen3-coder","name":"alibaba/qwen3-coder"},{"value":"alibaba/qwen3-coder-next","name":"alibaba/qwen3-coder-next"},{"value":"alibaba/qwen-3-235b","name":"alibaba/qwen-3-235b"},{"value":"alibaba/qwen-3-32b","name":"alibaba/qwen-3-32b"},{"value":"fireworks/ember-1","name":"fireworks/ember-1"},{"value":"zai/glm-5.3-fast","name":"zai/glm-5.3-fast"},{"value":"zai/glm-5.3","name":"zai/glm-5.3"},{"value":"nvidia/nemotron-3.5-lightning","name":"nvidia/nemotron-3.5-lightning"},{"value":"moonshotai/kimi-k3-fast","name":"moonshotai/kimi-k3-fast"},{"value":"moonshotai/kimi-k3","name":"moonshotai/kimi-k3"},{"value":"zai/glm-5.2-fast","name":"zai/glm-5.2-fast"},{"value":"zai/glm-5.2","name":"zai/glm-5.2"},{"value":"nvidia/nemotron-3-ultra-550b-a55b","name":"nvidia/nemotron-3-ultra-550b-a55b"},{"value":"spacexai/grok-build-0.1","name":"spacexai/grok-build-0.1"},{"value":"nvidia/nemotron-3-super-120b-a12b","name":"nvidia/nemotron-3-super-120b-a12b"},{"value":"zai/glm-5","name":"zai/glm-5"},{"value":"moonshotai/kimi-k2.5","name":"moonshotai/kimi-k2.5"},{"value":"zai/glm-4.7","name":"zai/glm-4.7"},{"value":"nvidia/nemotron-nano-12b-v2-vl","name":"nvidia/nemotron-nano-12b-v2-vl"},{"value":"nvidia/nemotron-nano-9b-v2","name":"nvidia/nemotron-nano-9b-v2"},{"value":"amazon/nova-micro","name":"amazon/nova-micro"},{"value":"deepseek/deepseek-v4.1-flash","name":"deepseek/deepseek-v4.1-flash"},{"value":"deepseek/deepseek-v4-flash","name":"deepseek/deepseek-v4-flash"},{"value":"deepseek/deepseek-v4-flash-0731","name":"deepseek/deepseek-v4-flash-0731"},{"value":"zai/glm-5.3-flash","name":"zai/glm-5.3-flash"},{"value":"zai/glm-4.7-flash","name":"zai/glm-4.7-flash"},{"value":"anthropic/claude-haiku-5.5","name":"anthropic/claude-haiku-5.5"},{"value":"anthropic/claude-haiku-4.5","name":"anthropic/claude-haiku-4.5"},{"value":"anthropic/claude-3-haiku","name":"anthropic/claude-3-haiku"},{"value":"openai/gpt-5.4-mini","name":"openai/gpt-5.4-mini"},{"value":"openai/gpt-5.4-mini-fast","name":"openai/gpt-5.4-mini-fast"},{"value":"openai/gpt-5-mini","name":"openai/gpt-5-mini"},{"value":"openai/gpt-5-mini-fast","name":"openai/gpt-5-mini-fast"},{"value":"openai/gpt-4.1-mini","name":"openai/gpt-4.1-mini"},{"value":"openai/gpt-4.1-mini-fast","name":"openai/gpt-4.1-mini-fast"},{"value":"openai/o4-mini","name":"openai/o4-mini"},{"value":"openai/o4-mini-fast","name":"openai/o4-mini-fast"},{"value":"openai/o3-mini","name":"openai/o3-mini"},{"value":"openai/gpt-4o-mini","name":"openai/gpt-4o-mini"},{"value":"openai/gpt-4o-mini-fast","name":"openai/gpt-4o-mini-fast"},{"value":"google/gemini-3.8-flash","name":"google/gemini-3.8-flash"},{"value":"google/gemini-3.7-flash","name":"google/gemini-3.7-flash"},{"value":"google/gemini-3.5-flash-lite","name":"google/gemini-3.5-flash-lite"},{"value":"google/gemini-3.6-flash","name":"google/gemini-3.6-flash"},{"value":"google/gemini-3.5-flash","name":"google/gemini-3.5-flash"},{"value":"google/gemini-3.1-flash-lite","name":"google/gemini-3.1-flash-lite"},{"value":"google/gemini-3-flash","name":"google/gemini-3-flash"},{"value":"google/gemini-2.5-flash-lite","name":"google/gemini-2.5-flash-lite"},{"value":"google/gemini-2.5-flash","name":"google/gemini-2.5-flash"},{"value":"google/gemini-2.5-pro","name":"google/gemini-2.5-pro"},{"value":"minimax/minimax-m3","name":"minimax/minimax-m3"},{"value":"minimax/minimax-m2.5","name":"minimax/minimax-m2.5"},{"value":"minimax/minimax-m2.1","name":"minimax/minimax-m2.1"},{"value":"amazon/nova-2-lite","name":"amazon/nova-2-lite"},{"value":"amazon/nova-lite","name":"amazon/nova-lite"},{"value":"google/gemini-3.1-pro-preview","name":"google/gemini-3.1-pro-preview"},{"value":"spacexai/grok-4.20-multi-agent-beta","name":"spacexai/grok-4.20-multi-agent-beta"},{"value":"spacexai/grok-4.20-non-reasoning-beta","name":"spacexai/grok-4.20-non-reasoning-beta"},{"value":"spacexai/grok-4.20-reasoning-beta","name":"spacexai/grok-4.20-reasoning-beta"}]},{"id":"mode","name":"Session Mode","description":"Controls how the agent requests permission","category":"mode","type":"select","currentValue":"ask","options":[{"value":"code","name":"Code","description":"Write and modify code with full tool access","permissionMode":"auto"},{"value":"ask","name":"Ask","description":"Request permission before making any changes","permissionMode":"ask"}]},{"id":"effort","name":"Reasoning Effort","description":"Controls how much the model thinks before responding","category":"thought_level","type":"select","currentValue":"xhigh","options":[{"value":"auto","name":"default"},{"value":"low","name":"low"},{"value":"medium","name":"medium"},{"value":"high","name":"high"},{"value":"xhigh","name":"xhigh"},{"value":"max","name":"max"}]}],"modes":{"currentModeId":"ask","availableModes":[{"id":"code","name":"Code","description":"Write and modify code with full tool access"},{"id":"ask","name":"Ask","description":"Request permission before making any changes"}]}}}} {"type":"emit_inbound","label":"notification:session/update:available_commands_update","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"available_commands_update","availableCommands":[]}}}} {"type":"expect_outbound","label":"request:session/set_config_option","frame":{"kind":"request","method":"session/set_config_option","params":{"sessionId":"fx-session-01","configId":"effort","value":"medium"}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent/fx_output.ts b/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent/fx_output.ts index 8760ac7ffbec..7ce830263f66 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent/fx_output.ts +++ b/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent/fx_output.ts @@ -14,8 +14,9 @@ import { } from "../shared.ts"; /** - * fx reports each sub-agent as a `subagent` tool call and streams none of the - * child's frames, so each child thread holds the task and fx's reply. + * This recording's fx does not confirm hooks, so fx reports each sub-agent + * only as a `subagent` tool call and each child thread holds the task and + * fx's clipped reply. */ export function assertFxSubagentOutput( result: OrchestratorV2ScenarioResult, diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent/fx_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent/fx_transcript.ndjson index e2ddcf46159a..e72e1dd0ec0d 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent/fx_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent/fx_transcript.ndjson @@ -1,7 +1,7 @@ {"type":"transcript_start","provider":"fx","protocol":"acp.ndjson-jsonrpc","version":"1","scenario":"fx_subagent","metadata":{"generatedBy":"live-fx-recorder","fxVersion":"0.0.14","normalization":"Session ids are fx-session-NN, the workspace is , HOME is /home/fx-replay and the recording user is fx-replay. T3-owned prompt text, MCP servers and initialize params other than clientCapabilities and _meta are . User skill names, emails and token-shaped strings are removed. Timestamps are kept as recorded.","droppedFrames":0}} {"type":"expect_outbound","label":"request:initialize","frame":{"kind":"request","method":"initialize","params":{"protocolVersion":2,"info":"","capabilities":"","clientCapabilities":{"fs":{"readTextFile":false,"writeTextFile":false},"terminal":false,"session":{"notices":{}},"elicitation":{"form":{}}},"clientInfo":""}}} {"type":"emit_inbound","label":"response:initialize","frame":{"kind":"response","method":"initialize","result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"promptCapabilities":{"image":true,"audio":false,"embeddedContext":true},"mcpCapabilities":{"http":true,"sse":true,"acp":true},"sessionCapabilities":{"list":{},"resume":{},"close":{},"systemPrompt":{}},"_meta":{"fx":{"steering":true}}},"agentInfo":{"name":"fx","title":"fx","version":"0.0.14"},"authMethods":[]}}} -{"type":"expect_outbound","label":"request:session/new","frame":{"kind":"request","method":"session/new","params":{"cwd":"","mcpServers":""}}} +{"type":"expect_outbound","label":"request:session/new","frame":{"kind":"request","method":"session/new","params":{"cwd":"","mcpServers":"","_meta":{"fx":{"hooks":{"captureContent":true}}}}}} {"type":"emit_inbound","label":"response:session/new","frame":{"kind":"response","method":"session/new","result":{"sessionId":"fx-session-01","configOptions":[{"id":"provider","name":"Provider","category":"model","type":"select","currentValue":"gateway","options":[{"value":"gateway","name":"Vercel AI Gateway"},{"value":"codex","name":"Codex subscription"},{"value":"grok","name":"Grok subscription"}]},{"id":"model","name":"Model","category":"model","type":"select","currentValue":"openai/gpt-5.2","options":[{"value":"anthropic/claude-sonnet-5.5","name":"anthropic/claude-sonnet-5.5"},{"value":"anthropic/claude-opus-5.5","name":"anthropic/claude-opus-5.5"},{"value":"anthropic/claude-opus-5.5-fast","name":"anthropic/claude-opus-5.5-fast"},{"value":"anthropic/claude-opus-5","name":"anthropic/claude-opus-5"},{"value":"anthropic/claude-opus-5-fast","name":"anthropic/claude-opus-5-fast"},{"value":"anthropic/claude-sonnet-5","name":"anthropic/claude-sonnet-5"},{"value":"anthropic/claude-opus-4.8","name":"anthropic/claude-opus-4.8"},{"value":"anthropic/claude-opus-4.8-fast","name":"anthropic/claude-opus-4.8-fast"},{"value":"anthropic/claude-opus-4.7","name":"anthropic/claude-opus-4.7"},{"value":"anthropic/claude-sonnet-4.6","name":"anthropic/claude-sonnet-4.6"},{"value":"anthropic/claude-opus-4.6","name":"anthropic/claude-opus-4.6"},{"value":"anthropic/claude-opus-4.5","name":"anthropic/claude-opus-4.5"},{"value":"anthropic/claude-sonnet-4.5","name":"anthropic/claude-sonnet-4.5"},{"value":"openai/gpt-5.6-luna","name":"openai/gpt-5.6-luna"},{"value":"openai/gpt-5.6-luna-fast","name":"openai/gpt-5.6-luna-fast"},{"value":"openai/gpt-5.6-sol","name":"openai/gpt-5.6-sol"},{"value":"openai/gpt-5.6-sol-fast","name":"openai/gpt-5.6-sol-fast"},{"value":"openai/gpt-5.6-terra","name":"openai/gpt-5.6-terra"},{"value":"openai/gpt-5.6-terra-fast","name":"openai/gpt-5.6-terra-fast"},{"value":"openai/gpt-5.5","name":"openai/gpt-5.5"},{"value":"openai/gpt-5.5-fast","name":"openai/gpt-5.5-fast"},{"value":"openai/gpt-5.5-pro","name":"openai/gpt-5.5-pro"},{"value":"openai/gpt-5.4-nano","name":"openai/gpt-5.4-nano"},{"value":"openai/gpt-5.4","name":"openai/gpt-5.4"},{"value":"openai/gpt-5.4-fast","name":"openai/gpt-5.4-fast"},{"value":"openai/gpt-5.4-pro","name":"openai/gpt-5.4-pro"},{"value":"openai/gpt-5.3-codex","name":"openai/gpt-5.3-codex"},{"value":"openai/gpt-5.3-codex-fast","name":"openai/gpt-5.3-codex-fast"},{"value":"openai/gpt-5.2-codex","name":"openai/gpt-5.2-codex"},{"value":"openai/gpt-5.2","name":"openai/gpt-5.2"},{"value":"openai/gpt-5.2-fast","name":"openai/gpt-5.2-fast"},{"value":"openai/gpt-5.2-pro","name":"openai/gpt-5.2-pro"},{"value":"openai/gpt-5.1-codex-max","name":"openai/gpt-5.1-codex-max"},{"value":"openai/gpt-5.1-codex","name":"openai/gpt-5.1-codex"},{"value":"openai/gpt-5.1-thinking","name":"openai/gpt-5.1-thinking"},{"value":"openai/gpt-5.1-thinking-fast","name":"openai/gpt-5.1-thinking-fast"},{"value":"openai/gpt-5-pro","name":"openai/gpt-5-pro"},{"value":"openai/gpt-5-codex","name":"openai/gpt-5-codex"},{"value":"openai/gpt-5","name":"openai/gpt-5"},{"value":"openai/gpt-5-fast","name":"openai/gpt-5-fast"},{"value":"openai/gpt-5-nano","name":"openai/gpt-5-nano"},{"value":"openai/o3-pro","name":"openai/o3-pro"},{"value":"openai/o3","name":"openai/o3"},{"value":"openai/o3-fast","name":"openai/o3-fast"},{"value":"openai/o1","name":"openai/o1"},{"value":"deepseek/deepseek-v4-pro-0813","name":"deepseek/deepseek-v4-pro-0813"},{"value":"deepseek/deepseek-v4-pro","name":"deepseek/deepseek-v4-pro"},{"value":"moonshotai/kimi-k3.1-pro","name":"moonshotai/kimi-k3.1-pro"},{"value":"moonshotai/kimi-k3.1-pro-highspeed","name":"moonshotai/kimi-k3.1-pro-highspeed"},{"value":"spacexai/grok-4.7","name":"spacexai/grok-4.7"},{"value":"xiaomi/mimo-v2.6-pro","name":"xiaomi/mimo-v2.6-pro"},{"value":"xiaomi/mimo-v2.6-pro-ultraspeed","name":"xiaomi/mimo-v2.6-pro-ultraspeed"},{"value":"spacexai/grok-4.6","name":"spacexai/grok-4.6"},{"value":"spacexai/grok-4.5","name":"spacexai/grok-4.5"},{"value":"spacexai/grok-4.3","name":"spacexai/grok-4.3"},{"value":"xiaomi/mimo-v2.5-pro","name":"xiaomi/mimo-v2.5-pro"},{"value":"spacexai/grok-4.20-multi-agent","name":"spacexai/grok-4.20-multi-agent"},{"value":"spacexai/grok-4.20-non-reasoning","name":"spacexai/grok-4.20-non-reasoning"},{"value":"spacexai/grok-4.20-reasoning","name":"spacexai/grok-4.20-reasoning"},{"value":"amazon/nova-pro","name":"amazon/nova-pro"},{"value":"anthropic/claude-fable-5.1","name":"anthropic/claude-fable-5.1"},{"value":"anthropic/claude-fable-5","name":"anthropic/claude-fable-5"},{"value":"openai/gpt-6.1-terra","name":"openai/gpt-6.1-terra"},{"value":"openai/gpt-6.1-terra-fast","name":"openai/gpt-6.1-terra-fast"},{"value":"openai/gpt-6.1-sol","name":"openai/gpt-6.1-sol"},{"value":"openai/gpt-6.1-sol-fast","name":"openai/gpt-6.1-sol-fast"},{"value":"openai/gpt-6-luna","name":"openai/gpt-6-luna"},{"value":"openai/gpt-6-luna-fast","name":"openai/gpt-6-luna-fast"},{"value":"openai/gpt-6-sol","name":"openai/gpt-6-sol"},{"value":"openai/gpt-6-sol-fast","name":"openai/gpt-6-sol-fast"},{"value":"openai/gpt-6-astra","name":"openai/gpt-6-astra"},{"value":"openai/gpt-6-astra-fast","name":"openai/gpt-6-astra-fast"},{"value":"openai/gpt-oss-safeguard-120b","name":"openai/gpt-oss-safeguard-120b"},{"value":"openai/gpt-oss-safeguard-20b","name":"openai/gpt-oss-safeguard-20b"},{"value":"openai/gpt-oss-120b","name":"openai/gpt-oss-120b"},{"value":"openai/gpt-oss-20b","name":"openai/gpt-oss-20b"},{"value":"openai/gpt-4.1","name":"openai/gpt-4.1"},{"value":"openai/gpt-4.1-fast","name":"openai/gpt-4.1-fast"},{"value":"openai/gpt-4.1-nano","name":"openai/gpt-4.1-nano"},{"value":"openai/gpt-4o","name":"openai/gpt-4o"},{"value":"openai/gpt-4o-fast","name":"openai/gpt-4o-fast"},{"value":"google/gemma-4-26b-a4b-it","name":"google/gemma-4-26b-a4b-it"},{"value":"google/gemma-4-31b-it","name":"google/gemma-4-31b-it"},{"value":"deepseek/deepseek-v3.2","name":"deepseek/deepseek-v3.2"},{"value":"deepseek/deepseek-v3.2-thinking","name":"deepseek/deepseek-v3.2-thinking"},{"value":"deepseek/deepseek-v3.1","name":"deepseek/deepseek-v3.1"},{"value":"deepseek/deepseek-r1","name":"deepseek/deepseek-r1"},{"value":"meta/muse-spark-open","name":"meta/muse-spark-open"},{"value":"meta/muse-wonder-1.0","name":"meta/muse-wonder-1.0"},{"value":"meta/muse-spark-1.3","name":"meta/muse-spark-1.3"},{"value":"meta/muse-spark-1.3-contributor","name":"meta/muse-spark-1.3-contributor"},{"value":"meta/muse-glimmer-30b","name":"meta/muse-glimmer-30b"},{"value":"meta/muse-spark-1.2","name":"meta/muse-spark-1.2"},{"value":"meta/muse-spark-1.2-contributor","name":"meta/muse-spark-1.2-contributor"},{"value":"meta/muse-spark-1.1","name":"meta/muse-spark-1.1"},{"value":"meta/llama-4-maverick","name":"meta/llama-4-maverick"},{"value":"meta/llama-4-scout","name":"meta/llama-4-scout"},{"value":"meta/llama-3.3-70b","name":"meta/llama-3.3-70b"},{"value":"meta/llama-3.1-70b","name":"meta/llama-3.1-70b"},{"value":"meta/llama-3.1-8b","name":"meta/llama-3.1-8b"},{"value":"mistral/mistral-large-4","name":"mistral/mistral-large-4"},{"value":"mistral/mistral-medium-3.5","name":"mistral/mistral-medium-3.5"},{"value":"mistral/mistral-large-3","name":"mistral/mistral-large-3"},{"value":"mistral/mistral-small","name":"mistral/mistral-small"},{"value":"mistral/mistral-nemo","name":"mistral/mistral-nemo"},{"value":"mistral/codestral","name":"mistral/codestral"},{"value":"alibaba/qwen3.8-max-prime","name":"alibaba/qwen3.8-max-prime"},{"value":"alibaba/qwen3.8-max-0902","name":"alibaba/qwen3.8-max-0902"},{"value":"alibaba/qwen3.8-27b","name":"alibaba/qwen3.8-27b"},{"value":"alibaba/qwen3.8-2.4t-a95b","name":"alibaba/qwen3.8-2.4t-a95b"},{"value":"alibaba/qwen3.8-max","name":"alibaba/qwen3.8-max"},{"value":"alibaba/qwen3.7-plus","name":"alibaba/qwen3.7-plus"},{"value":"alibaba/qwen3.7-max","name":"alibaba/qwen3.7-max"},{"value":"alibaba/qwen3.6-27b","name":"alibaba/qwen3.6-27b"},{"value":"alibaba/qwen3.6-plus","name":"alibaba/qwen3.6-plus"},{"value":"alibaba/qwen3.5-plus","name":"alibaba/qwen3.5-plus"},{"value":"alibaba/qwen3-max-thinking","name":"alibaba/qwen3-max-thinking"},{"value":"alibaba/qwen3-vl-instruct","name":"alibaba/qwen3-vl-instruct"},{"value":"alibaba/qwen3-next-80b-a3b-instruct","name":"alibaba/qwen3-next-80b-a3b-instruct"},{"value":"alibaba/qwen3-next-80b-a3b-thinking","name":"alibaba/qwen3-next-80b-a3b-thinking"},{"value":"alibaba/qwen3-coder-30b-a3b","name":"alibaba/qwen3-coder-30b-a3b"},{"value":"alibaba/qwen3-coder","name":"alibaba/qwen3-coder"},{"value":"alibaba/qwen3-coder-next","name":"alibaba/qwen3-coder-next"},{"value":"alibaba/qwen-3-14b","name":"alibaba/qwen-3-14b"},{"value":"alibaba/qwen-3-235b","name":"alibaba/qwen-3-235b"},{"value":"alibaba/qwen-3-30b","name":"alibaba/qwen-3-30b"},{"value":"alibaba/qwen-3-32b","name":"alibaba/qwen-3-32b"},{"value":"inception/mercury-3","name":"inception/mercury-3"},{"value":"reflection/beam","name":"reflection/beam"},{"value":"reflection/beam-free","name":"reflection/beam-free"},{"value":"nvidia/nemotron-3.5-super-vl","name":"nvidia/nemotron-3.5-super-vl"},{"value":"nvidia/nemotron-3.5-lightning-free","name":"nvidia/nemotron-3.5-lightning-free"},{"value":"liquid/lfm2.5-24b-a2b","name":"liquid/lfm2.5-24b-a2b"},{"value":"callstack/apex","name":"callstack/apex"},{"value":"stealth/glyph-cluster","name":"stealth/glyph-cluster"},{"value":"fireworks/ember-1","name":"fireworks/ember-1"},{"value":"sakana/fugu-max","name":"sakana/fugu-max"},{"value":"sakana/fugu-ultra-v2","name":"sakana/fugu-ultra-v2"},{"value":"inception/mercury-2.5","name":"inception/mercury-2.5"},{"value":"zai/glm-5.3-fast","name":"zai/glm-5.3-fast"},{"value":"zai/glm-5.3","name":"zai/glm-5.3"},{"value":"mixedbread/toast-1","name":"mixedbread/toast-1"},{"value":"nvidia/nemotron-3.5-lightning","name":"nvidia/nemotron-3.5-lightning"},{"value":"sakana/namazu","name":"sakana/namazu"},{"value":"thinkingmachines/inkling-small","name":"thinkingmachines/inkling-small"},{"value":"moonshotai/kimi-k3-fast","name":"moonshotai/kimi-k3-fast"},{"value":"poolside/laguna-s-2.1-free","name":"poolside/laguna-s-2.1-free"},{"value":"poolside/laguna-s-2.1","name":"poolside/laguna-s-2.1"},{"value":"moonshotai/kimi-k3","name":"moonshotai/kimi-k3"},{"value":"thinkingmachines/inkling","name":"thinkingmachines/inkling"},{"value":"tencent/hy3","name":"tencent/hy3"},{"value":"bytedance/seed-2.1-turbo","name":"bytedance/seed-2.1-turbo"},{"value":"zai/glm-5.2-fast","name":"zai/glm-5.2-fast"},{"value":"sakana/fugu-ultra","name":"sakana/fugu-ultra"},{"value":"zai/glm-5.2","name":"zai/glm-5.2"},{"value":"moonshotai/kimi-k2.7-code-highspeed","name":"moonshotai/kimi-k2.7-code-highspeed"},{"value":"moonshotai/kimi-k2.7-code","name":"moonshotai/kimi-k2.7-code"},{"value":"nvidia/nemotron-3-ultra-550b-a55b","name":"nvidia/nemotron-3-ultra-550b-a55b"},{"value":"spacexai/grok-build-0.1","name":"spacexai/grok-build-0.1"},{"value":"xiaomi/mimo-v2.5","name":"xiaomi/mimo-v2.5"},{"value":"moonshotai/kimi-k2.6","name":"moonshotai/kimi-k2.6"},{"value":"zai/glm-5.1","name":"zai/glm-5.1"},{"value":"arcee-ai/trinity-large-thinking","name":"arcee-ai/trinity-large-thinking"},{"value":"zai/glm-5v-turbo","name":"zai/glm-5v-turbo"},{"value":"zai/glm-5-turbo","name":"zai/glm-5-turbo"},{"value":"nvidia/nemotron-3-super-120b-a12b","name":"nvidia/nemotron-3-super-120b-a12b"},{"value":"inception/mercury-2","name":"inception/mercury-2"},{"value":"zai/glm-5","name":"zai/glm-5"},{"value":"moonshotai/kimi-k2.5","name":"moonshotai/kimi-k2.5"},{"value":"zai/glm-4.7","name":"zai/glm-4.7"},{"value":"nvidia/nemotron-3-nano-30b-a3b","name":"nvidia/nemotron-3-nano-30b-a3b"},{"value":"nvidia/nemotron-nano-12b-v2-vl","name":"nvidia/nemotron-nano-12b-v2-vl"},{"value":"zai/glm-4.6","name":"zai/glm-4.6"},{"value":"bytedance/seed-1.6","name":"bytedance/seed-1.6"},{"value":"bytedance/seed-1.8","name":"bytedance/seed-1.8"},{"value":"nvidia/nemotron-nano-9b-v2","name":"nvidia/nemotron-nano-9b-v2"},{"value":"zai/glm-4.5v","name":"zai/glm-4.5v"},{"value":"zai/glm-4.5","name":"zai/glm-4.5"},{"value":"zai/glm-4.5-air","name":"zai/glm-4.5-air"},{"value":"moonshotai/kimi-k2","name":"moonshotai/kimi-k2"},{"value":"cohere/command-a","name":"cohere/command-a"},{"value":"inception/mercury-coder-small","name":"inception/mercury-coder-small"},{"value":"amazon/nova-micro","name":"amazon/nova-micro"},{"value":"deepseek/deepseek-v4.1-flash-1017","name":"deepseek/deepseek-v4.1-flash-1017"},{"value":"deepseek/deepseek-v4.1-flash-1017-fast","name":"deepseek/deepseek-v4.1-flash-1017-fast"},{"value":"deepseek/deepseek-v4.1-flash-fast","name":"deepseek/deepseek-v4.1-flash-fast"},{"value":"deepseek/deepseek-v4.1-flash","name":"deepseek/deepseek-v4.1-flash"},{"value":"deepseek/deepseek-v4-flash-vision-exp","name":"deepseek/deepseek-v4-flash-vision-exp"},{"value":"deepseek/deepseek-v4-flash","name":"deepseek/deepseek-v4-flash"},{"value":"deepseek/deepseek-v4-flash-0731","name":"deepseek/deepseek-v4-flash-0731"},{"value":"alibaba/qwen3.8-omni-flash","name":"alibaba/qwen3.8-omni-flash"},{"value":"alibaba/qwen3.8-flash","name":"alibaba/qwen3.8-flash"},{"value":"alibaba/qwen3.7-flash","name":"alibaba/qwen3.7-flash"},{"value":"alibaba/qwen3.5-flash","name":"alibaba/qwen3.5-flash"},{"value":"inclusionai/ling-3.1-flash","name":"inclusionai/ling-3.1-flash"},{"value":"xiaomi/mimo-v2.6-flash","name":"xiaomi/mimo-v2.6-flash"},{"value":"zai/glm-5.3-flashx","name":"zai/glm-5.3-flashx"},{"value":"inclusionai/ling-3.0-flash-vl","name":"inclusionai/ling-3.0-flash-vl"},{"value":"inclusionai/ling-3.0-flash-sante","name":"inclusionai/ling-3.0-flash-sante"},{"value":"inclusionai/ling-3.0-flash-fin","name":"inclusionai/ling-3.0-flash-fin"},{"value":"zai/glm-5.3-flash","name":"zai/glm-5.3-flash"},{"value":"inclusionai/ling-3.0-flash","name":"inclusionai/ling-3.0-flash"},{"value":"stepfun/step-3.7-flash","name":"stepfun/step-3.7-flash"},{"value":"zai/glm-4.7-flash","name":"zai/glm-4.7-flash"},{"value":"zai/glm-4.7-flashx","name":"zai/glm-4.7-flashx"},{"value":"anthropic/claude-haiku-5.5","name":"anthropic/claude-haiku-5.5"},{"value":"anthropic/claude-haiku-4.5","name":"anthropic/claude-haiku-4.5"},{"value":"openai/gpt-5.4-mini","name":"openai/gpt-5.4-mini"},{"value":"openai/gpt-5.4-mini-fast","name":"openai/gpt-5.4-mini-fast"},{"value":"openai/gpt-5.1-codex-mini","name":"openai/gpt-5.1-codex-mini"},{"value":"openai/gpt-5-mini","name":"openai/gpt-5-mini"},{"value":"openai/gpt-5-mini-fast","name":"openai/gpt-5-mini-fast"},{"value":"openai/gpt-4.1-mini","name":"openai/gpt-4.1-mini"},{"value":"openai/gpt-4.1-mini-fast","name":"openai/gpt-4.1-mini-fast"},{"value":"openai/o4-mini","name":"openai/o4-mini"},{"value":"openai/o3-mini","name":"openai/o3-mini"},{"value":"openai/gpt-4o-mini","name":"openai/gpt-4o-mini"},{"value":"openai/gpt-4o-mini-fast","name":"openai/gpt-4o-mini-fast"},{"value":"google/gemini-4-argon","name":"google/gemini-4-argon"},{"value":"google/gemini-3.8-flash","name":"google/gemini-3.8-flash"},{"value":"google/gemini-3.7-flash","name":"google/gemini-3.7-flash"},{"value":"google/gemini-3.5-flash-lite","name":"google/gemini-3.5-flash-lite"},{"value":"google/gemini-3.6-flash","name":"google/gemini-3.6-flash"},{"value":"google/gemini-3.5-flash","name":"google/gemini-3.5-flash"},{"value":"google/gemini-3.1-flash-lite","name":"google/gemini-3.1-flash-lite"},{"value":"google/gemini-3-flash","name":"google/gemini-3-flash"},{"value":"google/gemini-2.5-flash-lite","name":"google/gemini-2.5-flash-lite"},{"value":"google/gemini-2.5-flash","name":"google/gemini-2.5-flash"},{"value":"google/gemini-2.5-pro","name":"google/gemini-2.5-pro"},{"value":"mistral/ministral-14b","name":"mistral/ministral-14b"},{"value":"mistral/ministral-3b","name":"mistral/ministral-3b"},{"value":"mistral/ministral-8b","name":"mistral/ministral-8b"},{"value":"minimax/minimax-m3.1-flash","name":"minimax/minimax-m3.1-flash"},{"value":"minimax/minimax-m3","name":"minimax/minimax-m3"},{"value":"minimax/minimax-m2.7","name":"minimax/minimax-m2.7"},{"value":"minimax/minimax-m2.7-highspeed","name":"minimax/minimax-m2.7-highspeed"},{"value":"minimax/minimax-m2.5","name":"minimax/minimax-m2.5"},{"value":"minimax/minimax-m2.5-highspeed","name":"minimax/minimax-m2.5-highspeed"},{"value":"minimax/minimax-m2.1","name":"minimax/minimax-m2.1"},{"value":"minimax/minimax-m2.1-lightning","name":"minimax/minimax-m2.1-lightning"},{"value":"amazon/nova-2-lite","name":"amazon/nova-2-lite"},{"value":"minimax/minimax-m2","name":"minimax/minimax-m2"},{"value":"amazon/nova-lite","name":"amazon/nova-lite"},{"value":"google/gemini-3.1-pro-preview","name":"google/gemini-3.1-pro-preview"},{"value":"meituan/longcat-2.5-preview","name":"meituan/longcat-2.5-preview"},{"value":"stepfun/step-5-preview","name":"stepfun/step-5-preview"},{"value":"tencent/hy4-preview","name":"tencent/hy4-preview"},{"value":"spacexai/grok-4.20-multi-agent-beta","name":"spacexai/grok-4.20-multi-agent-beta"},{"value":"spacexai/grok-4.20-non-reasoning-beta","name":"spacexai/grok-4.20-non-reasoning-beta"},{"value":"spacexai/grok-4.20-reasoning-beta","name":"spacexai/grok-4.20-reasoning-beta"},{"value":"interfaze/interfaze-beta","name":"interfaze/interfaze-beta"},{"value":"tencent/hy-mt2-pro","name":"tencent/hy-mt2-pro"},{"value":"scaledown/scaledown-extract-1","name":"scaledown/scaledown-extract-1"},{"value":"scaledown/scaledown-summarize-1","name":"scaledown/scaledown-summarize-1"},{"value":"elorian/glimpse","name":"elorian/glimpse"},{"value":"tencent/hy-mt2-plus","name":"tencent/hy-mt2-plus"},{"value":"inference-net/schematron-v2-small","name":"inference-net/schematron-v2-small"},{"value":"inference-net/schematron-v2-turbo","name":"inference-net/schematron-v2-turbo"},{"value":"morph/morph-v3-fast","name":"morph/morph-v3-fast"},{"value":"morph/morph-v3-large","name":"morph/morph-v3-large"},{"value":"perplexity/sonar","name":"perplexity/sonar"},{"value":"tencent/hy-mt2-lite","name":"tencent/hy-mt2-lite"}]},{"id":"mode","name":"Session Mode","description":"Controls how the agent requests permission","category":"mode","type":"select","currentValue":"ask","options":[{"value":"auto","name":"Auto","description":"Check actions with an automatic safety review instead of asking","_meta":{"fx":{"permissionMode":"auto"}}},{"value":"ask","name":"Ask","description":"Request permission before making any changes","_meta":{"fx":{"permissionMode":"ask"}}},{"value":"full-access","name":"Full access","description":"Run every action without asking, outside the sandbox","_meta":{"fx":{"permissionMode":"yolo"}}}]},{"id":"effort","name":"Reasoning Effort","description":"Controls how much the model thinks before responding","category":"thought_level","type":"select","currentValue":"auto","options":[{"value":"auto","name":"default"},{"value":"none","name":"none"},{"value":"minimal","name":"minimal"},{"value":"low","name":"low"},{"value":"medium","name":"medium"},{"value":"high","name":"high"},{"value":"xhigh","name":"xhigh"}]},{"id":"fast","name":"Fast Mode","description":"Uses the model's fast lane","category":"model","type":"select","currentValue":"false","options":[{"value":"false","name":"off"},{"value":"true","name":"on"}]}],"modes":{"currentModeId":"ask","availableModes":[{"id":"auto","name":"Auto","description":"Check actions with an automatic safety review instead of asking"},{"id":"ask","name":"Ask","description":"Request permission before making any changes"},{"id":"full-access","name":"Full access","description":"Run every action without asking, outside the sandbox"}]}}}} {"type":"emit_inbound","label":"notification:session/update:available_commands_update","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"available_commands_update","availableCommands":[]}}}} {"type":"expect_outbound","label":"request:session/set_config_option","frame":{"kind":"request","method":"session/set_config_option","params":{"sessionId":"fx-session-01","configId":"effort","value":"medium"}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent_hooks/fx_output.ts b/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent_hooks/fx_output.ts new file mode 100644 index 000000000000..81617d839ee0 --- /dev/null +++ b/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent_hooks/fx_output.ts @@ -0,0 +1,68 @@ +import { assert } from "@effect/vitest"; +import type { ProviderReplayTranscript } from "@t3tools/contracts"; + +import type { OrchestratorV2ScenarioResult } from "../../OrchestratorScenario.ts"; +import { + assertBaseProjection, + assertNoExtraAppRunsForProviderChildren, + assertProviderNativeSubagentRootTurns, + assertSemanticProjectionIntegrity, + projectionFor, + SUBAGENT_PROMPT, + assertUserMessagesInclude, +} from "../shared.ts"; + +/** + * With fx's hooks each child thread shows the child's own steps and ends on + * its whole reply, which ACP alone clips to 200 bytes. + */ +export function assertFxSubagentHooksOutput( + result: OrchestratorV2ScenarioResult, + transcript: ProviderReplayTranscript, +) { + assertBaseProjection({ result, transcript, runCount: 1, runStatuses: ["completed"] }); + const projection = projectionFor(result, transcript.scenario); + assertSemanticProjectionIntegrity(projection); + assertNoExtraAppRunsForProviderChildren({ projection, expectedAppRuns: 1 }); + assertProviderNativeSubagentRootTurns(result); + assertUserMessagesInclude(projection, [SUBAGENT_PROMPT]); + assert.notInclude( + projection.turnItems.map((item) => item.type), + "dynamic_tool", + "the subagent calls must not also show as tool rows", + ); + + assert.lengthOf(projection.subagents, 2); + for (const subagent of projection.subagents) { + assert.equal(subagent.status, "completed"); + assert.isNotNull(subagent.providerThreadId, "the child's fx session is its provider thread"); + const reply = subagent.result ?? ""; + assert.isAbove(reply.length, 200, "the reply is fx's whole result, not its ACP preview"); + assert.notInclude(reply, '"ok":'); + assert.isFalse(reply.endsWith("…")); + if (subagent.childThreadId === null) throw new Error(`Subagent ${subagent.id} has no thread`); + + const child = result.projections.get(subagent.childThreadId); + assert.isDefined(child); + const items = child.turnItems.toSorted((left, right) => left.ordinal - right.ordinal); + assert.equal(items[0]?.type, "user_message"); + assertUserMessagesInclude(child, [subagent.prompt]); + const steps = items.filter((item) => item.type === "dynamic_tool"); + assert.isNotEmpty(steps, "the child's tool calls are its steps"); + for (const step of steps) { + assert.equal(step.status, "completed"); + assert.notEqual(step.title, "Tool", "a step keeps fx's tool and its input in its title"); + } + const last = items.at(-1); + assert.equal(last?.type === "assistant_message" ? last.text : null, reply); + assert.lengthOf( + items.filter((item) => item.type === "assistant_message" && item.text === reply), + 1, + "the reply shows once", + ); + assert.isFalse( + projection.turnItems.some((item) => item.type === "assistant_message" && item.text === reply), + "the child's reply leaked into the parent thread", + ); + } +} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent_hooks/fx_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent_hooks/fx_transcript.ndjson new file mode 100644 index 000000000000..a3da4225d37d --- /dev/null +++ b/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent_hooks/fx_transcript.ndjson @@ -0,0 +1,151 @@ +{"type":"transcript_start","provider":"fx","protocol":"acp.ndjson-jsonrpc","version":"1","scenario":"fx_subagent_hooks","metadata":{"generatedBy":"live-fx-recorder","fxVersion":"0.0.15","normalization":"Session ids are fx-session-NN, the workspace is , HOME is /home/fx-replay and the recording user is fx-replay. T3-owned prompt text, MCP servers and initialize params other than clientCapabilities and _meta are . User skill names, emails and token-shaped strings are removed. libfx/hook model.start events keep their ids and purpose but not their request content. Timestamps are kept as recorded.","droppedFrames":0}} +{"type":"expect_outbound","label":"request:initialize","frame":{"kind":"request","method":"initialize","params":{"protocolVersion":2,"info":"","capabilities":"","clientCapabilities":{"fs":{"readTextFile":false,"writeTextFile":false},"terminal":false,"session":{"notices":{}},"elicitation":{"form":{}}},"clientInfo":""}}} +{"type":"emit_inbound","label":"response:initialize","frame":{"kind":"response","method":"initialize","result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"promptCapabilities":{"image":true,"audio":false,"embeddedContext":true},"mcpCapabilities":{"http":true,"sse":false,"acp":false},"sessionCapabilities":{"list":{},"resume":{},"close":{},"systemPrompt":{}},"_meta":{"fx":{"steering":true}}},"agentInfo":{"name":"fx","title":"fx","version":"0.0.15"},"authMethods":[]}}} +{"type":"expect_outbound","label":"request:session/new","frame":{"kind":"request","method":"session/new","params":{"cwd":"","mcpServers":"","_meta":{"fx":{"hooks":{"captureContent":true}}}}}} +{"type":"emit_inbound","label":"response:session/new","frame":{"kind":"response","method":"session/new","result":{"sessionId":"fx-session-01","configOptions":[{"id":"provider","name":"Provider","category":"model","type":"select","currentValue":"codex","options":[{"value":"gateway","name":"Vercel AI Gateway"},{"value":"codex","name":"Codex subscription"},{"value":"grok","name":"Grok subscription"}]},{"id":"model","name":"Model","category":"model","type":"select","currentValue":"gpt-6.1-sol","options":[{"value":"gpt-6.1-sol","name":"gpt-6.1-sol"},{"value":"gpt-6-astra","name":"gpt-6-astra"},{"value":"gpt-6.1-terra","name":"gpt-6.1-terra"},{"value":"gpt-6-sol","name":"gpt-6-sol"},{"value":"gpt-6-luna","name":"gpt-6-luna"},{"value":"gpt-5.6-sol","name":"gpt-5.6-sol"},{"value":"gpt-5.6-terra","name":"gpt-5.6-terra"},{"value":"gpt-5.6-luna","name":"gpt-5.6-luna"},{"value":"gpt-daybreak-blue-latest","name":"gpt-daybreak-blue-latest"}]},{"id":"mode","name":"Session Mode","description":"Controls how the agent requests permission","category":"mode","type":"select","currentValue":"auto","options":[{"value":"auto","name":"Auto","description":"Check actions with an automatic safety review instead of asking","_meta":{"fx":{"permissionMode":"auto"}}},{"value":"ask","name":"Ask","description":"Request permission before making any changes","_meta":{"fx":{"permissionMode":"ask"}}},{"value":"full-access","name":"Full access","description":"Run every action without asking, outside the sandbox","_meta":{"fx":{"permissionMode":"yolo"}}}]},{"id":"effort","name":"Reasoning Effort","description":"Controls how much the model thinks before responding","category":"thought_level","type":"select","currentValue":"auto","options":[{"value":"auto","name":"default"},{"value":"low","name":"low"},{"value":"medium","name":"medium"},{"value":"high","name":"high"},{"value":"xhigh","name":"xhigh"},{"value":"max","name":"max"},{"value":"ultra","name":"ultra"}]},{"id":"fast","name":"Fast Mode","description":"Uses the model's fast lane","category":"model","type":"select","currentValue":"false","options":[{"value":"false","name":"off"},{"value":"true","name":"on"}]}],"modes":{"currentModeId":"auto","availableModes":[{"id":"auto","name":"Auto","description":"Check actions with an automatic safety review instead of asking"},{"id":"ask","name":"Ask","description":"Request permission before making any changes"},{"id":"full-access","name":"Full access","description":"Run every action without asking, outside the sandbox"}]},"_meta":{"fx":{"hooksVersion":1}}}}} +{"type":"emit_inbound","label":"notification:session/update:available_commands_update","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"available_commands_update","availableCommands":[]}}}} +{"type":"expect_outbound","label":"request:session/set_config_option","frame":{"kind":"request","method":"session/set_config_option","params":{"sessionId":"fx-session-01","configId":"effort","value":"medium"}}} +{"type":"emit_inbound","label":"response:session/set_config_option","frame":{"kind":"response","method":"session/set_config_option","result":{"configOptions":[{"id":"provider","name":"Provider","category":"model","type":"select","currentValue":"codex","options":[{"value":"gateway","name":"Vercel AI Gateway"},{"value":"codex","name":"Codex subscription"},{"value":"grok","name":"Grok subscription"}]},{"id":"model","name":"Model","category":"model","type":"select","currentValue":"gpt-6.1-sol","options":[{"value":"gpt-6.1-sol","name":"gpt-6.1-sol"},{"value":"gpt-6-astra","name":"gpt-6-astra"},{"value":"gpt-6.1-terra","name":"gpt-6.1-terra"},{"value":"gpt-6-sol","name":"gpt-6-sol"},{"value":"gpt-6-luna","name":"gpt-6-luna"},{"value":"gpt-5.6-sol","name":"gpt-5.6-sol"},{"value":"gpt-5.6-terra","name":"gpt-5.6-terra"},{"value":"gpt-5.6-luna","name":"gpt-5.6-luna"},{"value":"gpt-daybreak-blue-latest","name":"gpt-daybreak-blue-latest"}]},{"id":"mode","name":"Session Mode","description":"Controls how the agent requests permission","category":"mode","type":"select","currentValue":"auto","options":[{"value":"auto","name":"Auto","description":"Check actions with an automatic safety review instead of asking","_meta":{"fx":{"permissionMode":"auto"}}},{"value":"ask","name":"Ask","description":"Request permission before making any changes","_meta":{"fx":{"permissionMode":"ask"}}},{"value":"full-access","name":"Full access","description":"Run every action without asking, outside the sandbox","_meta":{"fx":{"permissionMode":"yolo"}}}]},{"id":"effort","name":"Reasoning Effort","description":"Controls how much the model thinks before responding","category":"thought_level","type":"select","currentValue":"medium","options":[{"value":"auto","name":"default"},{"value":"low","name":"low"},{"value":"medium","name":"medium"},{"value":"high","name":"high"},{"value":"xhigh","name":"xhigh"},{"value":"max","name":"max"},{"value":"ultra","name":"ultra"}]},{"id":"fast","name":"Fast Mode","description":"Uses the model's fast lane","category":"model","type":"select","currentValue":"false","options":[{"value":"false","name":"off"},{"value":"true","name":"on"}]}]}}} +{"type":"expect_outbound","label":"request:session/set_config_option","frame":{"kind":"request","method":"session/set_config_option","params":{"sessionId":"fx-session-01","configId":"mode","value":"auto"}}} +{"type":"emit_inbound","label":"response:session/set_config_option","frame":{"kind":"response","method":"session/set_config_option","result":{"configOptions":[{"id":"provider","name":"Provider","category":"model","type":"select","currentValue":"codex","options":[{"value":"gateway","name":"Vercel AI Gateway"},{"value":"codex","name":"Codex subscription"},{"value":"grok","name":"Grok subscription"}]},{"id":"model","name":"Model","category":"model","type":"select","currentValue":"gpt-6.1-sol","options":[{"value":"gpt-6.1-sol","name":"gpt-6.1-sol"},{"value":"gpt-6-astra","name":"gpt-6-astra"},{"value":"gpt-6.1-terra","name":"gpt-6.1-terra"},{"value":"gpt-6-sol","name":"gpt-6-sol"},{"value":"gpt-6-luna","name":"gpt-6-luna"},{"value":"gpt-5.6-sol","name":"gpt-5.6-sol"},{"value":"gpt-5.6-terra","name":"gpt-5.6-terra"},{"value":"gpt-5.6-luna","name":"gpt-5.6-luna"},{"value":"gpt-daybreak-blue-latest","name":"gpt-daybreak-blue-latest"}]},{"id":"mode","name":"Session Mode","description":"Controls how the agent requests permission","category":"mode","type":"select","currentValue":"auto","options":[{"value":"auto","name":"Auto","description":"Check actions with an automatic safety review instead of asking","_meta":{"fx":{"permissionMode":"auto"}}},{"value":"ask","name":"Ask","description":"Request permission before making any changes","_meta":{"fx":{"permissionMode":"ask"}}},{"value":"full-access","name":"Full access","description":"Run every action without asking, outside the sandbox","_meta":{"fx":{"permissionMode":"yolo"}}}]},{"id":"effort","name":"Reasoning Effort","description":"Controls how much the model thinks before responding","category":"thought_level","type":"select","currentValue":"medium","options":[{"value":"auto","name":"default"},{"value":"low","name":"low"},{"value":"medium","name":"medium"},{"value":"high","name":"high"},{"value":"xhigh","name":"xhigh"},{"value":"max","name":"max"},{"value":"ultra","name":"ultra"}]},{"id":"fast","name":"Fast Mode","description":"Uses the model's fast lane","category":"model","type":"select","currentValue":"false","options":[{"value":"false","name":"off"},{"value":"true","name":"on"}]}]}}} +{"type":"expect_outbound","label":"request:session/prompt","frame":{"kind":"request","method":"session/prompt","params":{"sessionId":"fx-session-01","prompt":[{"type":"text","text":"\n\n\n\n\nSpawn 2 subagents, one to read package.json and one to read tsconfig.json\n"}]}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":0,"timestamp":1793283383909,"type":"turn.start","data":{"resumed":false,"input":"\n## T3 Code interaction mode: Default\n\nPrefer making reasonable assumptions and carrying out the user's request. Ask a concise question only when a missing user decision would materially change the result. Treat this mode as active until T3 Code supplies a different interaction-mode instruction.\n\n\n\nSpawn 2 subagents, one to read package.json and one to read tsconfig.json\n\nIn case you're asked: you are running in T3 Code through the fx harness. No need to mention this otherwise. You can embed images, video, and audio in your response using Markdown image syntax with absolute file paths. Link any other file you produce using its absolute path so the user can open it in T3 Code's files viewer.\n\n\nLink each PR you create, take over, or push to: one link_pull_request per PR, full URL, every stack layer, as your next call. Host tools do not link for you. If linking fails, say so. Skip PRs you only read or review, and PRs named only as background. Call watch_pull_request when asked to monitor or babysit, or when work you were asked to do, such as a merge, still waits on checks or reviews, even after an earlier unwatch. Finish any needed branch update and push first, then watch and end your turn. Otherwise act now or hand back. Delegated children never watch, even if the brief says babysit. After PR work, a child's last line is: PR , head , checks and reviews . Links and watches persist across pushes. Never wait with sleep, loops, gh --watch, Monitor, or ScheduleWakeup. Before handback, call unwatch_pull_request on any PR you watch.\n"},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":0,"timestamp":1793283383909,"type":"model.start","data":{"modelCallId":1,"parentCallId":null,"purpose":"title","model":"gpt-5.6-luna","toolChoice":"none","maxOutputTokens":128,"messageCount":1},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":0,"timestamp":1793283383909,"type":"model.admitted","data":{"modelCallId":1},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":1,"timestamp":1793283383909,"type":"step.start","data":{},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":1,"timestamp":1793283383910,"type":"model.start","data":{"modelCallId":2,"parentCallId":null,"purpose":"agent","model":"gpt-6.1-sol","toolChoice":"auto","maxOutputTokens":null,"messageCount":1},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":1,"timestamp":1793283383910,"type":"model.admitted","data":{"modelCallId":2},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":0,"timestamp":1793283385251,"type":"model.output_start","data":{"modelCallId":1},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":0,"timestamp":1793283385387,"type":"model.end","data":{"modelCallId":1,"error":null,"cancelled":false,"output":"Spawn Subagents to Inspect Project Configuration","finishReason":"stop","generationId":"resp_0a258f2214001438016ae35538737881938709471d4d20a38a","provider":null,"usage":{"inputTokens":451,"outputTokens":33,"cacheReadTokens":0,"cacheWriteTokens":0,"reasoningTokens":20},"toolCalls":[]},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":1,"timestamp":1793283385687,"type":"model.output_start","data":{"modelCallId":2},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":"I’ll spawn"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":" two"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":" sub"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":"agents"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":" in"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":" parallel"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":":"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":" one"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":" to"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":" read"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":" `"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":"package"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":".json"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":"`"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":" and"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":" one"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":" to"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":" read"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":" `"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":"ts"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":"config"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":".json"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"70761e484be6eb192877b1aef48fcfde","content":{"type":"text","text":"`.\n"}}}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":1,"timestamp":1793283387861,"type":"model.end","data":{"modelCallId":2,"error":null,"cancelled":false,"output":"I’ll spawn two subagents in parallel: one to read `package.json` and one to read `tsconfig.json`.\n","finishReason":"tool-calls","generationId":"resp_0450a6f19efae5e3016ae355385d848195ac92a3c255498997","provider":null,"usage":{"inputTokens":6385,"outputTokens":145,"cacheReadTokens":0,"cacheWriteTokens":0,"reasoningTokens":0},"toolCalls":[{"callId":"call_a00d8b57af314969bc93f183db9c24f2","name":"subagent","provenance":"fx_local","argumentsJson":"{\"action\": \"run\", \"task\": \"Read package.json in the current workspace root using read-only tools and summarize its contents briefly. Do not modify files. If absent, report that.\"}"},{"callId":"call_1fd5e84bf47448f88898e60db68c47d1","name":"subagent","provenance":"fx_local","argumentsJson":"{\"action\": \"run\", \"task\": \"Read tsconfig.json in the current workspace root using read-only tools and summarize its contents briefly. Do not modify files. If absent, report that.\"}"}]},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:session/update:tool_call","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"tool_call","toolCallId":"call_a00d8b57af314969bc93f183db9c24f2","name":"subagent","title":"Subagent · Read package.json in the current workspace root using read-only tools and summarize its contents briefly. Do not modi...","kind":"other","status":"pending","rawInput":{"action":"run","task":"Read package.json in the current workspace root using read-only tools and summarize its contents briefly. Do not modify files. If absent, report that."},"_meta":{"fx":{"toolCall":{"internal":false}}}}}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":1,"timestamp":1793283387861,"type":"permission.start","data":{"call":{"callId":"call_a00d8b57af314969bc93f183db9c24f2","name":"subagent","provenance":"fx_local","argumentsJson":"{\"action\": \"run\", \"task\": \"Read package.json in the current workspace root using read-only tools and summarize its contents briefly. Do not modify files. If absent, report that.\"}"}},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":1,"timestamp":1793283387861,"type":"permission.end","data":{"call":{"callId":"call_a00d8b57af314969bc93f183db9c24f2","name":"subagent","provenance":"fx_local","argumentsJson":"{\"action\": \"run\", \"task\": \"Read package.json in the current workspace root using read-only tools and summarize its contents briefly. Do not modify files. If absent, report that.\"}"},"decision":"once","error":null},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:session/update:tool_call","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"tool_call","toolCallId":"call_1fd5e84bf47448f88898e60db68c47d1","name":"subagent","title":"Subagent · Read tsconfig.json in the current workspace root using read-only tools and summarize its contents briefly. Do not mod...","kind":"other","status":"pending","rawInput":{"action":"run","task":"Read tsconfig.json in the current workspace root using read-only tools and summarize its contents briefly. Do not modify files. If absent, report that."},"_meta":{"fx":{"toolCall":{"internal":false}}}}}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":1,"timestamp":1793283387861,"type":"permission.start","data":{"call":{"callId":"call_1fd5e84bf47448f88898e60db68c47d1","name":"subagent","provenance":"fx_local","argumentsJson":"{\"action\": \"run\", \"task\": \"Read tsconfig.json in the current workspace root using read-only tools and summarize its contents briefly. Do not modify files. If absent, report that.\"}"}},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":1,"timestamp":1793283387861,"type":"permission.end","data":{"call":{"callId":"call_1fd5e84bf47448f88898e60db68c47d1","name":"subagent","provenance":"fx_local","argumentsJson":"{\"action\": \"run\", \"task\": \"Read tsconfig.json in the current workspace root using read-only tools and summarize its contents briefly. Do not modify files. If absent, report that.\"}"},"decision":"once","error":null},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":1,"timestamp":1793283387861,"type":"tool.start","data":{"call":{"callId":"call_a00d8b57af314969bc93f183db9c24f2","name":"subagent","provenance":"fx_local","argumentsJson":"{\"action\": \"run\", \"task\": \"Read package.json in the current workspace root using read-only tools and summarize its contents briefly. Do not modify files. If absent, report that.\"}"},"rerun":false},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":1,"timestamp":1793283387861,"type":"tool.start","data":{"call":{"callId":"call_1fd5e84bf47448f88898e60db68c47d1","name":"subagent","provenance":"fx_local","argumentsJson":"{\"action\": \"run\", \"task\": \"Read tsconfig.json in the current workspace root using read-only tools and summarize its contents briefly. Do not modify files. If absent, report that.\"}"},"rerun":false},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:session/update:tool_call_update","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"tool_call_update","toolCallId":"call_a00d8b57af314969bc93f183db9c24f2","status":"in_progress"}}}} +{"type":"emit_inbound","label":"notification:session/update:tool_call_update","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"tool_call_update","toolCallId":"call_1fd5e84bf47448f88898e60db68c47d1","status":"in_progress"}}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":1,"timestamp":1793283387862,"type":"delegation","data":{"callId":"call_1fd5e84bf47448f88898e60db68c47d1","childSessionId":"fx-session-02","workId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","operationId":null,"phase":"dispatched"},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":1,"timestamp":1793283387862,"type":"delegation","data":{"callId":"call_a00d8b57af314969bc93f183db9c24f2","childSessionId":"fx-session-03","workId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","operationId":null,"phase":"dispatched"},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-02","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","turnId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","executionId":"fd7335dfad27c303713f20e1d2e7700b","stepIndex":0,"timestamp":1793283387863,"type":"turn.start","data":{"resumed":false,"input":"Read tsconfig.json in the current workspace root using read-only tools and summarize its contents briefly. Do not modify files. If absent, report that."},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-02","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","turnId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","executionId":"fd7335dfad27c303713f20e1d2e7700b","stepIndex":1,"timestamp":1793283387863,"type":"step.start","data":{},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-03","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","turnId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","executionId":"c57470c13048676a68d94dc7ddd41ef9","stepIndex":0,"timestamp":1793283387863,"type":"turn.start","data":{"resumed":false,"input":"Read package.json in the current workspace root using read-only tools and summarize its contents briefly. Do not modify files. If absent, report that."},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-03","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","turnId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","executionId":"c57470c13048676a68d94dc7ddd41ef9","stepIndex":1,"timestamp":1793283387863,"type":"step.start","data":{},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-02","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","turnId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","executionId":"fd7335dfad27c303713f20e1d2e7700b","stepIndex":1,"timestamp":1793283387864,"type":"model.start","data":{"modelCallId":1,"parentCallId":null,"purpose":"agent","model":"gpt-6.1-sol","toolChoice":"auto","maxOutputTokens":null,"messageCount":1},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-03","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","turnId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","executionId":"c57470c13048676a68d94dc7ddd41ef9","stepIndex":1,"timestamp":1793283387864,"type":"model.start","data":{"modelCallId":1,"parentCallId":null,"purpose":"agent","model":"gpt-6.1-sol","toolChoice":"auto","maxOutputTokens":null,"messageCount":1},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-02","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","turnId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","executionId":"fd7335dfad27c303713f20e1d2e7700b","stepIndex":1,"timestamp":1793283387864,"type":"model.admitted","data":{"modelCallId":1},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-03","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","turnId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","executionId":"c57470c13048676a68d94dc7ddd41ef9","stepIndex":1,"timestamp":1793283387864,"type":"model.admitted","data":{"modelCallId":1},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-02","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","turnId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","executionId":"fd7335dfad27c303713f20e1d2e7700b","stepIndex":1,"timestamp":1793283389421,"type":"model.output_start","data":{"modelCallId":1},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-02","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","turnId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","executionId":"fd7335dfad27c303713f20e1d2e7700b","stepIndex":1,"timestamp":1793283390167,"type":"model.end","data":{"modelCallId":1,"error":null,"cancelled":false,"output":"I’ll read the root `tsconfig.json` and briefly summarize it without changing any files.\n","finishReason":"tool-calls","generationId":"resp_06971308f539a4a9016ae3553c3d0c8196b597d24f3e45eb8b","provider":null,"usage":{"inputTokens":5470,"outputTokens":46,"cacheReadTokens":0,"cacheWriteTokens":0,"reasoningTokens":0},"toolCalls":[{"callId":"call_4b29d3b94a4c4fd9be7bffa7c9e272ff","name":"read_file","provenance":"fx_local","argumentsJson":"{\"path\":\"tsconfig.json\"}"}]},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-02","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","turnId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","executionId":"fd7335dfad27c303713f20e1d2e7700b","stepIndex":1,"timestamp":1793283390167,"type":"permission.start","data":{"call":{"callId":"call_4b29d3b94a4c4fd9be7bffa7c9e272ff","name":"read_file","provenance":"fx_local","argumentsJson":"{\"path\":\"tsconfig.json\"}"}},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-02","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","turnId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","executionId":"fd7335dfad27c303713f20e1d2e7700b","stepIndex":1,"timestamp":1793283390167,"type":"permission.end","data":{"call":{"callId":"call_4b29d3b94a4c4fd9be7bffa7c9e272ff","name":"read_file","provenance":"fx_local","argumentsJson":"{\"path\":\"tsconfig.json\"}"},"decision":"once","error":null},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-02","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","turnId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","executionId":"fd7335dfad27c303713f20e1d2e7700b","stepIndex":1,"timestamp":1793283390167,"type":"tool.start","data":{"call":{"callId":"call_4b29d3b94a4c4fd9be7bffa7c9e272ff","name":"read_file","provenance":"fx_local","argumentsJson":"{\"path\":\"tsconfig.json\"}"},"rerun":false},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-02","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","turnId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","executionId":"fd7335dfad27c303713f20e1d2e7700b","stepIndex":1,"timestamp":1793283390168,"type":"tool.end","data":{"call":{"callId":"call_4b29d3b94a4c4fd9be7bffa7c9e272ff","name":"read_file","provenance":"fx_local","argumentsJson":"{\"path\":\"tsconfig.json\"}"},"output":"tsconfig.json\n\n1 \t{\n2 \t \"compilerOptions\": {\n3 \t \"target\": \"ES2023\",\n4 \t \"module\": \"NodeNext\",\n5 \t \"moduleResolution\": \"NodeNext\",\n6 \t \"strict\": true,\n7 \t \"noUncheckedIndexedAccess\": true,\n8 \t \"exactOptionalPropertyTypes\": true,\n9 \t \"jsx\": \"react-jsx\",\n10\t \"outDir\": \"dist\"\n11\t },\n12\t \"include\": [\n13\t \"src\"\n14\t ]\n15\t}\n","status":"success","error":null},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-02","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","turnId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","executionId":"fd7335dfad27c303713f20e1d2e7700b","stepIndex":1,"timestamp":1793283390168,"type":"tool.result","data":{"call":{"callId":"call_4b29d3b94a4c4fd9be7bffa7c9e272ff","name":"read_file","provenance":"fx_local","argumentsJson":"{\"path\":\"tsconfig.json\"}"},"output":"tsconfig.json\n\n1 \t{\n2 \t \"compilerOptions\": {\n3 \t \"target\": \"ES2023\",\n4 \t \"module\": \"NodeNext\",\n5 \t \"moduleResolution\": \"NodeNext\",\n6 \t \"strict\": true,\n7 \t \"noUncheckedIndexedAccess\": true,\n8 \t \"exactOptionalPropertyTypes\": true,\n9 \t \"jsx\": \"react-jsx\",\n10\t \"outDir\": \"dist\"\n11\t },\n12\t \"include\": [\n13\t \"src\"\n14\t ]\n15\t}\n","status":"success"},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-02","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","turnId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","executionId":"fd7335dfad27c303713f20e1d2e7700b","stepIndex":1,"timestamp":1793283390168,"type":"step.end","data":{},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-02","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","turnId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","executionId":"fd7335dfad27c303713f20e1d2e7700b","stepIndex":2,"timestamp":1793283390168,"type":"step.start","data":{},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-02","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","turnId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","executionId":"fd7335dfad27c303713f20e1d2e7700b","stepIndex":2,"timestamp":1793283390169,"type":"model.start","data":{"modelCallId":2,"parentCallId":null,"purpose":"agent","model":"gpt-6.1-sol","toolChoice":"auto","maxOutputTokens":null,"messageCount":3},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-02","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","turnId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","executionId":"fd7335dfad27c303713f20e1d2e7700b","stepIndex":2,"timestamp":1793283390169,"type":"model.admitted","data":{"modelCallId":2},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-03","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","turnId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","executionId":"c57470c13048676a68d94dc7ddd41ef9","stepIndex":1,"timestamp":1793283391340,"type":"model.output_start","data":{"modelCallId":1},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-03","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","turnId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","executionId":"c57470c13048676a68d94dc7ddd41ef9","stepIndex":1,"timestamp":1793283391868,"type":"model.end","data":{"modelCallId":1,"error":null,"cancelled":false,"output":"I’ll read the root `package.json` and briefly summarize it without changing any files.","finishReason":"tool-calls","generationId":"resp_0206327b1cb48797016ae3553c40c88197a9c83d911731dbc6","provider":null,"usage":{"inputTokens":5469,"outputTokens":44,"cacheReadTokens":0,"cacheWriteTokens":0,"reasoningTokens":0},"toolCalls":[{"callId":"call_9bbc5173f44c434db9413ddfc45dd91f","name":"read_file","provenance":"fx_local","argumentsJson":"{\"path\":\"package.json\"}"}]},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-03","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","turnId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","executionId":"c57470c13048676a68d94dc7ddd41ef9","stepIndex":1,"timestamp":1793283391868,"type":"permission.start","data":{"call":{"callId":"call_9bbc5173f44c434db9413ddfc45dd91f","name":"read_file","provenance":"fx_local","argumentsJson":"{\"path\":\"package.json\"}"}},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-03","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","turnId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","executionId":"c57470c13048676a68d94dc7ddd41ef9","stepIndex":1,"timestamp":1793283391868,"type":"permission.end","data":{"call":{"callId":"call_9bbc5173f44c434db9413ddfc45dd91f","name":"read_file","provenance":"fx_local","argumentsJson":"{\"path\":\"package.json\"}"},"decision":"once","error":null},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-03","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","turnId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","executionId":"c57470c13048676a68d94dc7ddd41ef9","stepIndex":1,"timestamp":1793283391868,"type":"tool.start","data":{"call":{"callId":"call_9bbc5173f44c434db9413ddfc45dd91f","name":"read_file","provenance":"fx_local","argumentsJson":"{\"path\":\"package.json\"}"},"rerun":false},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-03","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","turnId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","executionId":"c57470c13048676a68d94dc7ddd41ef9","stepIndex":1,"timestamp":1793283391868,"type":"tool.end","data":{"call":{"callId":"call_9bbc5173f44c434db9413ddfc45dd91f","name":"read_file","provenance":"fx_local","argumentsJson":"{\"path\":\"package.json\"}"},"output":"package.json\n\n1 \t{\n2 \t \"name\": \"fx-hooks-fixture\",\n3 \t \"version\": \"1.4.0\",\n4 \t \"type\": \"module\",\n5 \t \"packageManager\": \"pnpm@10.12.0\",\n6 \t \"engines\": {\n7 \t \"node\": \">=22\"\n8 \t },\n9 \t \"scripts\": {\n10\t \"build\": \"tsc -p tsconfig.json\",\n11\t \"dev\": \"vite\",\n12\t \"test\": \"vitest run\",\n13\t \"lint\": \"oxlint .\",\n14\t \"typecheck\": \"tsc --noEmit\"\n15\t },\n16\t \"dependencies\": {\n17\t \"effect\": \"^4.0.0\",\n18\t \"react\": \"^19.1.0\"\n19\t },\n20\t \"devDependencies\": {\n21\t \"typescript\": \"^5.9.0\",\n22\t \"vite\": \"^7.0.0\",\n23\t \"vitest\": \"^4.0.0\"\n24\t }\n25\t}\n","status":"success","error":null},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-03","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","turnId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","executionId":"c57470c13048676a68d94dc7ddd41ef9","stepIndex":1,"timestamp":1793283391868,"type":"tool.result","data":{"call":{"callId":"call_9bbc5173f44c434db9413ddfc45dd91f","name":"read_file","provenance":"fx_local","argumentsJson":"{\"path\":\"package.json\"}"},"output":"package.json\n\n1 \t{\n2 \t \"name\": \"fx-hooks-fixture\",\n3 \t \"version\": \"1.4.0\",\n4 \t \"type\": \"module\",\n5 \t \"packageManager\": \"pnpm@10.12.0\",\n6 \t \"engines\": {\n7 \t \"node\": \">=22\"\n8 \t },\n9 \t \"scripts\": {\n10\t \"build\": \"tsc -p tsconfig.json\",\n11\t \"dev\": \"vite\",\n12\t \"test\": \"vitest run\",\n13\t \"lint\": \"oxlint .\",\n14\t \"typecheck\": \"tsc --noEmit\"\n15\t },\n16\t \"dependencies\": {\n17\t \"effect\": \"^4.0.0\",\n18\t \"react\": \"^19.1.0\"\n19\t },\n20\t \"devDependencies\": {\n21\t \"typescript\": \"^5.9.0\",\n22\t \"vite\": \"^7.0.0\",\n23\t \"vitest\": \"^4.0.0\"\n24\t }\n25\t}\n","status":"success"},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-03","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","turnId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","executionId":"c57470c13048676a68d94dc7ddd41ef9","stepIndex":1,"timestamp":1793283391868,"type":"step.end","data":{},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-03","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","turnId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","executionId":"c57470c13048676a68d94dc7ddd41ef9","stepIndex":2,"timestamp":1793283391868,"type":"step.start","data":{},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-03","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","turnId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","executionId":"c57470c13048676a68d94dc7ddd41ef9","stepIndex":2,"timestamp":1793283391869,"type":"model.start","data":{"modelCallId":2,"parentCallId":null,"purpose":"agent","model":"gpt-6.1-sol","toolChoice":"auto","maxOutputTokens":null,"messageCount":3},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-03","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","turnId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","executionId":"c57470c13048676a68d94dc7ddd41ef9","stepIndex":2,"timestamp":1793283391869,"type":"model.admitted","data":{"modelCallId":2},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-02","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","turnId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","executionId":"fd7335dfad27c303713f20e1d2e7700b","stepIndex":2,"timestamp":1793283392967,"type":"model.output_start","data":{"modelCallId":2},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-02","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","turnId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","executionId":"fd7335dfad27c303713f20e1d2e7700b","stepIndex":2,"timestamp":1793283395103,"type":"model.end","data":{"modelCallId":2,"error":null,"cancelled":false,"output":"`tsconfig.json` targets ES2023 with NodeNext modules and resolution. It enables strict checking, unchecked-index checks, and exact optional-property types; uses React’s automatic JSX runtime; compiles `src`; and outputs to `dist`.\n\nNo files were modified.","finishReason":"stop","generationId":"resp_0ea984999f917f7b016ae3553ef9a08194b417263eda2bdf27","provider":null,"usage":{"inputTokens":5655,"outputTokens":58,"cacheReadTokens":0,"cacheWriteTokens":0,"reasoningTokens":0},"toolCalls":[]},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-02","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","turnId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","executionId":"fd7335dfad27c303713f20e1d2e7700b","stepIndex":2,"timestamp":1793283395107,"type":"step.end","data":{},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-02","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","turnId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","executionId":"fd7335dfad27c303713f20e1d2e7700b","stepIndex":0,"timestamp":1793283395107,"type":"turn.end","data":{"outcome":"completed","error":null},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":1,"timestamp":1793283395111,"type":"delegation","data":{"callId":"call_1fd5e84bf47448f88898e60db68c47d1","childSessionId":"fx-session-02","workId":"fxop:2:m:248014933974423831:8105f5487489d8acbbfdb306a805c46ae6c9352a8911e42568304e1bbc931e2b","operationId":null,"phase":"completed"},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:session/update:tool_call_update","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"tool_call_update","toolCallId":"call_1fd5e84bf47448f88898e60db68c47d1","status":"completed","content":[{"type":"content","content":{"type":"text","text":"{\"ok\":true,\"result\":\"`tsconfig.json` targets ES2023 with NodeNext modules and resolution. It enables strict checking, unchecked-index checks, and exact optional-property types; uses React’s automati"}}]}}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":1,"timestamp":1793283395111,"type":"tool.end","data":{"call":{"callId":"call_1fd5e84bf47448f88898e60db68c47d1","name":"subagent","provenance":"fx_local","argumentsJson":"{\"action\": \"run\", \"task\": \"Read tsconfig.json in the current workspace root using read-only tools and summarize its contents briefly. Do not modify files. If absent, report that.\"}"},"output":"{\"ok\":true,\"result\":\"`tsconfig.json` targets ES2023 with NodeNext modules and resolution. It enables strict checking, unchecked-index checks, and exact optional-property types; uses React’s automatic JSX runtime; compiles `src`; and outputs to `dist`.\\n\\nNo files were modified.\",\"error_code\":null}","status":"success","error":null},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-03","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","turnId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","executionId":"c57470c13048676a68d94dc7ddd41ef9","stepIndex":2,"timestamp":1793283395290,"type":"model.output_start","data":{"modelCallId":2},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-03","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","turnId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","executionId":"c57470c13048676a68d94dc7ddd41ef9","stepIndex":2,"timestamp":1793283397454,"type":"model.end","data":{"modelCallId":2,"error":null,"cancelled":false,"output":"`package.json` defines:\n\n- **Package:** `fx-hooks-fixture` v1.4.0, using ES modules.\n- **Runtime:** Node.js ≥22; pnpm 10.12.0.\n- **Scripts:** TypeScript build/typecheck, Vite dev server, Vitest tests, and oxlint linting.\n- **Dependencies:** Effect ^4.0.0 and React ^19.1.0.\n- **Dev dependencies:** TypeScript ^5.9.0, Vite ^7.0.0, and Vitest ^4.0.0.\n\nNo files were modified.","finishReason":"stop","generationId":"resp_08aec224fd551eae016ae3554049ec81959795769d61123852","provider":null,"usage":{"inputTokens":5756,"outputTokens":132,"cacheReadTokens":5376,"cacheWriteTokens":0,"reasoningTokens":0},"toolCalls":[]},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-03","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","turnId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","executionId":"c57470c13048676a68d94dc7ddd41ef9","stepIndex":2,"timestamp":1793283397454,"type":"step.end","data":{},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-03","rootSessionId":"fx-session-01","parentSessionId":"fx-session-01","sourceSessionId":"fx-session-01","workId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","turnId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","executionId":"c57470c13048676a68d94dc7ddd41ef9","stepIndex":0,"timestamp":1793283397454,"type":"turn.end","data":{"outcome":"completed","error":null},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":1,"timestamp":1793283397455,"type":"delegation","data":{"callId":"call_a00d8b57af314969bc93f183db9c24f2","childSessionId":"fx-session-03","workId":"fxop:2:m:13373308918347017893:582af0032139b111931fe6f4737c56b0a3bc6be902e613bec291ab89d33b9ee4","operationId":null,"phase":"completed"},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:session/update:tool_call_update","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"tool_call_update","toolCallId":"call_a00d8b57af314969bc93f183db9c24f2","status":"completed","content":[{"type":"content","content":{"type":"text","text":"{\"ok\":true,\"result\":\"`package.json` defines:\\n\\n- **Package:** `fx-hooks-fixture` v1.4.0, using ES modules.\\n- **Runtime:** Node.js ≥22; pnpm 10.12.0.\\n- **Scripts:** TypeScript build/typecheck, Vit"}}]}}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":1,"timestamp":1793283397455,"type":"tool.end","data":{"call":{"callId":"call_a00d8b57af314969bc93f183db9c24f2","name":"subagent","provenance":"fx_local","argumentsJson":"{\"action\": \"run\", \"task\": \"Read package.json in the current workspace root using read-only tools and summarize its contents briefly. Do not modify files. If absent, report that.\"}"},"output":"{\"ok\":true,\"result\":\"`package.json` defines:\\n\\n- **Package:** `fx-hooks-fixture` v1.4.0, using ES modules.\\n- **Runtime:** Node.js ≥22; pnpm 10.12.0.\\n- **Scripts:** TypeScript build/typecheck, Vite dev server, Vitest tests, and oxlint linting.\\n- **Dependencies:** Effect ^4.0.0 and React ^19.1.0.\\n- **Dev dependencies:** TypeScript ^5.9.0, Vite ^7.0.0, and Vitest ^4.0.0.\\n\\nNo files were modified.\",\"error_code\":null}","status":"success","error":null},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":1,"timestamp":1793283397455,"type":"tool.result","data":{"call":{"callId":"call_a00d8b57af314969bc93f183db9c24f2","name":"subagent","provenance":"fx_local","argumentsJson":"{\"action\": \"run\", \"task\": \"Read package.json in the current workspace root using read-only tools and summarize its contents briefly. Do not modify files. If absent, report that.\"}"},"output":"{\"ok\":true,\"result\":\"`package.json` defines:\\n\\n- **Package:** `fx-hooks-fixture` v1.4.0, using ES modules.\\n- **Runtime:** Node.js ≥22; pnpm 10.12.0.\\n- **Scripts:** TypeScript build/typecheck, Vite dev server, Vitest tests, and oxlint linting.\\n- **Dependencies:** Effect ^4.0.0 and React ^19.1.0.\\n- **Dev dependencies:** TypeScript ^5.9.0, Vite ^7.0.0, and Vitest ^4.0.0.\\n\\nNo files were modified.\",\"error_code\":null}","status":"success"},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":1,"timestamp":1793283397456,"type":"tool.result","data":{"call":{"callId":"call_1fd5e84bf47448f88898e60db68c47d1","name":"subagent","provenance":"fx_local","argumentsJson":"{\"action\": \"run\", \"task\": \"Read tsconfig.json in the current workspace root using read-only tools and summarize its contents briefly. Do not modify files. If absent, report that.\"}"},"output":"{\"ok\":true,\"result\":\"`tsconfig.json` targets ES2023 with NodeNext modules and resolution. It enables strict checking, unchecked-index checks, and exact optional-property types; uses React’s automatic JSX runtime; compiles `src`; and outputs to `dist`.\\n\\nNo files were modified.\",\"error_code\":null}","status":"success"},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":1,"timestamp":1793283397456,"type":"step.end","data":{},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":2,"timestamp":1793283397456,"type":"step.start","data":{},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":2,"timestamp":1793283397456,"type":"model.start","data":{"modelCallId":3,"parentCallId":null,"purpose":"agent","model":"gpt-6.1-sol","toolChoice":"auto","maxOutputTokens":null,"messageCount":4},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":2,"timestamp":1793283397457,"type":"model.admitted","data":{"modelCallId":3},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":2,"timestamp":1793283400538,"type":"model.output_start","data":{"modelCallId":3},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":"Both sub"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":"agents"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":" completed"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":":\n\n"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":"-"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":" `"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":"package"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":".json"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":"`:"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":" Read"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":" package"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":" metadata"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":","}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":" scripts"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":","}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":" and"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":" dependencies"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":".\n"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":"-"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":" `"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":"ts"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":"config"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":".json"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":"`:"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":" Read"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":" Type"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":"Script"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":" compiler"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":" settings"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":".\n\n"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":"No"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":" files"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":" were"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":" modified"}}}}} +{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"agent_message_chunk","messageId":"eea5502f7dbec4dc0134b305c0045b2c","content":{"type":"text","text":"."}}}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":2,"timestamp":1793283401288,"type":"model.end","data":{"modelCallId":3,"error":null,"cancelled":false,"output":"Both subagents completed:\n\n- `package.json`: Read package metadata, scripts, and dependencies.\n- `tsconfig.json`: Read TypeScript compiler settings.\n\nNo files were modified.","finishReason":"stop","generationId":"resp_0f6333d5bbb85c85016ae3554717bc8197866f8f639800de89","provider":null,"usage":{"inputTokens":6767,"outputTokens":40,"cacheReadTokens":6400,"cacheWriteTokens":0,"reasoningTokens":0},"toolCalls":[]},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":2,"timestamp":1793283401289,"type":"step.end","data":{},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":0,"timestamp":1793283401289,"type":"turn.end","data":{"outcome":"completed","error":null},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:session/update:session_info_update","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"session_info_update","title":"Spawn Subagents to Inspect Project Configuration","updatedAt":"2026-10-29T14:16:41Z"}}}} +{"type":"emit_inbound","label":"notification:session/update:usage_update","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"usage_update","used":6807,"size":500000}}}} +{"type":"emit_inbound","label":"response:session/prompt","frame":{"kind":"response","method":"session/prompt","result":{"stopReason":"end_turn","usage":{"inputTokens":13152,"outputTokens":185,"cacheReadTokens":6400,"cacheWriteTokens":0,"reasoningTokens":0}}}} +{"type":"expect_outbound","label":"request:session/close","frame":{"kind":"request","method":"session/close","params":{"sessionId":"fx-session-01"}}} +{"type":"emit_inbound","label":"response:session/close","frame":{"kind":"response","method":"session/close","result":{}}} +{"type":"runtime_exit","status":"success"} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent_hooks/input.ts b/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent_hooks/input.ts new file mode 100644 index 000000000000..1e778bf8b3bc --- /dev/null +++ b/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent_hooks/input.ts @@ -0,0 +1,48 @@ +import { SUBAGENT_PROMPT, type OrchestratorFixtureInput } from "../shared.ts"; + +/** Real files, so each child reads before replying past fx's 200-byte ACP preview. */ +export function fxSubagentHooksInput(): OrchestratorFixtureInput { + return { + runtimeMode: "auto", + workspaceFiles: { + "package.json": `${JSON.stringify( + { + name: "fx-hooks-fixture", + version: "1.4.0", + type: "module", + packageManager: "pnpm@10.12.0", + engines: { node: ">=22" }, + scripts: { + build: "tsc -p tsconfig.json", + dev: "vite", + test: "vitest run", + lint: "oxlint .", + typecheck: "tsc --noEmit", + }, + dependencies: { effect: "^4.0.0", react: "^19.1.0" }, + devDependencies: { typescript: "^5.9.0", vite: "^7.0.0", vitest: "^4.0.0" }, + }, + null, + 2, + )}\n`, + "tsconfig.json": `${JSON.stringify( + { + compilerOptions: { + target: "ES2023", + module: "NodeNext", + moduleResolution: "NodeNext", + strict: true, + noUncheckedIndexedAccess: true, + exactOptionalPropertyTypes: true, + jsx: "react-jsx", + outDir: "dist", + }, + include: ["src"], + }, + null, + 2, + )}\n`, + }, + steps: [{ type: "message", text: SUBAGENT_PROMPT }], + }; +} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/index.ts b/apps/server/src/orchestration-v2/testkit/fixtures/index.ts index f242c8e0a467..d4c001cc970a 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/index.ts +++ b/apps/server/src/orchestration-v2/testkit/fixtures/index.ts @@ -198,6 +198,8 @@ import { fxEarlyStopInput } from "./fx_early_stop/input.ts"; import { assertFxEarlyStopOutput } from "./fx_early_stop/fx_output.ts"; import { fxSubagentInput } from "./fx_subagent/input.ts"; import { assertFxSubagentOutput } from "./fx_subagent/fx_output.ts"; +import { fxSubagentHooksInput } from "./fx_subagent_hooks/input.ts"; +import { assertFxSubagentHooksOutput } from "./fx_subagent_hooks/fx_output.ts"; import { assertTurnInterruptMidToolClaudeOutput } from "./turn_interrupt_mid_tool/claude_output.ts"; import { assertTurnInterruptMidToolCodexOutput } from "./turn_interrupt_mid_tool/codex_output.ts"; import { assertTurnInterruptMidToolCursorOutput } from "./turn_interrupt_mid_tool/cursor_output.ts"; @@ -1673,6 +1675,18 @@ export const ORCHESTRATOR_REPLAY_FIXTURES: ReadonlyArray, HOME is /home/fx-replay and the recording user is fx-replay. T3-owned prompt text, MCP servers and initialize params other than clientCapabilities and _meta are . User skill names, emails and token-shaped strings are removed. Timestamps are kept as recorded.","droppedFrames":0}} {"type":"expect_outbound","label":"request:initialize","frame":{"kind":"request","method":"initialize","params":{"protocolVersion":2,"info":"","capabilities":"","clientCapabilities":{"fs":{"readTextFile":false,"writeTextFile":false},"terminal":false,"session":{"notices":{}},"elicitation":{"form":{}}},"clientInfo":""}}} {"type":"emit_inbound","label":"response:initialize","frame":{"kind":"response","method":"initialize","result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"promptCapabilities":{"image":true,"audio":false,"embeddedContext":true},"mcpCapabilities":{"http":true,"sse":true,"acp":true},"sessionCapabilities":{"list":{},"resume":{},"close":{},"systemPrompt":{}},"_meta":{"fx":{"steering":true}}},"agentInfo":{"name":"fx","title":"fx","version":"0.0.13"},"authMethods":[]}}} -{"type":"expect_outbound","label":"request:session/new","frame":{"kind":"request","method":"session/new","params":{"cwd":"","mcpServers":""}}} +{"type":"expect_outbound","label":"request:session/new","frame":{"kind":"request","method":"session/new","params":{"cwd":"","mcpServers":"","_meta":{"fx":{"hooks":{"captureContent":true}}}}}} {"type":"emit_inbound","label":"response:session/new","frame":{"kind":"response","method":"session/new","result":{"sessionId":"fx-session-01","configOptions":[{"id":"provider","name":"Provider","category":"model","type":"select","currentValue":"gateway","options":[{"value":"gateway","name":"Vercel AI Gateway"},{"value":"codex","name":"Codex subscription"},{"value":"grok","name":"Grok subscription"}]},{"id":"model","name":"Model","category":"model","type":"select","currentValue":"anthropic/claude-opus-5.5","options":[{"value":"anthropic/claude-sonnet-5.5","name":"anthropic/claude-sonnet-5.5"},{"value":"anthropic/claude-opus-5.5","name":"anthropic/claude-opus-5.5"},{"value":"anthropic/claude-opus-5.5-fast","name":"anthropic/claude-opus-5.5-fast"},{"value":"anthropic/claude-opus-5","name":"anthropic/claude-opus-5"},{"value":"anthropic/claude-opus-5-fast","name":"anthropic/claude-opus-5-fast"},{"value":"anthropic/claude-sonnet-5","name":"anthropic/claude-sonnet-5"},{"value":"anthropic/claude-opus-4.8","name":"anthropic/claude-opus-4.8"},{"value":"anthropic/claude-opus-4.8-fast","name":"anthropic/claude-opus-4.8-fast"},{"value":"anthropic/claude-opus-4.7","name":"anthropic/claude-opus-4.7"},{"value":"anthropic/claude-sonnet-4.6","name":"anthropic/claude-sonnet-4.6"},{"value":"anthropic/claude-opus-4.6","name":"anthropic/claude-opus-4.6"},{"value":"anthropic/claude-opus-4.5","name":"anthropic/claude-opus-4.5"},{"value":"anthropic/claude-sonnet-4.5","name":"anthropic/claude-sonnet-4.5"},{"value":"anthropic/claude-opus-4","name":"anthropic/claude-opus-4"},{"value":"anthropic/claude-sonnet-4","name":"anthropic/claude-sonnet-4"},{"value":"openai/gpt-5.6-luna","name":"openai/gpt-5.6-luna"},{"value":"openai/gpt-5.6-luna-fast","name":"openai/gpt-5.6-luna-fast"},{"value":"openai/gpt-5.6-sol","name":"openai/gpt-5.6-sol"},{"value":"openai/gpt-5.6-sol-fast","name":"openai/gpt-5.6-sol-fast"},{"value":"openai/gpt-5.6-terra","name":"openai/gpt-5.6-terra"},{"value":"openai/gpt-5.6-terra-fast","name":"openai/gpt-5.6-terra-fast"},{"value":"openai/gpt-5.5","name":"openai/gpt-5.5"},{"value":"openai/gpt-5.5-fast","name":"openai/gpt-5.5-fast"},{"value":"openai/gpt-5.5-pro","name":"openai/gpt-5.5-pro"},{"value":"openai/gpt-5.4-nano","name":"openai/gpt-5.4-nano"},{"value":"openai/gpt-5.4","name":"openai/gpt-5.4"},{"value":"openai/gpt-5.4-fast","name":"openai/gpt-5.4-fast"},{"value":"openai/gpt-5.4-pro","name":"openai/gpt-5.4-pro"},{"value":"openai/gpt-5.3-codex","name":"openai/gpt-5.3-codex"},{"value":"openai/gpt-5.3-codex-fast","name":"openai/gpt-5.3-codex-fast"},{"value":"openai/gpt-5.2","name":"openai/gpt-5.2"},{"value":"openai/gpt-5.2-fast","name":"openai/gpt-5.2-fast"},{"value":"openai/gpt-5.2-pro","name":"openai/gpt-5.2-pro"},{"value":"openai/gpt-5.1-thinking","name":"openai/gpt-5.1-thinking"},{"value":"openai/gpt-5.1-thinking-fast","name":"openai/gpt-5.1-thinking-fast"},{"value":"openai/gpt-5-pro","name":"openai/gpt-5-pro"},{"value":"openai/gpt-5","name":"openai/gpt-5"},{"value":"openai/gpt-5-fast","name":"openai/gpt-5-fast"},{"value":"openai/gpt-5-nano","name":"openai/gpt-5-nano"},{"value":"openai/o3-pro","name":"openai/o3-pro"},{"value":"openai/o3","name":"openai/o3"},{"value":"openai/o3-fast","name":"openai/o3-fast"},{"value":"openai/o1","name":"openai/o1"},{"value":"deepseek/deepseek-v4-pro-0813","name":"deepseek/deepseek-v4-pro-0813"},{"value":"deepseek/deepseek-v4-pro","name":"deepseek/deepseek-v4-pro"},{"value":"spacexai/grok-4.7","name":"spacexai/grok-4.7"},{"value":"spacexai/grok-4.6","name":"spacexai/grok-4.6"},{"value":"spacexai/grok-4.5","name":"spacexai/grok-4.5"},{"value":"spacexai/grok-4.3","name":"spacexai/grok-4.3"},{"value":"spacexai/grok-4.20-multi-agent","name":"spacexai/grok-4.20-multi-agent"},{"value":"spacexai/grok-4.20-non-reasoning","name":"spacexai/grok-4.20-non-reasoning"},{"value":"spacexai/grok-4.20-reasoning","name":"spacexai/grok-4.20-reasoning"},{"value":"spacexai/grok-4.1-fast-non-reasoning","name":"spacexai/grok-4.1-fast-non-reasoning"},{"value":"spacexai/grok-4.1-fast-reasoning","name":"spacexai/grok-4.1-fast-reasoning"},{"value":"amazon/nova-pro","name":"amazon/nova-pro"},{"value":"anthropic/claude-fable-5.1","name":"anthropic/claude-fable-5.1"},{"value":"anthropic/claude-fable-5","name":"anthropic/claude-fable-5"},{"value":"openai/gpt-6.1-sol","name":"openai/gpt-6.1-sol"},{"value":"openai/gpt-6.1-sol-fast","name":"openai/gpt-6.1-sol-fast"},{"value":"openai/gpt-6-luna","name":"openai/gpt-6-luna"},{"value":"openai/gpt-6-luna-fast","name":"openai/gpt-6-luna-fast"},{"value":"openai/gpt-6-sol","name":"openai/gpt-6-sol"},{"value":"openai/gpt-6-sol-fast","name":"openai/gpt-6-sol-fast"},{"value":"openai/gpt-6-astra","name":"openai/gpt-6-astra"},{"value":"openai/gpt-6-astra-fast","name":"openai/gpt-6-astra-fast"},{"value":"openai/gpt-oss-safeguard-120b","name":"openai/gpt-oss-safeguard-120b"},{"value":"openai/gpt-oss-safeguard-20b","name":"openai/gpt-oss-safeguard-20b"},{"value":"openai/gpt-oss-120b","name":"openai/gpt-oss-120b"},{"value":"openai/gpt-oss-20b","name":"openai/gpt-oss-20b"},{"value":"openai/gpt-4.1","name":"openai/gpt-4.1"},{"value":"openai/gpt-4.1-fast","name":"openai/gpt-4.1-fast"},{"value":"openai/gpt-4.1-nano","name":"openai/gpt-4.1-nano"},{"value":"openai/gpt-4.1-nano-fast","name":"openai/gpt-4.1-nano-fast"},{"value":"openai/gpt-4o","name":"openai/gpt-4o"},{"value":"openai/gpt-4o-fast","name":"openai/gpt-4o-fast"},{"value":"openai/gpt-4-turbo","name":"openai/gpt-4-turbo"},{"value":"openai/gpt-3.5-turbo","name":"openai/gpt-3.5-turbo"},{"value":"google/gemma-4-26b-a4b-it","name":"google/gemma-4-26b-a4b-it"},{"value":"deepseek/deepseek-v3.2","name":"deepseek/deepseek-v3.2"},{"value":"deepseek/deepseek-v3.2-thinking","name":"deepseek/deepseek-v3.2-thinking"},{"value":"deepseek/deepseek-r1","name":"deepseek/deepseek-r1"},{"value":"meta/llama-4-maverick","name":"meta/llama-4-maverick"},{"value":"meta/llama-4-scout","name":"meta/llama-4-scout"},{"value":"meta/llama-3.3-70b","name":"meta/llama-3.3-70b"},{"value":"meta/llama-3.1-70b","name":"meta/llama-3.1-70b"},{"value":"meta/llama-3.1-8b","name":"meta/llama-3.1-8b"},{"value":"alibaba/qwen3.8-2.4t-a95b","name":"alibaba/qwen3.8-2.4t-a95b"},{"value":"alibaba/qwen3.8-max","name":"alibaba/qwen3.8-max"},{"value":"alibaba/qwen3-next-80b-a3b-instruct","name":"alibaba/qwen3-next-80b-a3b-instruct"},{"value":"alibaba/qwen3-next-80b-a3b-thinking","name":"alibaba/qwen3-next-80b-a3b-thinking"},{"value":"alibaba/qwen3-coder-30b-a3b","name":"alibaba/qwen3-coder-30b-a3b"},{"value":"alibaba/qwen3-coder","name":"alibaba/qwen3-coder"},{"value":"alibaba/qwen3-coder-next","name":"alibaba/qwen3-coder-next"},{"value":"alibaba/qwen-3-235b","name":"alibaba/qwen-3-235b"},{"value":"alibaba/qwen-3-32b","name":"alibaba/qwen-3-32b"},{"value":"fireworks/ember-1","name":"fireworks/ember-1"},{"value":"zai/glm-5.3-fast","name":"zai/glm-5.3-fast"},{"value":"zai/glm-5.3","name":"zai/glm-5.3"},{"value":"nvidia/nemotron-3.5-lightning","name":"nvidia/nemotron-3.5-lightning"},{"value":"moonshotai/kimi-k3-fast","name":"moonshotai/kimi-k3-fast"},{"value":"moonshotai/kimi-k3","name":"moonshotai/kimi-k3"},{"value":"zai/glm-5.2-fast","name":"zai/glm-5.2-fast"},{"value":"zai/glm-5.2","name":"zai/glm-5.2"},{"value":"nvidia/nemotron-3-ultra-550b-a55b","name":"nvidia/nemotron-3-ultra-550b-a55b"},{"value":"spacexai/grok-build-0.1","name":"spacexai/grok-build-0.1"},{"value":"nvidia/nemotron-3-super-120b-a12b","name":"nvidia/nemotron-3-super-120b-a12b"},{"value":"zai/glm-5","name":"zai/glm-5"},{"value":"moonshotai/kimi-k2.5","name":"moonshotai/kimi-k2.5"},{"value":"zai/glm-4.7","name":"zai/glm-4.7"},{"value":"nvidia/nemotron-nano-12b-v2-vl","name":"nvidia/nemotron-nano-12b-v2-vl"},{"value":"nvidia/nemotron-nano-9b-v2","name":"nvidia/nemotron-nano-9b-v2"},{"value":"amazon/nova-micro","name":"amazon/nova-micro"},{"value":"deepseek/deepseek-v4.1-flash","name":"deepseek/deepseek-v4.1-flash"},{"value":"deepseek/deepseek-v4-flash","name":"deepseek/deepseek-v4-flash"},{"value":"deepseek/deepseek-v4-flash-0731","name":"deepseek/deepseek-v4-flash-0731"},{"value":"zai/glm-5.3-flash","name":"zai/glm-5.3-flash"},{"value":"zai/glm-4.7-flash","name":"zai/glm-4.7-flash"},{"value":"anthropic/claude-haiku-5.5","name":"anthropic/claude-haiku-5.5"},{"value":"anthropic/claude-haiku-4.5","name":"anthropic/claude-haiku-4.5"},{"value":"anthropic/claude-3-haiku","name":"anthropic/claude-3-haiku"},{"value":"openai/gpt-5.4-mini","name":"openai/gpt-5.4-mini"},{"value":"openai/gpt-5.4-mini-fast","name":"openai/gpt-5.4-mini-fast"},{"value":"openai/gpt-5-mini","name":"openai/gpt-5-mini"},{"value":"openai/gpt-5-mini-fast","name":"openai/gpt-5-mini-fast"},{"value":"openai/gpt-4.1-mini","name":"openai/gpt-4.1-mini"},{"value":"openai/gpt-4.1-mini-fast","name":"openai/gpt-4.1-mini-fast"},{"value":"openai/o4-mini","name":"openai/o4-mini"},{"value":"openai/o4-mini-fast","name":"openai/o4-mini-fast"},{"value":"openai/o3-mini","name":"openai/o3-mini"},{"value":"openai/gpt-4o-mini","name":"openai/gpt-4o-mini"},{"value":"openai/gpt-4o-mini-fast","name":"openai/gpt-4o-mini-fast"},{"value":"google/gemini-3.8-flash","name":"google/gemini-3.8-flash"},{"value":"google/gemini-3.7-flash","name":"google/gemini-3.7-flash"},{"value":"google/gemini-3.5-flash-lite","name":"google/gemini-3.5-flash-lite"},{"value":"google/gemini-3.6-flash","name":"google/gemini-3.6-flash"},{"value":"google/gemini-3.5-flash","name":"google/gemini-3.5-flash"},{"value":"google/gemini-3.1-flash-lite","name":"google/gemini-3.1-flash-lite"},{"value":"google/gemini-3-flash","name":"google/gemini-3-flash"},{"value":"google/gemini-2.5-flash-lite","name":"google/gemini-2.5-flash-lite"},{"value":"google/gemini-2.5-flash","name":"google/gemini-2.5-flash"},{"value":"google/gemini-2.5-pro","name":"google/gemini-2.5-pro"},{"value":"minimax/minimax-m3","name":"minimax/minimax-m3"},{"value":"minimax/minimax-m2.5","name":"minimax/minimax-m2.5"},{"value":"minimax/minimax-m2.1","name":"minimax/minimax-m2.1"},{"value":"amazon/nova-2-lite","name":"amazon/nova-2-lite"},{"value":"amazon/nova-lite","name":"amazon/nova-lite"},{"value":"google/gemini-3.1-pro-preview","name":"google/gemini-3.1-pro-preview"},{"value":"spacexai/grok-4.20-multi-agent-beta","name":"spacexai/grok-4.20-multi-agent-beta"},{"value":"spacexai/grok-4.20-non-reasoning-beta","name":"spacexai/grok-4.20-non-reasoning-beta"},{"value":"spacexai/grok-4.20-reasoning-beta","name":"spacexai/grok-4.20-reasoning-beta"}]},{"id":"mode","name":"Session Mode","description":"Controls how the agent requests permission","category":"mode","type":"select","currentValue":"ask","options":[{"value":"code","name":"Code","description":"Write and modify code with full tool access","permissionMode":"auto"},{"value":"ask","name":"Ask","description":"Request permission before making any changes","permissionMode":"ask"}]},{"id":"effort","name":"Reasoning Effort","description":"Controls how much the model thinks before responding","category":"thought_level","type":"select","currentValue":"xhigh","options":[{"value":"auto","name":"default"},{"value":"low","name":"low"},{"value":"medium","name":"medium"},{"value":"high","name":"high"},{"value":"xhigh","name":"xhigh"},{"value":"max","name":"max"}]}],"modes":{"currentModeId":"ask","availableModes":[{"id":"code","name":"Code","description":"Write and modify code with full tool access"},{"id":"ask","name":"Ask","description":"Request permission before making any changes"}]}}}} {"type":"expect_outbound","label":"request:session/set_config_option","frame":{"kind":"request","method":"session/set_config_option","params":{"sessionId":"fx-session-01","configId":"effort","value":"medium"}}} {"type":"emit_inbound","label":"notification:session/update:available_commands_update","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"available_commands_update","availableCommands":[]}}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/fx_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/fx_transcript.ndjson index d394fdf4c778..286451afd3fd 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/fx_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/fx_transcript.ndjson @@ -1,7 +1,7 @@ {"type":"transcript_start","provider":"fx","protocol":"acp.ndjson-jsonrpc","version":"1","scenario":"multi_turn","metadata":{"generatedBy":"live-fx-recorder","fxVersion":"0.0.13","normalization":"Session ids are fx-session-NN, the workspace is , HOME is /home/fx-replay and the recording user is fx-replay. T3-owned prompt text, MCP servers and initialize params other than clientCapabilities and _meta are . User skill names, emails and token-shaped strings are removed. Timestamps are kept as recorded.","droppedFrames":0}} {"type":"expect_outbound","label":"request:initialize","frame":{"kind":"request","method":"initialize","params":{"protocolVersion":2,"info":"","capabilities":"","clientCapabilities":{"fs":{"readTextFile":false,"writeTextFile":false},"terminal":false,"session":{"notices":{}},"elicitation":{"form":{}}},"clientInfo":""}}} {"type":"emit_inbound","label":"response:initialize","frame":{"kind":"response","method":"initialize","result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"promptCapabilities":{"image":true,"audio":false,"embeddedContext":true},"mcpCapabilities":{"http":true,"sse":true,"acp":true},"sessionCapabilities":{"list":{},"resume":{},"close":{},"systemPrompt":{}},"_meta":{"fx":{"steering":true}}},"agentInfo":{"name":"fx","title":"fx","version":"0.0.13"},"authMethods":[]}}} -{"type":"expect_outbound","label":"request:session/new","frame":{"kind":"request","method":"session/new","params":{"cwd":"","mcpServers":""}}} +{"type":"expect_outbound","label":"request:session/new","frame":{"kind":"request","method":"session/new","params":{"cwd":"","mcpServers":"","_meta":{"fx":{"hooks":{"captureContent":true}}}}}} {"type":"emit_inbound","label":"response:session/new","frame":{"kind":"response","method":"session/new","result":{"sessionId":"fx-session-01","configOptions":[{"id":"provider","name":"Provider","category":"model","type":"select","currentValue":"gateway","options":[{"value":"gateway","name":"Vercel AI Gateway"},{"value":"codex","name":"Codex subscription"},{"value":"grok","name":"Grok subscription"}]},{"id":"model","name":"Model","category":"model","type":"select","currentValue":"anthropic/claude-opus-5.5","options":[{"value":"anthropic/claude-sonnet-5.5","name":"anthropic/claude-sonnet-5.5"},{"value":"anthropic/claude-opus-5.5","name":"anthropic/claude-opus-5.5"},{"value":"anthropic/claude-opus-5.5-fast","name":"anthropic/claude-opus-5.5-fast"},{"value":"anthropic/claude-opus-5","name":"anthropic/claude-opus-5"},{"value":"anthropic/claude-opus-5-fast","name":"anthropic/claude-opus-5-fast"},{"value":"anthropic/claude-sonnet-5","name":"anthropic/claude-sonnet-5"},{"value":"anthropic/claude-opus-4.8","name":"anthropic/claude-opus-4.8"},{"value":"anthropic/claude-opus-4.8-fast","name":"anthropic/claude-opus-4.8-fast"},{"value":"anthropic/claude-opus-4.7","name":"anthropic/claude-opus-4.7"},{"value":"anthropic/claude-sonnet-4.6","name":"anthropic/claude-sonnet-4.6"},{"value":"anthropic/claude-opus-4.6","name":"anthropic/claude-opus-4.6"},{"value":"anthropic/claude-opus-4.5","name":"anthropic/claude-opus-4.5"},{"value":"anthropic/claude-sonnet-4.5","name":"anthropic/claude-sonnet-4.5"},{"value":"anthropic/claude-opus-4","name":"anthropic/claude-opus-4"},{"value":"anthropic/claude-sonnet-4","name":"anthropic/claude-sonnet-4"},{"value":"openai/gpt-5.6-luna","name":"openai/gpt-5.6-luna"},{"value":"openai/gpt-5.6-luna-fast","name":"openai/gpt-5.6-luna-fast"},{"value":"openai/gpt-5.6-sol","name":"openai/gpt-5.6-sol"},{"value":"openai/gpt-5.6-sol-fast","name":"openai/gpt-5.6-sol-fast"},{"value":"openai/gpt-5.6-terra","name":"openai/gpt-5.6-terra"},{"value":"openai/gpt-5.6-terra-fast","name":"openai/gpt-5.6-terra-fast"},{"value":"openai/gpt-5.5","name":"openai/gpt-5.5"},{"value":"openai/gpt-5.5-fast","name":"openai/gpt-5.5-fast"},{"value":"openai/gpt-5.5-pro","name":"openai/gpt-5.5-pro"},{"value":"openai/gpt-5.4-nano","name":"openai/gpt-5.4-nano"},{"value":"openai/gpt-5.4","name":"openai/gpt-5.4"},{"value":"openai/gpt-5.4-fast","name":"openai/gpt-5.4-fast"},{"value":"openai/gpt-5.4-pro","name":"openai/gpt-5.4-pro"},{"value":"openai/gpt-5.3-codex","name":"openai/gpt-5.3-codex"},{"value":"openai/gpt-5.3-codex-fast","name":"openai/gpt-5.3-codex-fast"},{"value":"openai/gpt-5.2","name":"openai/gpt-5.2"},{"value":"openai/gpt-5.2-fast","name":"openai/gpt-5.2-fast"},{"value":"openai/gpt-5.2-pro","name":"openai/gpt-5.2-pro"},{"value":"openai/gpt-5.1-thinking","name":"openai/gpt-5.1-thinking"},{"value":"openai/gpt-5.1-thinking-fast","name":"openai/gpt-5.1-thinking-fast"},{"value":"openai/gpt-5-pro","name":"openai/gpt-5-pro"},{"value":"openai/gpt-5","name":"openai/gpt-5"},{"value":"openai/gpt-5-fast","name":"openai/gpt-5-fast"},{"value":"openai/gpt-5-nano","name":"openai/gpt-5-nano"},{"value":"openai/o3-pro","name":"openai/o3-pro"},{"value":"openai/o3","name":"openai/o3"},{"value":"openai/o3-fast","name":"openai/o3-fast"},{"value":"openai/o1","name":"openai/o1"},{"value":"deepseek/deepseek-v4-pro-0813","name":"deepseek/deepseek-v4-pro-0813"},{"value":"deepseek/deepseek-v4-pro","name":"deepseek/deepseek-v4-pro"},{"value":"spacexai/grok-4.7","name":"spacexai/grok-4.7"},{"value":"spacexai/grok-4.6","name":"spacexai/grok-4.6"},{"value":"spacexai/grok-4.5","name":"spacexai/grok-4.5"},{"value":"spacexai/grok-4.3","name":"spacexai/grok-4.3"},{"value":"spacexai/grok-4.20-multi-agent","name":"spacexai/grok-4.20-multi-agent"},{"value":"spacexai/grok-4.20-non-reasoning","name":"spacexai/grok-4.20-non-reasoning"},{"value":"spacexai/grok-4.20-reasoning","name":"spacexai/grok-4.20-reasoning"},{"value":"spacexai/grok-4.1-fast-non-reasoning","name":"spacexai/grok-4.1-fast-non-reasoning"},{"value":"spacexai/grok-4.1-fast-reasoning","name":"spacexai/grok-4.1-fast-reasoning"},{"value":"amazon/nova-pro","name":"amazon/nova-pro"},{"value":"anthropic/claude-fable-5.1","name":"anthropic/claude-fable-5.1"},{"value":"anthropic/claude-fable-5","name":"anthropic/claude-fable-5"},{"value":"openai/gpt-6.1-sol","name":"openai/gpt-6.1-sol"},{"value":"openai/gpt-6.1-sol-fast","name":"openai/gpt-6.1-sol-fast"},{"value":"openai/gpt-6-luna","name":"openai/gpt-6-luna"},{"value":"openai/gpt-6-luna-fast","name":"openai/gpt-6-luna-fast"},{"value":"openai/gpt-6-sol","name":"openai/gpt-6-sol"},{"value":"openai/gpt-6-sol-fast","name":"openai/gpt-6-sol-fast"},{"value":"openai/gpt-6-astra","name":"openai/gpt-6-astra"},{"value":"openai/gpt-6-astra-fast","name":"openai/gpt-6-astra-fast"},{"value":"openai/gpt-oss-safeguard-120b","name":"openai/gpt-oss-safeguard-120b"},{"value":"openai/gpt-oss-safeguard-20b","name":"openai/gpt-oss-safeguard-20b"},{"value":"openai/gpt-oss-120b","name":"openai/gpt-oss-120b"},{"value":"openai/gpt-oss-20b","name":"openai/gpt-oss-20b"},{"value":"openai/gpt-4.1","name":"openai/gpt-4.1"},{"value":"openai/gpt-4.1-fast","name":"openai/gpt-4.1-fast"},{"value":"openai/gpt-4.1-nano","name":"openai/gpt-4.1-nano"},{"value":"openai/gpt-4.1-nano-fast","name":"openai/gpt-4.1-nano-fast"},{"value":"openai/gpt-4o","name":"openai/gpt-4o"},{"value":"openai/gpt-4o-fast","name":"openai/gpt-4o-fast"},{"value":"openai/gpt-4-turbo","name":"openai/gpt-4-turbo"},{"value":"openai/gpt-3.5-turbo","name":"openai/gpt-3.5-turbo"},{"value":"google/gemma-4-26b-a4b-it","name":"google/gemma-4-26b-a4b-it"},{"value":"deepseek/deepseek-v3.2","name":"deepseek/deepseek-v3.2"},{"value":"deepseek/deepseek-v3.2-thinking","name":"deepseek/deepseek-v3.2-thinking"},{"value":"deepseek/deepseek-r1","name":"deepseek/deepseek-r1"},{"value":"meta/llama-4-maverick","name":"meta/llama-4-maverick"},{"value":"meta/llama-4-scout","name":"meta/llama-4-scout"},{"value":"meta/llama-3.3-70b","name":"meta/llama-3.3-70b"},{"value":"meta/llama-3.1-70b","name":"meta/llama-3.1-70b"},{"value":"meta/llama-3.1-8b","name":"meta/llama-3.1-8b"},{"value":"alibaba/qwen3.8-2.4t-a95b","name":"alibaba/qwen3.8-2.4t-a95b"},{"value":"alibaba/qwen3.8-max","name":"alibaba/qwen3.8-max"},{"value":"alibaba/qwen3-next-80b-a3b-instruct","name":"alibaba/qwen3-next-80b-a3b-instruct"},{"value":"alibaba/qwen3-next-80b-a3b-thinking","name":"alibaba/qwen3-next-80b-a3b-thinking"},{"value":"alibaba/qwen3-coder-30b-a3b","name":"alibaba/qwen3-coder-30b-a3b"},{"value":"alibaba/qwen3-coder","name":"alibaba/qwen3-coder"},{"value":"alibaba/qwen3-coder-next","name":"alibaba/qwen3-coder-next"},{"value":"alibaba/qwen-3-235b","name":"alibaba/qwen-3-235b"},{"value":"alibaba/qwen-3-32b","name":"alibaba/qwen-3-32b"},{"value":"fireworks/ember-1","name":"fireworks/ember-1"},{"value":"zai/glm-5.3-fast","name":"zai/glm-5.3-fast"},{"value":"zai/glm-5.3","name":"zai/glm-5.3"},{"value":"nvidia/nemotron-3.5-lightning","name":"nvidia/nemotron-3.5-lightning"},{"value":"moonshotai/kimi-k3-fast","name":"moonshotai/kimi-k3-fast"},{"value":"moonshotai/kimi-k3","name":"moonshotai/kimi-k3"},{"value":"zai/glm-5.2-fast","name":"zai/glm-5.2-fast"},{"value":"zai/glm-5.2","name":"zai/glm-5.2"},{"value":"nvidia/nemotron-3-ultra-550b-a55b","name":"nvidia/nemotron-3-ultra-550b-a55b"},{"value":"spacexai/grok-build-0.1","name":"spacexai/grok-build-0.1"},{"value":"nvidia/nemotron-3-super-120b-a12b","name":"nvidia/nemotron-3-super-120b-a12b"},{"value":"zai/glm-5","name":"zai/glm-5"},{"value":"moonshotai/kimi-k2.5","name":"moonshotai/kimi-k2.5"},{"value":"zai/glm-4.7","name":"zai/glm-4.7"},{"value":"nvidia/nemotron-nano-12b-v2-vl","name":"nvidia/nemotron-nano-12b-v2-vl"},{"value":"nvidia/nemotron-nano-9b-v2","name":"nvidia/nemotron-nano-9b-v2"},{"value":"amazon/nova-micro","name":"amazon/nova-micro"},{"value":"deepseek/deepseek-v4.1-flash","name":"deepseek/deepseek-v4.1-flash"},{"value":"deepseek/deepseek-v4-flash","name":"deepseek/deepseek-v4-flash"},{"value":"deepseek/deepseek-v4-flash-0731","name":"deepseek/deepseek-v4-flash-0731"},{"value":"zai/glm-5.3-flash","name":"zai/glm-5.3-flash"},{"value":"zai/glm-4.7-flash","name":"zai/glm-4.7-flash"},{"value":"anthropic/claude-haiku-5.5","name":"anthropic/claude-haiku-5.5"},{"value":"anthropic/claude-haiku-4.5","name":"anthropic/claude-haiku-4.5"},{"value":"anthropic/claude-3-haiku","name":"anthropic/claude-3-haiku"},{"value":"openai/gpt-5.4-mini","name":"openai/gpt-5.4-mini"},{"value":"openai/gpt-5.4-mini-fast","name":"openai/gpt-5.4-mini-fast"},{"value":"openai/gpt-5-mini","name":"openai/gpt-5-mini"},{"value":"openai/gpt-5-mini-fast","name":"openai/gpt-5-mini-fast"},{"value":"openai/gpt-4.1-mini","name":"openai/gpt-4.1-mini"},{"value":"openai/gpt-4.1-mini-fast","name":"openai/gpt-4.1-mini-fast"},{"value":"openai/o4-mini","name":"openai/o4-mini"},{"value":"openai/o4-mini-fast","name":"openai/o4-mini-fast"},{"value":"openai/o3-mini","name":"openai/o3-mini"},{"value":"openai/gpt-4o-mini","name":"openai/gpt-4o-mini"},{"value":"openai/gpt-4o-mini-fast","name":"openai/gpt-4o-mini-fast"},{"value":"google/gemini-3.8-flash","name":"google/gemini-3.8-flash"},{"value":"google/gemini-3.7-flash","name":"google/gemini-3.7-flash"},{"value":"google/gemini-3.5-flash-lite","name":"google/gemini-3.5-flash-lite"},{"value":"google/gemini-3.6-flash","name":"google/gemini-3.6-flash"},{"value":"google/gemini-3.5-flash","name":"google/gemini-3.5-flash"},{"value":"google/gemini-3.1-flash-lite","name":"google/gemini-3.1-flash-lite"},{"value":"google/gemini-3-flash","name":"google/gemini-3-flash"},{"value":"google/gemini-2.5-flash-lite","name":"google/gemini-2.5-flash-lite"},{"value":"google/gemini-2.5-flash","name":"google/gemini-2.5-flash"},{"value":"google/gemini-2.5-pro","name":"google/gemini-2.5-pro"},{"value":"minimax/minimax-m3","name":"minimax/minimax-m3"},{"value":"minimax/minimax-m2.5","name":"minimax/minimax-m2.5"},{"value":"minimax/minimax-m2.1","name":"minimax/minimax-m2.1"},{"value":"amazon/nova-2-lite","name":"amazon/nova-2-lite"},{"value":"amazon/nova-lite","name":"amazon/nova-lite"},{"value":"google/gemini-3.1-pro-preview","name":"google/gemini-3.1-pro-preview"},{"value":"spacexai/grok-4.20-multi-agent-beta","name":"spacexai/grok-4.20-multi-agent-beta"},{"value":"spacexai/grok-4.20-non-reasoning-beta","name":"spacexai/grok-4.20-non-reasoning-beta"},{"value":"spacexai/grok-4.20-reasoning-beta","name":"spacexai/grok-4.20-reasoning-beta"}]},{"id":"mode","name":"Session Mode","description":"Controls how the agent requests permission","category":"mode","type":"select","currentValue":"ask","options":[{"value":"code","name":"Code","description":"Write and modify code with full tool access","permissionMode":"auto"},{"value":"ask","name":"Ask","description":"Request permission before making any changes","permissionMode":"ask"}]},{"id":"effort","name":"Reasoning Effort","description":"Controls how much the model thinks before responding","category":"thought_level","type":"select","currentValue":"xhigh","options":[{"value":"auto","name":"default"},{"value":"low","name":"low"},{"value":"medium","name":"medium"},{"value":"high","name":"high"},{"value":"xhigh","name":"xhigh"},{"value":"max","name":"max"}]}],"modes":{"currentModeId":"ask","availableModes":[{"id":"code","name":"Code","description":"Write and modify code with full tool access"},{"id":"ask","name":"Ask","description":"Request permission before making any changes"}]}}}} {"type":"emit_inbound","label":"notification:session/update:available_commands_update","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"available_commands_update","availableCommands":[]}}}} {"type":"expect_outbound","label":"request:session/set_config_option","frame":{"kind":"request","method":"session/set_config_option","params":{"sessionId":"fx-session-01","configId":"effort","value":"medium"}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/fx_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/fx_transcript.ndjson index 6780dd2a3ad4..61a4749cf906 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/fx_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/fx_transcript.ndjson @@ -1,7 +1,7 @@ {"type":"transcript_start","provider":"fx","protocol":"acp.ndjson-jsonrpc","version":"1","scenario":"queued_turn","metadata":{"generatedBy":"live-fx-recorder","fxVersion":"0.0.13","normalization":"Session ids are fx-session-NN, the workspace is , HOME is /home/fx-replay and the recording user is fx-replay. T3-owned prompt text, MCP servers and initialize params other than clientCapabilities and _meta are . User skill names, emails and token-shaped strings are removed. Timestamps are kept as recorded.","droppedFrames":0}} {"type":"expect_outbound","label":"request:initialize","frame":{"kind":"request","method":"initialize","params":{"protocolVersion":2,"info":"","capabilities":"","clientCapabilities":{"fs":{"readTextFile":false,"writeTextFile":false},"terminal":false,"session":{"notices":{}},"elicitation":{"form":{}}},"clientInfo":""}}} {"type":"emit_inbound","label":"response:initialize","frame":{"kind":"response","method":"initialize","result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"promptCapabilities":{"image":true,"audio":false,"embeddedContext":true},"mcpCapabilities":{"http":true,"sse":true,"acp":true},"sessionCapabilities":{"list":{},"resume":{},"close":{},"systemPrompt":{}},"_meta":{"fx":{"steering":true}}},"agentInfo":{"name":"fx","title":"fx","version":"0.0.13"},"authMethods":[]}}} -{"type":"expect_outbound","label":"request:session/new","frame":{"kind":"request","method":"session/new","params":{"cwd":"","mcpServers":""}}} +{"type":"expect_outbound","label":"request:session/new","frame":{"kind":"request","method":"session/new","params":{"cwd":"","mcpServers":"","_meta":{"fx":{"hooks":{"captureContent":true}}}}}} {"type":"emit_inbound","label":"response:session/new","frame":{"kind":"response","method":"session/new","result":{"sessionId":"fx-session-01","configOptions":[{"id":"provider","name":"Provider","category":"model","type":"select","currentValue":"gateway","options":[{"value":"gateway","name":"Vercel AI Gateway"},{"value":"codex","name":"Codex subscription"},{"value":"grok","name":"Grok subscription"}]},{"id":"model","name":"Model","category":"model","type":"select","currentValue":"anthropic/claude-opus-5.5","options":[{"value":"anthropic/claude-sonnet-5.5","name":"anthropic/claude-sonnet-5.5"},{"value":"anthropic/claude-opus-5.5","name":"anthropic/claude-opus-5.5"},{"value":"anthropic/claude-opus-5.5-fast","name":"anthropic/claude-opus-5.5-fast"},{"value":"anthropic/claude-opus-5","name":"anthropic/claude-opus-5"},{"value":"anthropic/claude-opus-5-fast","name":"anthropic/claude-opus-5-fast"},{"value":"anthropic/claude-sonnet-5","name":"anthropic/claude-sonnet-5"},{"value":"anthropic/claude-opus-4.8","name":"anthropic/claude-opus-4.8"},{"value":"anthropic/claude-opus-4.8-fast","name":"anthropic/claude-opus-4.8-fast"},{"value":"anthropic/claude-opus-4.7","name":"anthropic/claude-opus-4.7"},{"value":"anthropic/claude-sonnet-4.6","name":"anthropic/claude-sonnet-4.6"},{"value":"anthropic/claude-opus-4.6","name":"anthropic/claude-opus-4.6"},{"value":"anthropic/claude-opus-4.5","name":"anthropic/claude-opus-4.5"},{"value":"anthropic/claude-sonnet-4.5","name":"anthropic/claude-sonnet-4.5"},{"value":"anthropic/claude-opus-4","name":"anthropic/claude-opus-4"},{"value":"anthropic/claude-sonnet-4","name":"anthropic/claude-sonnet-4"},{"value":"openai/gpt-5.6-luna","name":"openai/gpt-5.6-luna"},{"value":"openai/gpt-5.6-luna-fast","name":"openai/gpt-5.6-luna-fast"},{"value":"openai/gpt-5.6-sol","name":"openai/gpt-5.6-sol"},{"value":"openai/gpt-5.6-sol-fast","name":"openai/gpt-5.6-sol-fast"},{"value":"openai/gpt-5.6-terra","name":"openai/gpt-5.6-terra"},{"value":"openai/gpt-5.6-terra-fast","name":"openai/gpt-5.6-terra-fast"},{"value":"openai/gpt-5.5","name":"openai/gpt-5.5"},{"value":"openai/gpt-5.5-fast","name":"openai/gpt-5.5-fast"},{"value":"openai/gpt-5.5-pro","name":"openai/gpt-5.5-pro"},{"value":"openai/gpt-5.4-nano","name":"openai/gpt-5.4-nano"},{"value":"openai/gpt-5.4","name":"openai/gpt-5.4"},{"value":"openai/gpt-5.4-fast","name":"openai/gpt-5.4-fast"},{"value":"openai/gpt-5.4-pro","name":"openai/gpt-5.4-pro"},{"value":"openai/gpt-5.3-codex","name":"openai/gpt-5.3-codex"},{"value":"openai/gpt-5.3-codex-fast","name":"openai/gpt-5.3-codex-fast"},{"value":"openai/gpt-5.2","name":"openai/gpt-5.2"},{"value":"openai/gpt-5.2-fast","name":"openai/gpt-5.2-fast"},{"value":"openai/gpt-5.2-pro","name":"openai/gpt-5.2-pro"},{"value":"openai/gpt-5.1-thinking","name":"openai/gpt-5.1-thinking"},{"value":"openai/gpt-5.1-thinking-fast","name":"openai/gpt-5.1-thinking-fast"},{"value":"openai/gpt-5-pro","name":"openai/gpt-5-pro"},{"value":"openai/gpt-5","name":"openai/gpt-5"},{"value":"openai/gpt-5-fast","name":"openai/gpt-5-fast"},{"value":"openai/gpt-5-nano","name":"openai/gpt-5-nano"},{"value":"openai/o3-pro","name":"openai/o3-pro"},{"value":"openai/o3","name":"openai/o3"},{"value":"openai/o3-fast","name":"openai/o3-fast"},{"value":"openai/o1","name":"openai/o1"},{"value":"deepseek/deepseek-v4-pro-0813","name":"deepseek/deepseek-v4-pro-0813"},{"value":"deepseek/deepseek-v4-pro","name":"deepseek/deepseek-v4-pro"},{"value":"spacexai/grok-4.7","name":"spacexai/grok-4.7"},{"value":"spacexai/grok-4.6","name":"spacexai/grok-4.6"},{"value":"spacexai/grok-4.5","name":"spacexai/grok-4.5"},{"value":"spacexai/grok-4.3","name":"spacexai/grok-4.3"},{"value":"spacexai/grok-4.20-multi-agent","name":"spacexai/grok-4.20-multi-agent"},{"value":"spacexai/grok-4.20-non-reasoning","name":"spacexai/grok-4.20-non-reasoning"},{"value":"spacexai/grok-4.20-reasoning","name":"spacexai/grok-4.20-reasoning"},{"value":"spacexai/grok-4.1-fast-non-reasoning","name":"spacexai/grok-4.1-fast-non-reasoning"},{"value":"spacexai/grok-4.1-fast-reasoning","name":"spacexai/grok-4.1-fast-reasoning"},{"value":"amazon/nova-pro","name":"amazon/nova-pro"},{"value":"anthropic/claude-fable-5.1","name":"anthropic/claude-fable-5.1"},{"value":"anthropic/claude-fable-5","name":"anthropic/claude-fable-5"},{"value":"openai/gpt-6.1-sol","name":"openai/gpt-6.1-sol"},{"value":"openai/gpt-6.1-sol-fast","name":"openai/gpt-6.1-sol-fast"},{"value":"openai/gpt-6-luna","name":"openai/gpt-6-luna"},{"value":"openai/gpt-6-luna-fast","name":"openai/gpt-6-luna-fast"},{"value":"openai/gpt-6-sol","name":"openai/gpt-6-sol"},{"value":"openai/gpt-6-sol-fast","name":"openai/gpt-6-sol-fast"},{"value":"openai/gpt-6-astra","name":"openai/gpt-6-astra"},{"value":"openai/gpt-6-astra-fast","name":"openai/gpt-6-astra-fast"},{"value":"openai/gpt-oss-safeguard-120b","name":"openai/gpt-oss-safeguard-120b"},{"value":"openai/gpt-oss-safeguard-20b","name":"openai/gpt-oss-safeguard-20b"},{"value":"openai/gpt-oss-120b","name":"openai/gpt-oss-120b"},{"value":"openai/gpt-oss-20b","name":"openai/gpt-oss-20b"},{"value":"openai/gpt-4.1","name":"openai/gpt-4.1"},{"value":"openai/gpt-4.1-fast","name":"openai/gpt-4.1-fast"},{"value":"openai/gpt-4.1-nano","name":"openai/gpt-4.1-nano"},{"value":"openai/gpt-4.1-nano-fast","name":"openai/gpt-4.1-nano-fast"},{"value":"openai/gpt-4o","name":"openai/gpt-4o"},{"value":"openai/gpt-4o-fast","name":"openai/gpt-4o-fast"},{"value":"openai/gpt-4-turbo","name":"openai/gpt-4-turbo"},{"value":"openai/gpt-3.5-turbo","name":"openai/gpt-3.5-turbo"},{"value":"google/gemma-4-26b-a4b-it","name":"google/gemma-4-26b-a4b-it"},{"value":"deepseek/deepseek-v3.2","name":"deepseek/deepseek-v3.2"},{"value":"deepseek/deepseek-v3.2-thinking","name":"deepseek/deepseek-v3.2-thinking"},{"value":"deepseek/deepseek-r1","name":"deepseek/deepseek-r1"},{"value":"meta/llama-4-maverick","name":"meta/llama-4-maverick"},{"value":"meta/llama-4-scout","name":"meta/llama-4-scout"},{"value":"meta/llama-3.3-70b","name":"meta/llama-3.3-70b"},{"value":"meta/llama-3.1-70b","name":"meta/llama-3.1-70b"},{"value":"meta/llama-3.1-8b","name":"meta/llama-3.1-8b"},{"value":"alibaba/qwen3.8-2.4t-a95b","name":"alibaba/qwen3.8-2.4t-a95b"},{"value":"alibaba/qwen3.8-max","name":"alibaba/qwen3.8-max"},{"value":"alibaba/qwen3-next-80b-a3b-instruct","name":"alibaba/qwen3-next-80b-a3b-instruct"},{"value":"alibaba/qwen3-next-80b-a3b-thinking","name":"alibaba/qwen3-next-80b-a3b-thinking"},{"value":"alibaba/qwen3-coder-30b-a3b","name":"alibaba/qwen3-coder-30b-a3b"},{"value":"alibaba/qwen3-coder","name":"alibaba/qwen3-coder"},{"value":"alibaba/qwen3-coder-next","name":"alibaba/qwen3-coder-next"},{"value":"alibaba/qwen-3-235b","name":"alibaba/qwen-3-235b"},{"value":"alibaba/qwen-3-32b","name":"alibaba/qwen-3-32b"},{"value":"fireworks/ember-1","name":"fireworks/ember-1"},{"value":"zai/glm-5.3-fast","name":"zai/glm-5.3-fast"},{"value":"zai/glm-5.3","name":"zai/glm-5.3"},{"value":"nvidia/nemotron-3.5-lightning","name":"nvidia/nemotron-3.5-lightning"},{"value":"moonshotai/kimi-k3-fast","name":"moonshotai/kimi-k3-fast"},{"value":"moonshotai/kimi-k3","name":"moonshotai/kimi-k3"},{"value":"zai/glm-5.2-fast","name":"zai/glm-5.2-fast"},{"value":"zai/glm-5.2","name":"zai/glm-5.2"},{"value":"nvidia/nemotron-3-ultra-550b-a55b","name":"nvidia/nemotron-3-ultra-550b-a55b"},{"value":"spacexai/grok-build-0.1","name":"spacexai/grok-build-0.1"},{"value":"nvidia/nemotron-3-super-120b-a12b","name":"nvidia/nemotron-3-super-120b-a12b"},{"value":"zai/glm-5","name":"zai/glm-5"},{"value":"moonshotai/kimi-k2.5","name":"moonshotai/kimi-k2.5"},{"value":"zai/glm-4.7","name":"zai/glm-4.7"},{"value":"nvidia/nemotron-nano-12b-v2-vl","name":"nvidia/nemotron-nano-12b-v2-vl"},{"value":"nvidia/nemotron-nano-9b-v2","name":"nvidia/nemotron-nano-9b-v2"},{"value":"amazon/nova-micro","name":"amazon/nova-micro"},{"value":"deepseek/deepseek-v4.1-flash","name":"deepseek/deepseek-v4.1-flash"},{"value":"deepseek/deepseek-v4-flash","name":"deepseek/deepseek-v4-flash"},{"value":"deepseek/deepseek-v4-flash-0731","name":"deepseek/deepseek-v4-flash-0731"},{"value":"zai/glm-5.3-flash","name":"zai/glm-5.3-flash"},{"value":"zai/glm-4.7-flash","name":"zai/glm-4.7-flash"},{"value":"anthropic/claude-haiku-5.5","name":"anthropic/claude-haiku-5.5"},{"value":"anthropic/claude-haiku-4.5","name":"anthropic/claude-haiku-4.5"},{"value":"anthropic/claude-3-haiku","name":"anthropic/claude-3-haiku"},{"value":"openai/gpt-5.4-mini","name":"openai/gpt-5.4-mini"},{"value":"openai/gpt-5.4-mini-fast","name":"openai/gpt-5.4-mini-fast"},{"value":"openai/gpt-5-mini","name":"openai/gpt-5-mini"},{"value":"openai/gpt-5-mini-fast","name":"openai/gpt-5-mini-fast"},{"value":"openai/gpt-4.1-mini","name":"openai/gpt-4.1-mini"},{"value":"openai/gpt-4.1-mini-fast","name":"openai/gpt-4.1-mini-fast"},{"value":"openai/o4-mini","name":"openai/o4-mini"},{"value":"openai/o4-mini-fast","name":"openai/o4-mini-fast"},{"value":"openai/o3-mini","name":"openai/o3-mini"},{"value":"openai/gpt-4o-mini","name":"openai/gpt-4o-mini"},{"value":"openai/gpt-4o-mini-fast","name":"openai/gpt-4o-mini-fast"},{"value":"google/gemini-3.8-flash","name":"google/gemini-3.8-flash"},{"value":"google/gemini-3.7-flash","name":"google/gemini-3.7-flash"},{"value":"google/gemini-3.5-flash-lite","name":"google/gemini-3.5-flash-lite"},{"value":"google/gemini-3.6-flash","name":"google/gemini-3.6-flash"},{"value":"google/gemini-3.5-flash","name":"google/gemini-3.5-flash"},{"value":"google/gemini-3.1-flash-lite","name":"google/gemini-3.1-flash-lite"},{"value":"google/gemini-3-flash","name":"google/gemini-3-flash"},{"value":"google/gemini-2.5-flash-lite","name":"google/gemini-2.5-flash-lite"},{"value":"google/gemini-2.5-flash","name":"google/gemini-2.5-flash"},{"value":"google/gemini-2.5-pro","name":"google/gemini-2.5-pro"},{"value":"minimax/minimax-m3","name":"minimax/minimax-m3"},{"value":"minimax/minimax-m2.5","name":"minimax/minimax-m2.5"},{"value":"minimax/minimax-m2.1","name":"minimax/minimax-m2.1"},{"value":"amazon/nova-2-lite","name":"amazon/nova-2-lite"},{"value":"amazon/nova-lite","name":"amazon/nova-lite"},{"value":"google/gemini-3.1-pro-preview","name":"google/gemini-3.1-pro-preview"},{"value":"spacexai/grok-4.20-multi-agent-beta","name":"spacexai/grok-4.20-multi-agent-beta"},{"value":"spacexai/grok-4.20-non-reasoning-beta","name":"spacexai/grok-4.20-non-reasoning-beta"},{"value":"spacexai/grok-4.20-reasoning-beta","name":"spacexai/grok-4.20-reasoning-beta"}]},{"id":"mode","name":"Session Mode","description":"Controls how the agent requests permission","category":"mode","type":"select","currentValue":"ask","options":[{"value":"code","name":"Code","description":"Write and modify code with full tool access","permissionMode":"auto"},{"value":"ask","name":"Ask","description":"Request permission before making any changes","permissionMode":"ask"}]},{"id":"effort","name":"Reasoning Effort","description":"Controls how much the model thinks before responding","category":"thought_level","type":"select","currentValue":"xhigh","options":[{"value":"auto","name":"default"},{"value":"low","name":"low"},{"value":"medium","name":"medium"},{"value":"high","name":"high"},{"value":"xhigh","name":"xhigh"},{"value":"max","name":"max"}]}],"modes":{"currentModeId":"ask","availableModes":[{"id":"code","name":"Code","description":"Write and modify code with full tool access"},{"id":"ask","name":"Ask","description":"Request permission before making any changes"}]}}}} {"type":"emit_inbound","label":"notification:session/update:available_commands_update","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"available_commands_update","availableCommands":[]}}}} {"type":"expect_outbound","label":"request:session/set_config_option","frame":{"kind":"request","method":"session/set_config_option","params":{"sessionId":"fx-session-01","configId":"effort","value":"medium"}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/simple/fx_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/simple/fx_transcript.ndjson index 84d694c6a157..8d35c26e783d 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/simple/fx_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/simple/fx_transcript.ndjson @@ -1,7 +1,7 @@ {"type":"transcript_start","provider":"fx","protocol":"acp.ndjson-jsonrpc","version":"1","scenario":"simple","metadata":{"generatedBy":"live-fx-recorder","fxVersion":"0.0.13","normalization":"Session ids are fx-session-NN, the workspace is , HOME is /home/fx-replay and the recording user is fx-replay. T3-owned prompt text, MCP servers and initialize params other than clientCapabilities and _meta are . User skill names, emails and token-shaped strings are removed. Timestamps are kept as recorded.","droppedFrames":0}} {"type":"expect_outbound","label":"request:initialize","frame":{"kind":"request","method":"initialize","params":{"protocolVersion":2,"info":"","capabilities":"","clientCapabilities":{"fs":{"readTextFile":false,"writeTextFile":false},"terminal":false,"session":{"notices":{}},"elicitation":{"form":{}}},"clientInfo":""}}} {"type":"emit_inbound","label":"response:initialize","frame":{"kind":"response","method":"initialize","result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"promptCapabilities":{"image":true,"audio":false,"embeddedContext":true},"mcpCapabilities":{"http":true,"sse":true,"acp":true},"sessionCapabilities":{"list":{},"resume":{},"close":{},"systemPrompt":{}},"_meta":{"fx":{"steering":true}}},"agentInfo":{"name":"fx","title":"fx","version":"0.0.13"},"authMethods":[]}}} -{"type":"expect_outbound","label":"request:session/new","frame":{"kind":"request","method":"session/new","params":{"cwd":"","mcpServers":""}}} +{"type":"expect_outbound","label":"request:session/new","frame":{"kind":"request","method":"session/new","params":{"cwd":"","mcpServers":"","_meta":{"fx":{"hooks":{"captureContent":true}}}}}} {"type":"emit_inbound","label":"response:session/new","frame":{"kind":"response","method":"session/new","result":{"sessionId":"fx-session-01","configOptions":[{"id":"provider","name":"Provider","category":"model","type":"select","currentValue":"gateway","options":[{"value":"gateway","name":"Vercel AI Gateway"},{"value":"codex","name":"Codex subscription"},{"value":"grok","name":"Grok subscription"}]},{"id":"model","name":"Model","category":"model","type":"select","currentValue":"anthropic/claude-opus-5.5","options":[{"value":"anthropic/claude-sonnet-5.5","name":"anthropic/claude-sonnet-5.5"},{"value":"anthropic/claude-opus-5.5","name":"anthropic/claude-opus-5.5"},{"value":"anthropic/claude-opus-5.5-fast","name":"anthropic/claude-opus-5.5-fast"},{"value":"anthropic/claude-opus-5","name":"anthropic/claude-opus-5"},{"value":"anthropic/claude-opus-5-fast","name":"anthropic/claude-opus-5-fast"},{"value":"anthropic/claude-sonnet-5","name":"anthropic/claude-sonnet-5"},{"value":"anthropic/claude-opus-4.8","name":"anthropic/claude-opus-4.8"},{"value":"anthropic/claude-opus-4.8-fast","name":"anthropic/claude-opus-4.8-fast"},{"value":"anthropic/claude-opus-4.7","name":"anthropic/claude-opus-4.7"},{"value":"anthropic/claude-sonnet-4.6","name":"anthropic/claude-sonnet-4.6"},{"value":"anthropic/claude-opus-4.6","name":"anthropic/claude-opus-4.6"},{"value":"anthropic/claude-opus-4.5","name":"anthropic/claude-opus-4.5"},{"value":"anthropic/claude-sonnet-4.5","name":"anthropic/claude-sonnet-4.5"},{"value":"anthropic/claude-opus-4","name":"anthropic/claude-opus-4"},{"value":"anthropic/claude-sonnet-4","name":"anthropic/claude-sonnet-4"},{"value":"openai/gpt-5.6-luna","name":"openai/gpt-5.6-luna"},{"value":"openai/gpt-5.6-luna-fast","name":"openai/gpt-5.6-luna-fast"},{"value":"openai/gpt-5.6-sol","name":"openai/gpt-5.6-sol"},{"value":"openai/gpt-5.6-sol-fast","name":"openai/gpt-5.6-sol-fast"},{"value":"openai/gpt-5.6-terra","name":"openai/gpt-5.6-terra"},{"value":"openai/gpt-5.6-terra-fast","name":"openai/gpt-5.6-terra-fast"},{"value":"openai/gpt-5.5","name":"openai/gpt-5.5"},{"value":"openai/gpt-5.5-fast","name":"openai/gpt-5.5-fast"},{"value":"openai/gpt-5.5-pro","name":"openai/gpt-5.5-pro"},{"value":"openai/gpt-5.4-nano","name":"openai/gpt-5.4-nano"},{"value":"openai/gpt-5.4","name":"openai/gpt-5.4"},{"value":"openai/gpt-5.4-fast","name":"openai/gpt-5.4-fast"},{"value":"openai/gpt-5.4-pro","name":"openai/gpt-5.4-pro"},{"value":"openai/gpt-5.3-codex","name":"openai/gpt-5.3-codex"},{"value":"openai/gpt-5.3-codex-fast","name":"openai/gpt-5.3-codex-fast"},{"value":"openai/gpt-5.2","name":"openai/gpt-5.2"},{"value":"openai/gpt-5.2-fast","name":"openai/gpt-5.2-fast"},{"value":"openai/gpt-5.2-pro","name":"openai/gpt-5.2-pro"},{"value":"openai/gpt-5.1-thinking","name":"openai/gpt-5.1-thinking"},{"value":"openai/gpt-5.1-thinking-fast","name":"openai/gpt-5.1-thinking-fast"},{"value":"openai/gpt-5-pro","name":"openai/gpt-5-pro"},{"value":"openai/gpt-5","name":"openai/gpt-5"},{"value":"openai/gpt-5-fast","name":"openai/gpt-5-fast"},{"value":"openai/gpt-5-nano","name":"openai/gpt-5-nano"},{"value":"openai/o3-pro","name":"openai/o3-pro"},{"value":"openai/o3","name":"openai/o3"},{"value":"openai/o3-fast","name":"openai/o3-fast"},{"value":"openai/o1","name":"openai/o1"},{"value":"deepseek/deepseek-v4-pro-0813","name":"deepseek/deepseek-v4-pro-0813"},{"value":"deepseek/deepseek-v4-pro","name":"deepseek/deepseek-v4-pro"},{"value":"spacexai/grok-4.7","name":"spacexai/grok-4.7"},{"value":"spacexai/grok-4.6","name":"spacexai/grok-4.6"},{"value":"spacexai/grok-4.5","name":"spacexai/grok-4.5"},{"value":"spacexai/grok-4.3","name":"spacexai/grok-4.3"},{"value":"spacexai/grok-4.20-multi-agent","name":"spacexai/grok-4.20-multi-agent"},{"value":"spacexai/grok-4.20-non-reasoning","name":"spacexai/grok-4.20-non-reasoning"},{"value":"spacexai/grok-4.20-reasoning","name":"spacexai/grok-4.20-reasoning"},{"value":"spacexai/grok-4.1-fast-non-reasoning","name":"spacexai/grok-4.1-fast-non-reasoning"},{"value":"spacexai/grok-4.1-fast-reasoning","name":"spacexai/grok-4.1-fast-reasoning"},{"value":"amazon/nova-pro","name":"amazon/nova-pro"},{"value":"anthropic/claude-fable-5.1","name":"anthropic/claude-fable-5.1"},{"value":"anthropic/claude-fable-5","name":"anthropic/claude-fable-5"},{"value":"openai/gpt-6.1-sol","name":"openai/gpt-6.1-sol"},{"value":"openai/gpt-6.1-sol-fast","name":"openai/gpt-6.1-sol-fast"},{"value":"openai/gpt-6-luna","name":"openai/gpt-6-luna"},{"value":"openai/gpt-6-luna-fast","name":"openai/gpt-6-luna-fast"},{"value":"openai/gpt-6-sol","name":"openai/gpt-6-sol"},{"value":"openai/gpt-6-sol-fast","name":"openai/gpt-6-sol-fast"},{"value":"openai/gpt-6-astra","name":"openai/gpt-6-astra"},{"value":"openai/gpt-6-astra-fast","name":"openai/gpt-6-astra-fast"},{"value":"openai/gpt-oss-safeguard-120b","name":"openai/gpt-oss-safeguard-120b"},{"value":"openai/gpt-oss-safeguard-20b","name":"openai/gpt-oss-safeguard-20b"},{"value":"openai/gpt-oss-120b","name":"openai/gpt-oss-120b"},{"value":"openai/gpt-oss-20b","name":"openai/gpt-oss-20b"},{"value":"openai/gpt-4.1","name":"openai/gpt-4.1"},{"value":"openai/gpt-4.1-fast","name":"openai/gpt-4.1-fast"},{"value":"openai/gpt-4.1-nano","name":"openai/gpt-4.1-nano"},{"value":"openai/gpt-4.1-nano-fast","name":"openai/gpt-4.1-nano-fast"},{"value":"openai/gpt-4o","name":"openai/gpt-4o"},{"value":"openai/gpt-4o-fast","name":"openai/gpt-4o-fast"},{"value":"openai/gpt-4-turbo","name":"openai/gpt-4-turbo"},{"value":"openai/gpt-3.5-turbo","name":"openai/gpt-3.5-turbo"},{"value":"google/gemma-4-26b-a4b-it","name":"google/gemma-4-26b-a4b-it"},{"value":"deepseek/deepseek-v3.2","name":"deepseek/deepseek-v3.2"},{"value":"deepseek/deepseek-v3.2-thinking","name":"deepseek/deepseek-v3.2-thinking"},{"value":"deepseek/deepseek-r1","name":"deepseek/deepseek-r1"},{"value":"meta/llama-4-maverick","name":"meta/llama-4-maverick"},{"value":"meta/llama-4-scout","name":"meta/llama-4-scout"},{"value":"meta/llama-3.3-70b","name":"meta/llama-3.3-70b"},{"value":"meta/llama-3.1-70b","name":"meta/llama-3.1-70b"},{"value":"meta/llama-3.1-8b","name":"meta/llama-3.1-8b"},{"value":"alibaba/qwen3.8-2.4t-a95b","name":"alibaba/qwen3.8-2.4t-a95b"},{"value":"alibaba/qwen3.8-max","name":"alibaba/qwen3.8-max"},{"value":"alibaba/qwen3-next-80b-a3b-instruct","name":"alibaba/qwen3-next-80b-a3b-instruct"},{"value":"alibaba/qwen3-next-80b-a3b-thinking","name":"alibaba/qwen3-next-80b-a3b-thinking"},{"value":"alibaba/qwen3-coder-30b-a3b","name":"alibaba/qwen3-coder-30b-a3b"},{"value":"alibaba/qwen3-coder","name":"alibaba/qwen3-coder"},{"value":"alibaba/qwen3-coder-next","name":"alibaba/qwen3-coder-next"},{"value":"alibaba/qwen-3-235b","name":"alibaba/qwen-3-235b"},{"value":"alibaba/qwen-3-32b","name":"alibaba/qwen-3-32b"},{"value":"fireworks/ember-1","name":"fireworks/ember-1"},{"value":"zai/glm-5.3-fast","name":"zai/glm-5.3-fast"},{"value":"zai/glm-5.3","name":"zai/glm-5.3"},{"value":"nvidia/nemotron-3.5-lightning","name":"nvidia/nemotron-3.5-lightning"},{"value":"moonshotai/kimi-k3-fast","name":"moonshotai/kimi-k3-fast"},{"value":"moonshotai/kimi-k3","name":"moonshotai/kimi-k3"},{"value":"zai/glm-5.2-fast","name":"zai/glm-5.2-fast"},{"value":"zai/glm-5.2","name":"zai/glm-5.2"},{"value":"nvidia/nemotron-3-ultra-550b-a55b","name":"nvidia/nemotron-3-ultra-550b-a55b"},{"value":"spacexai/grok-build-0.1","name":"spacexai/grok-build-0.1"},{"value":"nvidia/nemotron-3-super-120b-a12b","name":"nvidia/nemotron-3-super-120b-a12b"},{"value":"zai/glm-5","name":"zai/glm-5"},{"value":"moonshotai/kimi-k2.5","name":"moonshotai/kimi-k2.5"},{"value":"zai/glm-4.7","name":"zai/glm-4.7"},{"value":"nvidia/nemotron-nano-12b-v2-vl","name":"nvidia/nemotron-nano-12b-v2-vl"},{"value":"nvidia/nemotron-nano-9b-v2","name":"nvidia/nemotron-nano-9b-v2"},{"value":"amazon/nova-micro","name":"amazon/nova-micro"},{"value":"deepseek/deepseek-v4.1-flash","name":"deepseek/deepseek-v4.1-flash"},{"value":"deepseek/deepseek-v4-flash","name":"deepseek/deepseek-v4-flash"},{"value":"deepseek/deepseek-v4-flash-0731","name":"deepseek/deepseek-v4-flash-0731"},{"value":"zai/glm-5.3-flash","name":"zai/glm-5.3-flash"},{"value":"zai/glm-4.7-flash","name":"zai/glm-4.7-flash"},{"value":"anthropic/claude-haiku-5.5","name":"anthropic/claude-haiku-5.5"},{"value":"anthropic/claude-haiku-4.5","name":"anthropic/claude-haiku-4.5"},{"value":"anthropic/claude-3-haiku","name":"anthropic/claude-3-haiku"},{"value":"openai/gpt-5.4-mini","name":"openai/gpt-5.4-mini"},{"value":"openai/gpt-5.4-mini-fast","name":"openai/gpt-5.4-mini-fast"},{"value":"openai/gpt-5-mini","name":"openai/gpt-5-mini"},{"value":"openai/gpt-5-mini-fast","name":"openai/gpt-5-mini-fast"},{"value":"openai/gpt-4.1-mini","name":"openai/gpt-4.1-mini"},{"value":"openai/gpt-4.1-mini-fast","name":"openai/gpt-4.1-mini-fast"},{"value":"openai/o4-mini","name":"openai/o4-mini"},{"value":"openai/o4-mini-fast","name":"openai/o4-mini-fast"},{"value":"openai/o3-mini","name":"openai/o3-mini"},{"value":"openai/gpt-4o-mini","name":"openai/gpt-4o-mini"},{"value":"openai/gpt-4o-mini-fast","name":"openai/gpt-4o-mini-fast"},{"value":"google/gemini-3.8-flash","name":"google/gemini-3.8-flash"},{"value":"google/gemini-3.7-flash","name":"google/gemini-3.7-flash"},{"value":"google/gemini-3.5-flash-lite","name":"google/gemini-3.5-flash-lite"},{"value":"google/gemini-3.6-flash","name":"google/gemini-3.6-flash"},{"value":"google/gemini-3.5-flash","name":"google/gemini-3.5-flash"},{"value":"google/gemini-3.1-flash-lite","name":"google/gemini-3.1-flash-lite"},{"value":"google/gemini-3-flash","name":"google/gemini-3-flash"},{"value":"google/gemini-2.5-flash-lite","name":"google/gemini-2.5-flash-lite"},{"value":"google/gemini-2.5-flash","name":"google/gemini-2.5-flash"},{"value":"google/gemini-2.5-pro","name":"google/gemini-2.5-pro"},{"value":"minimax/minimax-m3","name":"minimax/minimax-m3"},{"value":"minimax/minimax-m2.5","name":"minimax/minimax-m2.5"},{"value":"minimax/minimax-m2.1","name":"minimax/minimax-m2.1"},{"value":"amazon/nova-2-lite","name":"amazon/nova-2-lite"},{"value":"amazon/nova-lite","name":"amazon/nova-lite"},{"value":"google/gemini-3.1-pro-preview","name":"google/gemini-3.1-pro-preview"},{"value":"spacexai/grok-4.20-multi-agent-beta","name":"spacexai/grok-4.20-multi-agent-beta"},{"value":"spacexai/grok-4.20-non-reasoning-beta","name":"spacexai/grok-4.20-non-reasoning-beta"},{"value":"spacexai/grok-4.20-reasoning-beta","name":"spacexai/grok-4.20-reasoning-beta"}]},{"id":"mode","name":"Session Mode","description":"Controls how the agent requests permission","category":"mode","type":"select","currentValue":"ask","options":[{"value":"code","name":"Code","description":"Write and modify code with full tool access","permissionMode":"auto"},{"value":"ask","name":"Ask","description":"Request permission before making any changes","permissionMode":"ask"}]},{"id":"effort","name":"Reasoning Effort","description":"Controls how much the model thinks before responding","category":"thought_level","type":"select","currentValue":"xhigh","options":[{"value":"auto","name":"default"},{"value":"low","name":"low"},{"value":"medium","name":"medium"},{"value":"high","name":"high"},{"value":"xhigh","name":"xhigh"},{"value":"max","name":"max"}]}],"modes":{"currentModeId":"ask","availableModes":[{"id":"code","name":"Code","description":"Write and modify code with full tool access"},{"id":"ask","name":"Ask","description":"Request permission before making any changes"}]}}}} {"type":"emit_inbound","label":"notification:session/update:available_commands_update","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"available_commands_update","availableCommands":[]}}}} {"type":"expect_outbound","label":"request:session/set_config_option","frame":{"kind":"request","method":"session/set_config_option","params":{"sessionId":"fx-session-01","configId":"effort","value":"medium"}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/tool_call_denied_write/fx_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/tool_call_denied_write/fx_transcript.ndjson index 6a1c048f6aff..cf3d7f8da0ef 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/tool_call_denied_write/fx_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/tool_call_denied_write/fx_transcript.ndjson @@ -1,7 +1,7 @@ {"type":"transcript_start","provider":"fx","protocol":"acp.ndjson-jsonrpc","version":"1","scenario":"tool_call_denied_write","metadata":{"generatedBy":"live-fx-recorder","fxVersion":"0.0.13","normalization":"Session ids are fx-session-NN, the workspace is , HOME is /home/fx-replay and the recording user is fx-replay. T3-owned prompt text, MCP servers and initialize params other than clientCapabilities and _meta are . User skill names, emails and token-shaped strings are removed. Timestamps are kept as recorded.","droppedFrames":0}} {"type":"expect_outbound","label":"request:initialize","frame":{"kind":"request","method":"initialize","params":{"protocolVersion":2,"info":"","capabilities":"","clientCapabilities":{"fs":{"readTextFile":false,"writeTextFile":false},"terminal":false,"session":{"notices":{}},"elicitation":{"form":{}}},"clientInfo":""}}} {"type":"emit_inbound","label":"response:initialize","frame":{"kind":"response","method":"initialize","result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"promptCapabilities":{"image":true,"audio":false,"embeddedContext":true},"mcpCapabilities":{"http":true,"sse":true,"acp":true},"sessionCapabilities":{"list":{},"resume":{},"close":{},"systemPrompt":{}},"_meta":{"fx":{"steering":true}}},"agentInfo":{"name":"fx","title":"fx","version":"0.0.13"},"authMethods":[]}}} -{"type":"expect_outbound","label":"request:session/new","frame":{"kind":"request","method":"session/new","params":{"cwd":"","mcpServers":""}}} +{"type":"expect_outbound","label":"request:session/new","frame":{"kind":"request","method":"session/new","params":{"cwd":"","mcpServers":"","_meta":{"fx":{"hooks":{"captureContent":true}}}}}} {"type":"emit_inbound","label":"response:session/new","frame":{"kind":"response","method":"session/new","result":{"sessionId":"fx-session-01","configOptions":[{"id":"provider","name":"Provider","category":"model","type":"select","currentValue":"gateway","options":[{"value":"gateway","name":"Vercel AI Gateway"},{"value":"codex","name":"Codex subscription"},{"value":"grok","name":"Grok subscription"}]},{"id":"model","name":"Model","category":"model","type":"select","currentValue":"anthropic/claude-opus-5.5","options":[{"value":"anthropic/claude-sonnet-5.5","name":"anthropic/claude-sonnet-5.5"},{"value":"anthropic/claude-opus-5.5","name":"anthropic/claude-opus-5.5"},{"value":"anthropic/claude-opus-5.5-fast","name":"anthropic/claude-opus-5.5-fast"},{"value":"anthropic/claude-opus-5","name":"anthropic/claude-opus-5"},{"value":"anthropic/claude-opus-5-fast","name":"anthropic/claude-opus-5-fast"},{"value":"anthropic/claude-sonnet-5","name":"anthropic/claude-sonnet-5"},{"value":"anthropic/claude-opus-4.8","name":"anthropic/claude-opus-4.8"},{"value":"anthropic/claude-opus-4.8-fast","name":"anthropic/claude-opus-4.8-fast"},{"value":"anthropic/claude-opus-4.7","name":"anthropic/claude-opus-4.7"},{"value":"anthropic/claude-sonnet-4.6","name":"anthropic/claude-sonnet-4.6"},{"value":"anthropic/claude-opus-4.6","name":"anthropic/claude-opus-4.6"},{"value":"anthropic/claude-opus-4.5","name":"anthropic/claude-opus-4.5"},{"value":"anthropic/claude-sonnet-4.5","name":"anthropic/claude-sonnet-4.5"},{"value":"anthropic/claude-opus-4","name":"anthropic/claude-opus-4"},{"value":"anthropic/claude-sonnet-4","name":"anthropic/claude-sonnet-4"},{"value":"openai/gpt-5.6-luna","name":"openai/gpt-5.6-luna"},{"value":"openai/gpt-5.6-luna-fast","name":"openai/gpt-5.6-luna-fast"},{"value":"openai/gpt-5.6-sol","name":"openai/gpt-5.6-sol"},{"value":"openai/gpt-5.6-sol-fast","name":"openai/gpt-5.6-sol-fast"},{"value":"openai/gpt-5.6-terra","name":"openai/gpt-5.6-terra"},{"value":"openai/gpt-5.6-terra-fast","name":"openai/gpt-5.6-terra-fast"},{"value":"openai/gpt-5.5","name":"openai/gpt-5.5"},{"value":"openai/gpt-5.5-fast","name":"openai/gpt-5.5-fast"},{"value":"openai/gpt-5.5-pro","name":"openai/gpt-5.5-pro"},{"value":"openai/gpt-5.4-nano","name":"openai/gpt-5.4-nano"},{"value":"openai/gpt-5.4","name":"openai/gpt-5.4"},{"value":"openai/gpt-5.4-fast","name":"openai/gpt-5.4-fast"},{"value":"openai/gpt-5.4-pro","name":"openai/gpt-5.4-pro"},{"value":"openai/gpt-5.3-codex","name":"openai/gpt-5.3-codex"},{"value":"openai/gpt-5.3-codex-fast","name":"openai/gpt-5.3-codex-fast"},{"value":"openai/gpt-5.2","name":"openai/gpt-5.2"},{"value":"openai/gpt-5.2-fast","name":"openai/gpt-5.2-fast"},{"value":"openai/gpt-5.2-pro","name":"openai/gpt-5.2-pro"},{"value":"openai/gpt-5.1-thinking","name":"openai/gpt-5.1-thinking"},{"value":"openai/gpt-5.1-thinking-fast","name":"openai/gpt-5.1-thinking-fast"},{"value":"openai/gpt-5-pro","name":"openai/gpt-5-pro"},{"value":"openai/gpt-5","name":"openai/gpt-5"},{"value":"openai/gpt-5-fast","name":"openai/gpt-5-fast"},{"value":"openai/gpt-5-nano","name":"openai/gpt-5-nano"},{"value":"openai/o3-pro","name":"openai/o3-pro"},{"value":"openai/o3","name":"openai/o3"},{"value":"openai/o3-fast","name":"openai/o3-fast"},{"value":"openai/o1","name":"openai/o1"},{"value":"deepseek/deepseek-v4-pro-0813","name":"deepseek/deepseek-v4-pro-0813"},{"value":"deepseek/deepseek-v4-pro","name":"deepseek/deepseek-v4-pro"},{"value":"spacexai/grok-4.7","name":"spacexai/grok-4.7"},{"value":"spacexai/grok-4.6","name":"spacexai/grok-4.6"},{"value":"spacexai/grok-4.5","name":"spacexai/grok-4.5"},{"value":"spacexai/grok-4.3","name":"spacexai/grok-4.3"},{"value":"spacexai/grok-4.20-multi-agent","name":"spacexai/grok-4.20-multi-agent"},{"value":"spacexai/grok-4.20-non-reasoning","name":"spacexai/grok-4.20-non-reasoning"},{"value":"spacexai/grok-4.20-reasoning","name":"spacexai/grok-4.20-reasoning"},{"value":"spacexai/grok-4.1-fast-non-reasoning","name":"spacexai/grok-4.1-fast-non-reasoning"},{"value":"spacexai/grok-4.1-fast-reasoning","name":"spacexai/grok-4.1-fast-reasoning"},{"value":"amazon/nova-pro","name":"amazon/nova-pro"},{"value":"anthropic/claude-fable-5.1","name":"anthropic/claude-fable-5.1"},{"value":"anthropic/claude-fable-5","name":"anthropic/claude-fable-5"},{"value":"openai/gpt-6.1-sol","name":"openai/gpt-6.1-sol"},{"value":"openai/gpt-6.1-sol-fast","name":"openai/gpt-6.1-sol-fast"},{"value":"openai/gpt-6-luna","name":"openai/gpt-6-luna"},{"value":"openai/gpt-6-luna-fast","name":"openai/gpt-6-luna-fast"},{"value":"openai/gpt-6-sol","name":"openai/gpt-6-sol"},{"value":"openai/gpt-6-sol-fast","name":"openai/gpt-6-sol-fast"},{"value":"openai/gpt-6-astra","name":"openai/gpt-6-astra"},{"value":"openai/gpt-6-astra-fast","name":"openai/gpt-6-astra-fast"},{"value":"openai/gpt-oss-safeguard-120b","name":"openai/gpt-oss-safeguard-120b"},{"value":"openai/gpt-oss-safeguard-20b","name":"openai/gpt-oss-safeguard-20b"},{"value":"openai/gpt-oss-120b","name":"openai/gpt-oss-120b"},{"value":"openai/gpt-oss-20b","name":"openai/gpt-oss-20b"},{"value":"openai/gpt-4.1","name":"openai/gpt-4.1"},{"value":"openai/gpt-4.1-fast","name":"openai/gpt-4.1-fast"},{"value":"openai/gpt-4.1-nano","name":"openai/gpt-4.1-nano"},{"value":"openai/gpt-4.1-nano-fast","name":"openai/gpt-4.1-nano-fast"},{"value":"openai/gpt-4o","name":"openai/gpt-4o"},{"value":"openai/gpt-4o-fast","name":"openai/gpt-4o-fast"},{"value":"openai/gpt-4-turbo","name":"openai/gpt-4-turbo"},{"value":"openai/gpt-3.5-turbo","name":"openai/gpt-3.5-turbo"},{"value":"google/gemma-4-26b-a4b-it","name":"google/gemma-4-26b-a4b-it"},{"value":"deepseek/deepseek-v3.2","name":"deepseek/deepseek-v3.2"},{"value":"deepseek/deepseek-v3.2-thinking","name":"deepseek/deepseek-v3.2-thinking"},{"value":"deepseek/deepseek-r1","name":"deepseek/deepseek-r1"},{"value":"meta/llama-4-maverick","name":"meta/llama-4-maverick"},{"value":"meta/llama-4-scout","name":"meta/llama-4-scout"},{"value":"meta/llama-3.3-70b","name":"meta/llama-3.3-70b"},{"value":"meta/llama-3.1-70b","name":"meta/llama-3.1-70b"},{"value":"meta/llama-3.1-8b","name":"meta/llama-3.1-8b"},{"value":"alibaba/qwen3.8-2.4t-a95b","name":"alibaba/qwen3.8-2.4t-a95b"},{"value":"alibaba/qwen3.8-max","name":"alibaba/qwen3.8-max"},{"value":"alibaba/qwen3-next-80b-a3b-instruct","name":"alibaba/qwen3-next-80b-a3b-instruct"},{"value":"alibaba/qwen3-next-80b-a3b-thinking","name":"alibaba/qwen3-next-80b-a3b-thinking"},{"value":"alibaba/qwen3-coder-30b-a3b","name":"alibaba/qwen3-coder-30b-a3b"},{"value":"alibaba/qwen3-coder","name":"alibaba/qwen3-coder"},{"value":"alibaba/qwen3-coder-next","name":"alibaba/qwen3-coder-next"},{"value":"alibaba/qwen-3-235b","name":"alibaba/qwen-3-235b"},{"value":"alibaba/qwen-3-32b","name":"alibaba/qwen-3-32b"},{"value":"fireworks/ember-1","name":"fireworks/ember-1"},{"value":"zai/glm-5.3-fast","name":"zai/glm-5.3-fast"},{"value":"zai/glm-5.3","name":"zai/glm-5.3"},{"value":"nvidia/nemotron-3.5-lightning","name":"nvidia/nemotron-3.5-lightning"},{"value":"moonshotai/kimi-k3-fast","name":"moonshotai/kimi-k3-fast"},{"value":"moonshotai/kimi-k3","name":"moonshotai/kimi-k3"},{"value":"zai/glm-5.2-fast","name":"zai/glm-5.2-fast"},{"value":"zai/glm-5.2","name":"zai/glm-5.2"},{"value":"nvidia/nemotron-3-ultra-550b-a55b","name":"nvidia/nemotron-3-ultra-550b-a55b"},{"value":"spacexai/grok-build-0.1","name":"spacexai/grok-build-0.1"},{"value":"nvidia/nemotron-3-super-120b-a12b","name":"nvidia/nemotron-3-super-120b-a12b"},{"value":"zai/glm-5","name":"zai/glm-5"},{"value":"moonshotai/kimi-k2.5","name":"moonshotai/kimi-k2.5"},{"value":"zai/glm-4.7","name":"zai/glm-4.7"},{"value":"nvidia/nemotron-nano-12b-v2-vl","name":"nvidia/nemotron-nano-12b-v2-vl"},{"value":"nvidia/nemotron-nano-9b-v2","name":"nvidia/nemotron-nano-9b-v2"},{"value":"amazon/nova-micro","name":"amazon/nova-micro"},{"value":"deepseek/deepseek-v4.1-flash","name":"deepseek/deepseek-v4.1-flash"},{"value":"deepseek/deepseek-v4-flash","name":"deepseek/deepseek-v4-flash"},{"value":"deepseek/deepseek-v4-flash-0731","name":"deepseek/deepseek-v4-flash-0731"},{"value":"zai/glm-5.3-flash","name":"zai/glm-5.3-flash"},{"value":"zai/glm-4.7-flash","name":"zai/glm-4.7-flash"},{"value":"anthropic/claude-haiku-5.5","name":"anthropic/claude-haiku-5.5"},{"value":"anthropic/claude-haiku-4.5","name":"anthropic/claude-haiku-4.5"},{"value":"anthropic/claude-3-haiku","name":"anthropic/claude-3-haiku"},{"value":"openai/gpt-5.4-mini","name":"openai/gpt-5.4-mini"},{"value":"openai/gpt-5.4-mini-fast","name":"openai/gpt-5.4-mini-fast"},{"value":"openai/gpt-5-mini","name":"openai/gpt-5-mini"},{"value":"openai/gpt-5-mini-fast","name":"openai/gpt-5-mini-fast"},{"value":"openai/gpt-4.1-mini","name":"openai/gpt-4.1-mini"},{"value":"openai/gpt-4.1-mini-fast","name":"openai/gpt-4.1-mini-fast"},{"value":"openai/o4-mini","name":"openai/o4-mini"},{"value":"openai/o4-mini-fast","name":"openai/o4-mini-fast"},{"value":"openai/o3-mini","name":"openai/o3-mini"},{"value":"openai/gpt-4o-mini","name":"openai/gpt-4o-mini"},{"value":"openai/gpt-4o-mini-fast","name":"openai/gpt-4o-mini-fast"},{"value":"google/gemini-3.8-flash","name":"google/gemini-3.8-flash"},{"value":"google/gemini-3.7-flash","name":"google/gemini-3.7-flash"},{"value":"google/gemini-3.5-flash-lite","name":"google/gemini-3.5-flash-lite"},{"value":"google/gemini-3.6-flash","name":"google/gemini-3.6-flash"},{"value":"google/gemini-3.5-flash","name":"google/gemini-3.5-flash"},{"value":"google/gemini-3.1-flash-lite","name":"google/gemini-3.1-flash-lite"},{"value":"google/gemini-3-flash","name":"google/gemini-3-flash"},{"value":"google/gemini-2.5-flash-lite","name":"google/gemini-2.5-flash-lite"},{"value":"google/gemini-2.5-flash","name":"google/gemini-2.5-flash"},{"value":"google/gemini-2.5-pro","name":"google/gemini-2.5-pro"},{"value":"minimax/minimax-m3","name":"minimax/minimax-m3"},{"value":"minimax/minimax-m2.5","name":"minimax/minimax-m2.5"},{"value":"minimax/minimax-m2.1","name":"minimax/minimax-m2.1"},{"value":"amazon/nova-2-lite","name":"amazon/nova-2-lite"},{"value":"amazon/nova-lite","name":"amazon/nova-lite"},{"value":"google/gemini-3.1-pro-preview","name":"google/gemini-3.1-pro-preview"},{"value":"spacexai/grok-4.20-multi-agent-beta","name":"spacexai/grok-4.20-multi-agent-beta"},{"value":"spacexai/grok-4.20-non-reasoning-beta","name":"spacexai/grok-4.20-non-reasoning-beta"},{"value":"spacexai/grok-4.20-reasoning-beta","name":"spacexai/grok-4.20-reasoning-beta"}]},{"id":"mode","name":"Session Mode","description":"Controls how the agent requests permission","category":"mode","type":"select","currentValue":"ask","options":[{"value":"code","name":"Code","description":"Write and modify code with full tool access","permissionMode":"auto"},{"value":"ask","name":"Ask","description":"Request permission before making any changes","permissionMode":"ask"}]},{"id":"effort","name":"Reasoning Effort","description":"Controls how much the model thinks before responding","category":"thought_level","type":"select","currentValue":"xhigh","options":[{"value":"auto","name":"default"},{"value":"low","name":"low"},{"value":"medium","name":"medium"},{"value":"high","name":"high"},{"value":"xhigh","name":"xhigh"},{"value":"max","name":"max"}]}],"modes":{"currentModeId":"ask","availableModes":[{"id":"code","name":"Code","description":"Write and modify code with full tool access"},{"id":"ask","name":"Ask","description":"Request permission before making any changes"}]}}}} {"type":"emit_inbound","label":"notification:session/update:available_commands_update","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"available_commands_update","availableCommands":[]}}}} {"type":"expect_outbound","label":"request:session/set_config_option","frame":{"kind":"request","method":"session/set_config_option","params":{"sessionId":"fx-session-01","configId":"effort","value":"medium"}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/fx_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/fx_transcript.ndjson index 34f26d8efb33..9a6ef4fe0501 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/fx_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/tool_call_read_only_on_request/fx_transcript.ndjson @@ -1,7 +1,7 @@ {"type":"transcript_start","provider":"fx","protocol":"acp.ndjson-jsonrpc","version":"1","scenario":"tool_call_read_only_on_request","metadata":{"generatedBy":"live-fx-recorder","fxVersion":"0.0.13","normalization":"Session ids are fx-session-NN, the workspace is , HOME is /home/fx-replay and the recording user is fx-replay. T3-owned prompt text, MCP servers and initialize params other than clientCapabilities and _meta are . User skill names, emails and token-shaped strings are removed. Timestamps are kept as recorded.","droppedFrames":0}} {"type":"expect_outbound","label":"request:initialize","frame":{"kind":"request","method":"initialize","params":{"protocolVersion":2,"info":"","capabilities":"","clientCapabilities":{"fs":{"readTextFile":false,"writeTextFile":false},"terminal":false,"session":{"notices":{}},"elicitation":{"form":{}}},"clientInfo":""}}} {"type":"emit_inbound","label":"response:initialize","frame":{"kind":"response","method":"initialize","result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"promptCapabilities":{"image":true,"audio":false,"embeddedContext":true},"mcpCapabilities":{"http":true,"sse":true,"acp":true},"sessionCapabilities":{"list":{},"resume":{},"close":{},"systemPrompt":{}},"_meta":{"fx":{"steering":true}}},"agentInfo":{"name":"fx","title":"fx","version":"0.0.13"},"authMethods":[]}}} -{"type":"expect_outbound","label":"request:session/new","frame":{"kind":"request","method":"session/new","params":{"cwd":"","mcpServers":""}}} +{"type":"expect_outbound","label":"request:session/new","frame":{"kind":"request","method":"session/new","params":{"cwd":"","mcpServers":"","_meta":{"fx":{"hooks":{"captureContent":true}}}}}} {"type":"emit_inbound","label":"response:session/new","frame":{"kind":"response","method":"session/new","result":{"sessionId":"fx-session-01","configOptions":[{"id":"provider","name":"Provider","category":"model","type":"select","currentValue":"gateway","options":[{"value":"gateway","name":"Vercel AI Gateway"},{"value":"codex","name":"Codex subscription"},{"value":"grok","name":"Grok subscription"}]},{"id":"model","name":"Model","category":"model","type":"select","currentValue":"anthropic/claude-opus-5.5","options":[{"value":"anthropic/claude-sonnet-5.5","name":"anthropic/claude-sonnet-5.5"},{"value":"anthropic/claude-opus-5.5","name":"anthropic/claude-opus-5.5"},{"value":"anthropic/claude-opus-5.5-fast","name":"anthropic/claude-opus-5.5-fast"},{"value":"anthropic/claude-opus-5","name":"anthropic/claude-opus-5"},{"value":"anthropic/claude-opus-5-fast","name":"anthropic/claude-opus-5-fast"},{"value":"anthropic/claude-sonnet-5","name":"anthropic/claude-sonnet-5"},{"value":"anthropic/claude-opus-4.8","name":"anthropic/claude-opus-4.8"},{"value":"anthropic/claude-opus-4.8-fast","name":"anthropic/claude-opus-4.8-fast"},{"value":"anthropic/claude-opus-4.7","name":"anthropic/claude-opus-4.7"},{"value":"anthropic/claude-sonnet-4.6","name":"anthropic/claude-sonnet-4.6"},{"value":"anthropic/claude-opus-4.6","name":"anthropic/claude-opus-4.6"},{"value":"anthropic/claude-opus-4.5","name":"anthropic/claude-opus-4.5"},{"value":"anthropic/claude-sonnet-4.5","name":"anthropic/claude-sonnet-4.5"},{"value":"anthropic/claude-opus-4","name":"anthropic/claude-opus-4"},{"value":"anthropic/claude-sonnet-4","name":"anthropic/claude-sonnet-4"},{"value":"openai/gpt-5.6-luna","name":"openai/gpt-5.6-luna"},{"value":"openai/gpt-5.6-luna-fast","name":"openai/gpt-5.6-luna-fast"},{"value":"openai/gpt-5.6-sol","name":"openai/gpt-5.6-sol"},{"value":"openai/gpt-5.6-sol-fast","name":"openai/gpt-5.6-sol-fast"},{"value":"openai/gpt-5.6-terra","name":"openai/gpt-5.6-terra"},{"value":"openai/gpt-5.6-terra-fast","name":"openai/gpt-5.6-terra-fast"},{"value":"openai/gpt-5.5","name":"openai/gpt-5.5"},{"value":"openai/gpt-5.5-fast","name":"openai/gpt-5.5-fast"},{"value":"openai/gpt-5.5-pro","name":"openai/gpt-5.5-pro"},{"value":"openai/gpt-5.4-nano","name":"openai/gpt-5.4-nano"},{"value":"openai/gpt-5.4","name":"openai/gpt-5.4"},{"value":"openai/gpt-5.4-fast","name":"openai/gpt-5.4-fast"},{"value":"openai/gpt-5.4-pro","name":"openai/gpt-5.4-pro"},{"value":"openai/gpt-5.3-codex","name":"openai/gpt-5.3-codex"},{"value":"openai/gpt-5.3-codex-fast","name":"openai/gpt-5.3-codex-fast"},{"value":"openai/gpt-5.2","name":"openai/gpt-5.2"},{"value":"openai/gpt-5.2-fast","name":"openai/gpt-5.2-fast"},{"value":"openai/gpt-5.2-pro","name":"openai/gpt-5.2-pro"},{"value":"openai/gpt-5.1-thinking","name":"openai/gpt-5.1-thinking"},{"value":"openai/gpt-5.1-thinking-fast","name":"openai/gpt-5.1-thinking-fast"},{"value":"openai/gpt-5-pro","name":"openai/gpt-5-pro"},{"value":"openai/gpt-5","name":"openai/gpt-5"},{"value":"openai/gpt-5-fast","name":"openai/gpt-5-fast"},{"value":"openai/gpt-5-nano","name":"openai/gpt-5-nano"},{"value":"openai/o3-pro","name":"openai/o3-pro"},{"value":"openai/o3","name":"openai/o3"},{"value":"openai/o3-fast","name":"openai/o3-fast"},{"value":"openai/o1","name":"openai/o1"},{"value":"deepseek/deepseek-v4-pro-0813","name":"deepseek/deepseek-v4-pro-0813"},{"value":"deepseek/deepseek-v4-pro","name":"deepseek/deepseek-v4-pro"},{"value":"spacexai/grok-4.7","name":"spacexai/grok-4.7"},{"value":"spacexai/grok-4.6","name":"spacexai/grok-4.6"},{"value":"spacexai/grok-4.5","name":"spacexai/grok-4.5"},{"value":"spacexai/grok-4.3","name":"spacexai/grok-4.3"},{"value":"spacexai/grok-4.20-multi-agent","name":"spacexai/grok-4.20-multi-agent"},{"value":"spacexai/grok-4.20-non-reasoning","name":"spacexai/grok-4.20-non-reasoning"},{"value":"spacexai/grok-4.20-reasoning","name":"spacexai/grok-4.20-reasoning"},{"value":"spacexai/grok-4.1-fast-non-reasoning","name":"spacexai/grok-4.1-fast-non-reasoning"},{"value":"spacexai/grok-4.1-fast-reasoning","name":"spacexai/grok-4.1-fast-reasoning"},{"value":"amazon/nova-pro","name":"amazon/nova-pro"},{"value":"anthropic/claude-fable-5.1","name":"anthropic/claude-fable-5.1"},{"value":"anthropic/claude-fable-5","name":"anthropic/claude-fable-5"},{"value":"openai/gpt-6.1-sol","name":"openai/gpt-6.1-sol"},{"value":"openai/gpt-6.1-sol-fast","name":"openai/gpt-6.1-sol-fast"},{"value":"openai/gpt-6-luna","name":"openai/gpt-6-luna"},{"value":"openai/gpt-6-luna-fast","name":"openai/gpt-6-luna-fast"},{"value":"openai/gpt-6-sol","name":"openai/gpt-6-sol"},{"value":"openai/gpt-6-sol-fast","name":"openai/gpt-6-sol-fast"},{"value":"openai/gpt-6-astra","name":"openai/gpt-6-astra"},{"value":"openai/gpt-6-astra-fast","name":"openai/gpt-6-astra-fast"},{"value":"openai/gpt-oss-safeguard-120b","name":"openai/gpt-oss-safeguard-120b"},{"value":"openai/gpt-oss-safeguard-20b","name":"openai/gpt-oss-safeguard-20b"},{"value":"openai/gpt-oss-120b","name":"openai/gpt-oss-120b"},{"value":"openai/gpt-oss-20b","name":"openai/gpt-oss-20b"},{"value":"openai/gpt-4.1","name":"openai/gpt-4.1"},{"value":"openai/gpt-4.1-fast","name":"openai/gpt-4.1-fast"},{"value":"openai/gpt-4.1-nano","name":"openai/gpt-4.1-nano"},{"value":"openai/gpt-4.1-nano-fast","name":"openai/gpt-4.1-nano-fast"},{"value":"openai/gpt-4o","name":"openai/gpt-4o"},{"value":"openai/gpt-4o-fast","name":"openai/gpt-4o-fast"},{"value":"openai/gpt-4-turbo","name":"openai/gpt-4-turbo"},{"value":"openai/gpt-3.5-turbo","name":"openai/gpt-3.5-turbo"},{"value":"google/gemma-4-26b-a4b-it","name":"google/gemma-4-26b-a4b-it"},{"value":"deepseek/deepseek-v3.2","name":"deepseek/deepseek-v3.2"},{"value":"deepseek/deepseek-v3.2-thinking","name":"deepseek/deepseek-v3.2-thinking"},{"value":"deepseek/deepseek-r1","name":"deepseek/deepseek-r1"},{"value":"meta/llama-4-maverick","name":"meta/llama-4-maverick"},{"value":"meta/llama-4-scout","name":"meta/llama-4-scout"},{"value":"meta/llama-3.3-70b","name":"meta/llama-3.3-70b"},{"value":"meta/llama-3.1-70b","name":"meta/llama-3.1-70b"},{"value":"meta/llama-3.1-8b","name":"meta/llama-3.1-8b"},{"value":"alibaba/qwen3.8-2.4t-a95b","name":"alibaba/qwen3.8-2.4t-a95b"},{"value":"alibaba/qwen3.8-max","name":"alibaba/qwen3.8-max"},{"value":"alibaba/qwen3-next-80b-a3b-instruct","name":"alibaba/qwen3-next-80b-a3b-instruct"},{"value":"alibaba/qwen3-next-80b-a3b-thinking","name":"alibaba/qwen3-next-80b-a3b-thinking"},{"value":"alibaba/qwen3-coder-30b-a3b","name":"alibaba/qwen3-coder-30b-a3b"},{"value":"alibaba/qwen3-coder","name":"alibaba/qwen3-coder"},{"value":"alibaba/qwen3-coder-next","name":"alibaba/qwen3-coder-next"},{"value":"alibaba/qwen-3-235b","name":"alibaba/qwen-3-235b"},{"value":"alibaba/qwen-3-32b","name":"alibaba/qwen-3-32b"},{"value":"fireworks/ember-1","name":"fireworks/ember-1"},{"value":"zai/glm-5.3-fast","name":"zai/glm-5.3-fast"},{"value":"zai/glm-5.3","name":"zai/glm-5.3"},{"value":"nvidia/nemotron-3.5-lightning","name":"nvidia/nemotron-3.5-lightning"},{"value":"moonshotai/kimi-k3-fast","name":"moonshotai/kimi-k3-fast"},{"value":"moonshotai/kimi-k3","name":"moonshotai/kimi-k3"},{"value":"zai/glm-5.2-fast","name":"zai/glm-5.2-fast"},{"value":"zai/glm-5.2","name":"zai/glm-5.2"},{"value":"nvidia/nemotron-3-ultra-550b-a55b","name":"nvidia/nemotron-3-ultra-550b-a55b"},{"value":"spacexai/grok-build-0.1","name":"spacexai/grok-build-0.1"},{"value":"nvidia/nemotron-3-super-120b-a12b","name":"nvidia/nemotron-3-super-120b-a12b"},{"value":"zai/glm-5","name":"zai/glm-5"},{"value":"moonshotai/kimi-k2.5","name":"moonshotai/kimi-k2.5"},{"value":"zai/glm-4.7","name":"zai/glm-4.7"},{"value":"nvidia/nemotron-nano-12b-v2-vl","name":"nvidia/nemotron-nano-12b-v2-vl"},{"value":"nvidia/nemotron-nano-9b-v2","name":"nvidia/nemotron-nano-9b-v2"},{"value":"amazon/nova-micro","name":"amazon/nova-micro"},{"value":"deepseek/deepseek-v4.1-flash","name":"deepseek/deepseek-v4.1-flash"},{"value":"deepseek/deepseek-v4-flash","name":"deepseek/deepseek-v4-flash"},{"value":"deepseek/deepseek-v4-flash-0731","name":"deepseek/deepseek-v4-flash-0731"},{"value":"zai/glm-5.3-flash","name":"zai/glm-5.3-flash"},{"value":"zai/glm-4.7-flash","name":"zai/glm-4.7-flash"},{"value":"anthropic/claude-haiku-5.5","name":"anthropic/claude-haiku-5.5"},{"value":"anthropic/claude-haiku-4.5","name":"anthropic/claude-haiku-4.5"},{"value":"anthropic/claude-3-haiku","name":"anthropic/claude-3-haiku"},{"value":"openai/gpt-5.4-mini","name":"openai/gpt-5.4-mini"},{"value":"openai/gpt-5.4-mini-fast","name":"openai/gpt-5.4-mini-fast"},{"value":"openai/gpt-5-mini","name":"openai/gpt-5-mini"},{"value":"openai/gpt-5-mini-fast","name":"openai/gpt-5-mini-fast"},{"value":"openai/gpt-4.1-mini","name":"openai/gpt-4.1-mini"},{"value":"openai/gpt-4.1-mini-fast","name":"openai/gpt-4.1-mini-fast"},{"value":"openai/o4-mini","name":"openai/o4-mini"},{"value":"openai/o4-mini-fast","name":"openai/o4-mini-fast"},{"value":"openai/o3-mini","name":"openai/o3-mini"},{"value":"openai/gpt-4o-mini","name":"openai/gpt-4o-mini"},{"value":"openai/gpt-4o-mini-fast","name":"openai/gpt-4o-mini-fast"},{"value":"google/gemini-3.8-flash","name":"google/gemini-3.8-flash"},{"value":"google/gemini-3.7-flash","name":"google/gemini-3.7-flash"},{"value":"google/gemini-3.5-flash-lite","name":"google/gemini-3.5-flash-lite"},{"value":"google/gemini-3.6-flash","name":"google/gemini-3.6-flash"},{"value":"google/gemini-3.5-flash","name":"google/gemini-3.5-flash"},{"value":"google/gemini-3.1-flash-lite","name":"google/gemini-3.1-flash-lite"},{"value":"google/gemini-3-flash","name":"google/gemini-3-flash"},{"value":"google/gemini-2.5-flash-lite","name":"google/gemini-2.5-flash-lite"},{"value":"google/gemini-2.5-flash","name":"google/gemini-2.5-flash"},{"value":"google/gemini-2.5-pro","name":"google/gemini-2.5-pro"},{"value":"minimax/minimax-m3","name":"minimax/minimax-m3"},{"value":"minimax/minimax-m2.5","name":"minimax/minimax-m2.5"},{"value":"minimax/minimax-m2.1","name":"minimax/minimax-m2.1"},{"value":"amazon/nova-2-lite","name":"amazon/nova-2-lite"},{"value":"amazon/nova-lite","name":"amazon/nova-lite"},{"value":"google/gemini-3.1-pro-preview","name":"google/gemini-3.1-pro-preview"},{"value":"spacexai/grok-4.20-multi-agent-beta","name":"spacexai/grok-4.20-multi-agent-beta"},{"value":"spacexai/grok-4.20-non-reasoning-beta","name":"spacexai/grok-4.20-non-reasoning-beta"},{"value":"spacexai/grok-4.20-reasoning-beta","name":"spacexai/grok-4.20-reasoning-beta"}]},{"id":"mode","name":"Session Mode","description":"Controls how the agent requests permission","category":"mode","type":"select","currentValue":"ask","options":[{"value":"code","name":"Code","description":"Write and modify code with full tool access","permissionMode":"auto"},{"value":"ask","name":"Ask","description":"Request permission before making any changes","permissionMode":"ask"}]},{"id":"effort","name":"Reasoning Effort","description":"Controls how much the model thinks before responding","category":"thought_level","type":"select","currentValue":"xhigh","options":[{"value":"auto","name":"default"},{"value":"low","name":"low"},{"value":"medium","name":"medium"},{"value":"high","name":"high"},{"value":"xhigh","name":"xhigh"},{"value":"max","name":"max"}]}],"modes":{"currentModeId":"ask","availableModes":[{"id":"code","name":"Code","description":"Write and modify code with full tool access"},{"id":"ask","name":"Ask","description":"Request permission before making any changes"}]}}}} {"type":"emit_inbound","label":"notification:session/update:available_commands_update","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"available_commands_update","availableCommands":[]}}}} {"type":"expect_outbound","label":"request:session/set_config_option","frame":{"kind":"request","method":"session/set_config_option","params":{"sessionId":"fx-session-01","configId":"effort","value":"medium"}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/fx_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/fx_transcript.ndjson index c0e0b356fdd8..d88418614b75 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/fx_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt/fx_transcript.ndjson @@ -1,7 +1,7 @@ {"type":"transcript_start","provider":"fx","protocol":"acp.ndjson-jsonrpc","version":"1","scenario":"turn_interrupt","metadata":{"generatedBy":"live-fx-recorder","fxVersion":"0.0.13","normalization":"Session ids are fx-session-NN, the workspace is , HOME is /home/fx-replay and the recording user is fx-replay. T3-owned prompt text, MCP servers and initialize params other than clientCapabilities and _meta are . User skill names, emails and token-shaped strings are removed. Timestamps are kept as recorded.","droppedFrames":0}} {"type":"expect_outbound","label":"request:initialize","frame":{"kind":"request","method":"initialize","params":{"protocolVersion":2,"info":"","capabilities":"","clientCapabilities":{"fs":{"readTextFile":false,"writeTextFile":false},"terminal":false,"session":{"notices":{}},"elicitation":{"form":{}}},"clientInfo":""}}} {"type":"emit_inbound","label":"response:initialize","frame":{"kind":"response","method":"initialize","result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"promptCapabilities":{"image":true,"audio":false,"embeddedContext":true},"mcpCapabilities":{"http":true,"sse":true,"acp":true},"sessionCapabilities":{"list":{},"resume":{},"close":{},"systemPrompt":{}},"_meta":{"fx":{"steering":true}}},"agentInfo":{"name":"fx","title":"fx","version":"0.0.13"},"authMethods":[]}}} -{"type":"expect_outbound","label":"request:session/new","frame":{"kind":"request","method":"session/new","params":{"cwd":"","mcpServers":""}}} +{"type":"expect_outbound","label":"request:session/new","frame":{"kind":"request","method":"session/new","params":{"cwd":"","mcpServers":"","_meta":{"fx":{"hooks":{"captureContent":true}}}}}} {"type":"emit_inbound","label":"response:session/new","frame":{"kind":"response","method":"session/new","result":{"sessionId":"fx-session-01","configOptions":[{"id":"provider","name":"Provider","category":"model","type":"select","currentValue":"gateway","options":[{"value":"gateway","name":"Vercel AI Gateway"},{"value":"codex","name":"Codex subscription"},{"value":"grok","name":"Grok subscription"}]},{"id":"model","name":"Model","category":"model","type":"select","currentValue":"anthropic/claude-opus-5.5","options":[{"value":"anthropic/claude-sonnet-5.5","name":"anthropic/claude-sonnet-5.5"},{"value":"anthropic/claude-opus-5.5","name":"anthropic/claude-opus-5.5"},{"value":"anthropic/claude-opus-5.5-fast","name":"anthropic/claude-opus-5.5-fast"},{"value":"anthropic/claude-opus-5","name":"anthropic/claude-opus-5"},{"value":"anthropic/claude-opus-5-fast","name":"anthropic/claude-opus-5-fast"},{"value":"anthropic/claude-sonnet-5","name":"anthropic/claude-sonnet-5"},{"value":"anthropic/claude-opus-4.8","name":"anthropic/claude-opus-4.8"},{"value":"anthropic/claude-opus-4.8-fast","name":"anthropic/claude-opus-4.8-fast"},{"value":"anthropic/claude-opus-4.7","name":"anthropic/claude-opus-4.7"},{"value":"anthropic/claude-sonnet-4.6","name":"anthropic/claude-sonnet-4.6"},{"value":"anthropic/claude-opus-4.6","name":"anthropic/claude-opus-4.6"},{"value":"anthropic/claude-opus-4.5","name":"anthropic/claude-opus-4.5"},{"value":"anthropic/claude-sonnet-4.5","name":"anthropic/claude-sonnet-4.5"},{"value":"anthropic/claude-opus-4","name":"anthropic/claude-opus-4"},{"value":"anthropic/claude-sonnet-4","name":"anthropic/claude-sonnet-4"},{"value":"openai/gpt-5.6-luna","name":"openai/gpt-5.6-luna"},{"value":"openai/gpt-5.6-luna-fast","name":"openai/gpt-5.6-luna-fast"},{"value":"openai/gpt-5.6-sol","name":"openai/gpt-5.6-sol"},{"value":"openai/gpt-5.6-sol-fast","name":"openai/gpt-5.6-sol-fast"},{"value":"openai/gpt-5.6-terra","name":"openai/gpt-5.6-terra"},{"value":"openai/gpt-5.6-terra-fast","name":"openai/gpt-5.6-terra-fast"},{"value":"openai/gpt-5.5","name":"openai/gpt-5.5"},{"value":"openai/gpt-5.5-fast","name":"openai/gpt-5.5-fast"},{"value":"openai/gpt-5.5-pro","name":"openai/gpt-5.5-pro"},{"value":"openai/gpt-5.4-nano","name":"openai/gpt-5.4-nano"},{"value":"openai/gpt-5.4","name":"openai/gpt-5.4"},{"value":"openai/gpt-5.4-fast","name":"openai/gpt-5.4-fast"},{"value":"openai/gpt-5.4-pro","name":"openai/gpt-5.4-pro"},{"value":"openai/gpt-5.3-codex","name":"openai/gpt-5.3-codex"},{"value":"openai/gpt-5.3-codex-fast","name":"openai/gpt-5.3-codex-fast"},{"value":"openai/gpt-5.2","name":"openai/gpt-5.2"},{"value":"openai/gpt-5.2-fast","name":"openai/gpt-5.2-fast"},{"value":"openai/gpt-5.2-pro","name":"openai/gpt-5.2-pro"},{"value":"openai/gpt-5.1-thinking","name":"openai/gpt-5.1-thinking"},{"value":"openai/gpt-5.1-thinking-fast","name":"openai/gpt-5.1-thinking-fast"},{"value":"openai/gpt-5-pro","name":"openai/gpt-5-pro"},{"value":"openai/gpt-5","name":"openai/gpt-5"},{"value":"openai/gpt-5-fast","name":"openai/gpt-5-fast"},{"value":"openai/gpt-5-nano","name":"openai/gpt-5-nano"},{"value":"openai/o3-pro","name":"openai/o3-pro"},{"value":"openai/o3","name":"openai/o3"},{"value":"openai/o3-fast","name":"openai/o3-fast"},{"value":"openai/o1","name":"openai/o1"},{"value":"deepseek/deepseek-v4-pro-0813","name":"deepseek/deepseek-v4-pro-0813"},{"value":"deepseek/deepseek-v4-pro","name":"deepseek/deepseek-v4-pro"},{"value":"spacexai/grok-4.7","name":"spacexai/grok-4.7"},{"value":"spacexai/grok-4.6","name":"spacexai/grok-4.6"},{"value":"spacexai/grok-4.5","name":"spacexai/grok-4.5"},{"value":"spacexai/grok-4.3","name":"spacexai/grok-4.3"},{"value":"spacexai/grok-4.20-multi-agent","name":"spacexai/grok-4.20-multi-agent"},{"value":"spacexai/grok-4.20-non-reasoning","name":"spacexai/grok-4.20-non-reasoning"},{"value":"spacexai/grok-4.20-reasoning","name":"spacexai/grok-4.20-reasoning"},{"value":"spacexai/grok-4.1-fast-non-reasoning","name":"spacexai/grok-4.1-fast-non-reasoning"},{"value":"spacexai/grok-4.1-fast-reasoning","name":"spacexai/grok-4.1-fast-reasoning"},{"value":"amazon/nova-pro","name":"amazon/nova-pro"},{"value":"anthropic/claude-fable-5.1","name":"anthropic/claude-fable-5.1"},{"value":"anthropic/claude-fable-5","name":"anthropic/claude-fable-5"},{"value":"openai/gpt-6.1-sol","name":"openai/gpt-6.1-sol"},{"value":"openai/gpt-6.1-sol-fast","name":"openai/gpt-6.1-sol-fast"},{"value":"openai/gpt-6-luna","name":"openai/gpt-6-luna"},{"value":"openai/gpt-6-luna-fast","name":"openai/gpt-6-luna-fast"},{"value":"openai/gpt-6-sol","name":"openai/gpt-6-sol"},{"value":"openai/gpt-6-sol-fast","name":"openai/gpt-6-sol-fast"},{"value":"openai/gpt-6-astra","name":"openai/gpt-6-astra"},{"value":"openai/gpt-6-astra-fast","name":"openai/gpt-6-astra-fast"},{"value":"openai/gpt-oss-safeguard-120b","name":"openai/gpt-oss-safeguard-120b"},{"value":"openai/gpt-oss-safeguard-20b","name":"openai/gpt-oss-safeguard-20b"},{"value":"openai/gpt-oss-120b","name":"openai/gpt-oss-120b"},{"value":"openai/gpt-oss-20b","name":"openai/gpt-oss-20b"},{"value":"openai/gpt-4.1","name":"openai/gpt-4.1"},{"value":"openai/gpt-4.1-fast","name":"openai/gpt-4.1-fast"},{"value":"openai/gpt-4.1-nano","name":"openai/gpt-4.1-nano"},{"value":"openai/gpt-4.1-nano-fast","name":"openai/gpt-4.1-nano-fast"},{"value":"openai/gpt-4o","name":"openai/gpt-4o"},{"value":"openai/gpt-4o-fast","name":"openai/gpt-4o-fast"},{"value":"openai/gpt-4-turbo","name":"openai/gpt-4-turbo"},{"value":"openai/gpt-3.5-turbo","name":"openai/gpt-3.5-turbo"},{"value":"google/gemma-4-26b-a4b-it","name":"google/gemma-4-26b-a4b-it"},{"value":"deepseek/deepseek-v3.2","name":"deepseek/deepseek-v3.2"},{"value":"deepseek/deepseek-v3.2-thinking","name":"deepseek/deepseek-v3.2-thinking"},{"value":"deepseek/deepseek-r1","name":"deepseek/deepseek-r1"},{"value":"meta/llama-4-maverick","name":"meta/llama-4-maverick"},{"value":"meta/llama-4-scout","name":"meta/llama-4-scout"},{"value":"meta/llama-3.3-70b","name":"meta/llama-3.3-70b"},{"value":"meta/llama-3.1-70b","name":"meta/llama-3.1-70b"},{"value":"meta/llama-3.1-8b","name":"meta/llama-3.1-8b"},{"value":"alibaba/qwen3.8-2.4t-a95b","name":"alibaba/qwen3.8-2.4t-a95b"},{"value":"alibaba/qwen3.8-max","name":"alibaba/qwen3.8-max"},{"value":"alibaba/qwen3-next-80b-a3b-instruct","name":"alibaba/qwen3-next-80b-a3b-instruct"},{"value":"alibaba/qwen3-next-80b-a3b-thinking","name":"alibaba/qwen3-next-80b-a3b-thinking"},{"value":"alibaba/qwen3-coder-30b-a3b","name":"alibaba/qwen3-coder-30b-a3b"},{"value":"alibaba/qwen3-coder","name":"alibaba/qwen3-coder"},{"value":"alibaba/qwen3-coder-next","name":"alibaba/qwen3-coder-next"},{"value":"alibaba/qwen-3-235b","name":"alibaba/qwen-3-235b"},{"value":"alibaba/qwen-3-32b","name":"alibaba/qwen-3-32b"},{"value":"fireworks/ember-1","name":"fireworks/ember-1"},{"value":"zai/glm-5.3-fast","name":"zai/glm-5.3-fast"},{"value":"zai/glm-5.3","name":"zai/glm-5.3"},{"value":"nvidia/nemotron-3.5-lightning","name":"nvidia/nemotron-3.5-lightning"},{"value":"moonshotai/kimi-k3-fast","name":"moonshotai/kimi-k3-fast"},{"value":"moonshotai/kimi-k3","name":"moonshotai/kimi-k3"},{"value":"zai/glm-5.2-fast","name":"zai/glm-5.2-fast"},{"value":"zai/glm-5.2","name":"zai/glm-5.2"},{"value":"nvidia/nemotron-3-ultra-550b-a55b","name":"nvidia/nemotron-3-ultra-550b-a55b"},{"value":"spacexai/grok-build-0.1","name":"spacexai/grok-build-0.1"},{"value":"nvidia/nemotron-3-super-120b-a12b","name":"nvidia/nemotron-3-super-120b-a12b"},{"value":"zai/glm-5","name":"zai/glm-5"},{"value":"moonshotai/kimi-k2.5","name":"moonshotai/kimi-k2.5"},{"value":"zai/glm-4.7","name":"zai/glm-4.7"},{"value":"nvidia/nemotron-nano-12b-v2-vl","name":"nvidia/nemotron-nano-12b-v2-vl"},{"value":"nvidia/nemotron-nano-9b-v2","name":"nvidia/nemotron-nano-9b-v2"},{"value":"amazon/nova-micro","name":"amazon/nova-micro"},{"value":"deepseek/deepseek-v4.1-flash","name":"deepseek/deepseek-v4.1-flash"},{"value":"deepseek/deepseek-v4-flash","name":"deepseek/deepseek-v4-flash"},{"value":"deepseek/deepseek-v4-flash-0731","name":"deepseek/deepseek-v4-flash-0731"},{"value":"zai/glm-5.3-flash","name":"zai/glm-5.3-flash"},{"value":"zai/glm-4.7-flash","name":"zai/glm-4.7-flash"},{"value":"anthropic/claude-haiku-5.5","name":"anthropic/claude-haiku-5.5"},{"value":"anthropic/claude-haiku-4.5","name":"anthropic/claude-haiku-4.5"},{"value":"anthropic/claude-3-haiku","name":"anthropic/claude-3-haiku"},{"value":"openai/gpt-5.4-mini","name":"openai/gpt-5.4-mini"},{"value":"openai/gpt-5.4-mini-fast","name":"openai/gpt-5.4-mini-fast"},{"value":"openai/gpt-5-mini","name":"openai/gpt-5-mini"},{"value":"openai/gpt-5-mini-fast","name":"openai/gpt-5-mini-fast"},{"value":"openai/gpt-4.1-mini","name":"openai/gpt-4.1-mini"},{"value":"openai/gpt-4.1-mini-fast","name":"openai/gpt-4.1-mini-fast"},{"value":"openai/o4-mini","name":"openai/o4-mini"},{"value":"openai/o4-mini-fast","name":"openai/o4-mini-fast"},{"value":"openai/o3-mini","name":"openai/o3-mini"},{"value":"openai/gpt-4o-mini","name":"openai/gpt-4o-mini"},{"value":"openai/gpt-4o-mini-fast","name":"openai/gpt-4o-mini-fast"},{"value":"google/gemini-3.8-flash","name":"google/gemini-3.8-flash"},{"value":"google/gemini-3.7-flash","name":"google/gemini-3.7-flash"},{"value":"google/gemini-3.5-flash-lite","name":"google/gemini-3.5-flash-lite"},{"value":"google/gemini-3.6-flash","name":"google/gemini-3.6-flash"},{"value":"google/gemini-3.5-flash","name":"google/gemini-3.5-flash"},{"value":"google/gemini-3.1-flash-lite","name":"google/gemini-3.1-flash-lite"},{"value":"google/gemini-3-flash","name":"google/gemini-3-flash"},{"value":"google/gemini-2.5-flash-lite","name":"google/gemini-2.5-flash-lite"},{"value":"google/gemini-2.5-flash","name":"google/gemini-2.5-flash"},{"value":"google/gemini-2.5-pro","name":"google/gemini-2.5-pro"},{"value":"minimax/minimax-m3","name":"minimax/minimax-m3"},{"value":"minimax/minimax-m2.5","name":"minimax/minimax-m2.5"},{"value":"minimax/minimax-m2.1","name":"minimax/minimax-m2.1"},{"value":"amazon/nova-2-lite","name":"amazon/nova-2-lite"},{"value":"amazon/nova-lite","name":"amazon/nova-lite"},{"value":"google/gemini-3.1-pro-preview","name":"google/gemini-3.1-pro-preview"},{"value":"spacexai/grok-4.20-multi-agent-beta","name":"spacexai/grok-4.20-multi-agent-beta"},{"value":"spacexai/grok-4.20-non-reasoning-beta","name":"spacexai/grok-4.20-non-reasoning-beta"},{"value":"spacexai/grok-4.20-reasoning-beta","name":"spacexai/grok-4.20-reasoning-beta"}]},{"id":"mode","name":"Session Mode","description":"Controls how the agent requests permission","category":"mode","type":"select","currentValue":"ask","options":[{"value":"code","name":"Code","description":"Write and modify code with full tool access","permissionMode":"auto"},{"value":"ask","name":"Ask","description":"Request permission before making any changes","permissionMode":"ask"}]},{"id":"effort","name":"Reasoning Effort","description":"Controls how much the model thinks before responding","category":"thought_level","type":"select","currentValue":"xhigh","options":[{"value":"auto","name":"default"},{"value":"low","name":"low"},{"value":"medium","name":"medium"},{"value":"high","name":"high"},{"value":"xhigh","name":"xhigh"},{"value":"max","name":"max"}]}],"modes":{"currentModeId":"ask","availableModes":[{"id":"code","name":"Code","description":"Write and modify code with full tool access"},{"id":"ask","name":"Ask","description":"Request permission before making any changes"}]}}}} {"type":"emit_inbound","label":"notification:session/update:available_commands_update","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"fx-session-01","update":{"sessionUpdate":"available_commands_update","availableCommands":[]}}}} {"type":"expect_outbound","label":"request:session/set_config_option","frame":{"kind":"request","method":"session/set_config_option","params":{"sessionId":"fx-session-01","configId":"effort","value":"medium"}}} diff --git a/docs/user/providers-fx.md b/docs/user/providers-fx.md index 430f7a705a49..0a321995405a 100644 --- a/docs/user/providers-fx.md +++ b/docs/user/providers-fx.md @@ -44,7 +44,9 @@ A message steered into a running turn stops that turn, and fx answers the new me ## Sub-agents -When fx hands work to a sub-agent, it opens in its own thread under the parent. That thread shows the sub-agent's task and its reply, which fx shortens to a couple of lines. fx doesn't share the sub-agent's individual steps. +When fx hands work to a sub-agent, it opens in its own thread under the parent. That thread shows the sub-agent's task, each tool it runs and what it says between them, and its full reply. A reply longer than 64 KiB is cut off with a note. Steps from a sub-agent's own sub-agents aren't shown. + +fx versions before 0.0.14 don't report sub-agent steps, so there the thread shows only the task and a reply shortened to a couple of lines. fx doesn't pass on a sub-agent's permission requests, so a sub-agent that needs one waits until you press **Stop**. This mostly happens in **Supervised**. In **Auto**, sub-agents can finish routine work by themselves. diff --git a/packages/provider-acp/src/server/AcpSessionRuntime.ts b/packages/provider-acp/src/server/AcpSessionRuntime.ts index 4544a4ac5ce9..3dd53c9e2c57 100644 --- a/packages/provider-acp/src/server/AcpSessionRuntime.ts +++ b/packages/provider-acp/src/server/AcpSessionRuntime.ts @@ -103,6 +103,8 @@ export interface AcpSessionRuntimeOptions { readonly interruptPromptOnCancel?: boolean; readonly cancelMeta?: EffectAcpSchema.CancelNotification["_meta"]; readonly initializeMeta?: EffectAcpSchema.InitializeRequest["_meta"]; + /** Sent on every `session/new`, `session/load` and `session/resume`. */ + readonly sessionMeta?: EffectAcpSchema.NewSessionRequest["_meta"]; readonly ownDetachedProcessGroup?: boolean; readonly ownDescendantProcessGroups?: boolean; readonly processGroupPlatform?: NodeJS.Platform; @@ -2112,6 +2114,7 @@ export const make = Effect.fn("AcpSessionRuntime.make")(function* ( } return activationOptions?.mcpServers ?? options.mcpServers ?? []; }; + const sessionMeta = options.sessionMeta === undefined ? {} : { _meta: options.sessionMeta }; const sessionSetupFields = (initializeResult: EffectAcpSchema.InitializeResponse) => { const meta = options.sessionMeta?.(initializeResult); @@ -2190,6 +2193,7 @@ export const make = Effect.fn("AcpSessionRuntime.make")(function* ( cwd: options.cwd, ...additionalDirectories, mcpServers: sessionMcpServers(initializeResult), + ...sessionMeta, } satisfies EffectAcpSchema.LoadSessionRequest; sessionSetupResult = yield* runLoadSessionWithReplayIdle(loadPayload, initializeResult); } else if (initializeResult.agentCapabilities?.sessionCapabilities?.resume != null) { @@ -2198,6 +2202,7 @@ export const make = Effect.fn("AcpSessionRuntime.make")(function* ( cwd: options.cwd, ...additionalDirectories, mcpServers: sessionMcpServers(initializeResult), + ...sessionMeta, } satisfies EffectAcpSchema.ResumeSessionRequest; sessionSetupResult = yield* runLoggedRequest( "session/resume", @@ -2215,6 +2220,7 @@ export const make = Effect.fn("AcpSessionRuntime.make")(function* ( cwd: options.cwd, mcpServers: sessionMcpServers(initializeResult), ...additionalDirectories, + ...sessionMeta, } satisfies EffectAcpSchema.NewSessionRequest; const created = yield* runLoggedRequest( "session/new", @@ -2415,6 +2421,7 @@ export const make = Effect.fn("AcpSessionRuntime.make")(function* ( cwd: options.cwd, ...sessionSetupFields(started.initializeResult), mcpServers: sessionMcpServers(started.initializeResult, activationOptions), + ...sessionMeta, } satisfies EffectAcpSchema.LoadSessionRequest; return runLoadSessionWithReplayIdle(requestPayload, started.initializeResult); }), @@ -2428,6 +2435,7 @@ export const make = Effect.fn("AcpSessionRuntime.make")(function* ( cwd: options.cwd, ...sessionSetupFields(started.initializeResult), mcpServers: sessionMcpServers(started.initializeResult, activationOptions), + ...sessionMeta, } satisfies EffectAcpSchema.ResumeSessionRequest; return runLoggedRequest( "session/resume", diff --git a/packages/provider-fx/src/server/acpSupport.ts b/packages/provider-fx/src/server/acpSupport.ts index fb6dfae56ef8..7da2e6dec053 100644 --- a/packages/provider-fx/src/server/acpSupport.ts +++ b/packages/provider-fx/src/server/acpSupport.ts @@ -21,6 +21,7 @@ import { type AcpPermissionDisposition, } from "@t3tools/provider-acp/server/clientPolicy"; import type * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter"; +import { FX_HOOKS_SESSION_META } from "./subagents.ts"; export const FX_DRIVER_KIND = ProviderDriverKind.make("fx"); /** T3's slug for "Default": the model `fx status` reports, never an fx model id. */ @@ -232,6 +233,7 @@ export const makeFxAcpRuntime = Effect.fn("makeFxAcpRuntime")(function* ( env: { ...environment }, }, cancelBehavior: FX_ACP_CANCEL_BEHAVIOR, + sessionMeta: FX_HOOKS_SESSION_META, }), ); return yield* Effect.service(AcpSessionRuntime.AcpSessionRuntime).pipe(Effect.provide(context)); diff --git a/packages/provider-fx/src/server/adapter.ts b/packages/provider-fx/src/server/adapter.ts index dfb956e6d13f..7cd46dd02aaa 100644 --- a/packages/provider-fx/src/server/adapter.ts +++ b/packages/provider-fx/src/server/adapter.ts @@ -5,7 +5,6 @@ import { FxSettings } from "../settings.ts"; import type * as Crypto from "effect/Crypto"; import * as Effect from "effect/Effect"; import type * as FileSystem from "effect/FileSystem"; -import * as Option from "effect/Option"; import type * as Path from "effect/Path"; import * as Schema from "effect/Schema"; import type * as Scope from "effect/Scope"; @@ -38,9 +37,8 @@ import { makeAcpAdapterV2, type AcpAdapterV2Flavor, type AcpAdapterV2RuntimeInput, - type AcpAdapterV2SubagentUpdate, } from "@t3tools/provider-acp/server/adapter"; -import type { AcpToolCallState } from "@t3tools/provider-acp/server/runtimeModel"; +import { extractFxSubagentUpdate, withFxSubagentHooks } from "./subagents.ts"; export { FX_DRIVER_KIND } from "./acpSupport.ts"; @@ -71,79 +69,6 @@ const FxProviderCapabilitiesV2 = { }, } satisfies OrchestrationV2ProviderCapabilities; -const FxSubagentRequest = Schema.Union([ - Schema.Struct({ - action: Schema.Literal("run"), - task: Schema.String, - model: Schema.optionalKey(Schema.String), - }), - Schema.Struct({ - action: Schema.Literal("message"), - agent: Schema.String, - message: Schema.String, - model: Schema.optionalKey(Schema.String), - }), -]); -// fx 0.0.14 wraps the arguments in `request`; later fx sends them flat (vercel-labs/fx@df0878eb). -const decodeFxSubagentInput = Schema.decodeUnknownOption( - Schema.Union([FxSubagentRequest, Schema.Struct({ request: FxSubagentRequest })]), -); -const decodeFxToolContent = Schema.decodeUnknownOption( - Schema.Array(Schema.Struct({ content: Schema.Struct({ text: Schema.String }) })), -); -const decodeFxSubagentResult = Schema.decodeUnknownOption( - Schema.fromJsonString( - Schema.Struct({ ok: Schema.Boolean, result: Schema.NullOr(Schema.String) }), - ), -); -const decodeJsonString = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.String)); - -/** - * fx clips every tool result to 199 bytes, so a long sub-agent reply arrives - * as cut-off `{"ok":…,"result":"…` JSON. Keeps the readable prefix and marks the cut. - */ -function fxSubagentOutcome( - text: string, -): { readonly ok: boolean; readonly result: string | null } | undefined { - const whole = decodeFxSubagentResult(text); - if (Option.isSome(whole)) return whole.value; - const clipped = /^\{"ok":(true|false),"result":"((?:[^"\\]|\\.)*)/.exec(text); - if (clipped === null) return undefined; - const prefix = decodeJsonString(`"${(clipped[2] ?? "").replace(/\\u[0-9a-fA-F]{0,3}$/, "")}"`); - const cut = text[clipped[0].length] !== '"'; - return { - ok: clipped[1] === "true", - result: Option.isSome(prefix) ? `${prefix.value}${cut ? "…" : ""}` : null, - }; -} - -/** fx streams no child frames over ACP, so each `subagent` call becomes a child thread holding its task and reply. */ -function extractFxSubagentUpdate(tool: AcpToolCallState): AcpAdapterV2SubagentUpdate | undefined { - if (tool.data.name !== "subagent") return undefined; - const input = decodeFxSubagentInput(tool.data.rawInput); - if (Option.isNone(input)) return undefined; - const request = "request" in input.value ? input.value.request : input.value; - const text = Option.getOrUndefined(decodeFxToolContent(tool.data.content))?.[0]?.content.text; - const outcome = text === undefined ? undefined : fxSubagentOutcome(text); - const status = - tool.status === "completed" - ? outcome?.ok === false - ? "failed" - : "completed" - : tool.status === "failed" - ? "failed" - : "running"; - return { - nativeTaskId: tool.toolCallId, - childSessionId: null, - prompt: request.action === "run" ? request.task : request.message, - title: request.action === "message" ? request.agent : null, - model: request.model ?? null, - status, - result: status === "running" ? null : (outcome?.result ?? text ?? null), - }; -} - export interface FxAdapterV2Options { readonly instanceId: Parameters[0]["instanceId"]; readonly settings: FxSettings; @@ -177,7 +102,10 @@ function makeFxAcpAdapterFlavor(options: FxAdapterV2Options): AcpAdapterV2Flavor driver: FX_DRIVER_KIND, runtimeHarness: "fx", capabilities: FxProviderCapabilitiesV2, - makeRuntime: (input) => makeRuntime(input).pipe(Effect.map(withFxSessionMode)), + makeRuntime: (input) => + makeRuntime(input).pipe( + Effect.map((runtime) => withFxSubagentHooks(withFxSessionMode(runtime))), + ), applyModelSelection: ({ runtime, modelSelection }) => applyFxModelSelection(runtime, modelSelection, options.defaultModel).pipe( Effect.tap(() => applyFxEffortDefault(runtime, modelSelection)), diff --git a/packages/provider-fx/src/server/subagents.test.ts b/packages/provider-fx/src/server/subagents.test.ts new file mode 100644 index 000000000000..f6a81071885d --- /dev/null +++ b/packages/provider-fx/src/server/subagents.test.ts @@ -0,0 +1,250 @@ +import { assert, describe, it } from "@effect/vitest"; +import type * as EffectAcpSchema from "effect-acp/compat"; + +import { + type AcpToolCallState, + mergeToolCallState, + parseSessionUpdateEvent, +} from "@t3tools/provider-acp/server/runtimeModel"; + +import { extractFxSubagentUpdate, makeFxSubagentHookRouter } from "./subagents.ts"; + +const ROOT = "fx-session-01"; +const CHILD = "fx-session-02"; +const CALL = "call_subagent"; +const TASK = "Read package.json and summarize it."; +const REPLY = `The package is fx-replay.\n${"Scripts: build, test, lint. ".repeat(20)}`; +const WHOLE_RESULT = JSON.stringify({ ok: true, result: REPLY, error_code: null }); + +/** fx's ACP view of a `subagent` call: its arguments, then a 200-byte preview of the result. */ +const fxToolCall = { + toolCallId: CALL, + title: "Subagent · Read package.json", + _meta: { fx: { toolCall: { internal: false } } }, +}; +const announce: EffectAcpSchema.SessionNotification = { + sessionId: ROOT, + update: { + sessionUpdate: "tool_call", + ...fxToolCall, + name: "subagent", + kind: "other", + status: "pending", + rawInput: { action: "run", task: TASK }, + }, +}; +const subagentUpdate = ( + update: Omit, +): EffectAcpSchema.SessionNotification => ({ + sessionId: ROOT, + update: { sessionUpdate: "tool_call_update", ...fxToolCall, ...update }, +}); +const preview = (status: "completed" | "failed", text = WHOLE_RESULT.slice(0, 199)) => + subagentUpdate({ + status, + content: [{ type: "content", content: { type: "text", text } }], + }); + +const hook = ( + sessionId: string, + type: string, + data: Record, + contentTruncated = false, +) => ({ + version: 1 as const, + sessionId, + rootSessionId: ROOT, + executionId: sessionId === ROOT ? "exec-root" : "exec-child", + type, + data, + contentTruncated, +}); +const call = (callId: string, name: string, argumentsJson?: string) => ({ + callId, + name, + provenance: "model", + ...(argumentsJson === undefined ? {} : { argumentsJson }), +}); +const delegation = (phase: string) => + hook(ROOT, "delegation", { callId: CALL, childSessionId: CHILD, workId: "work-1", phase }); +const rootResult = (output: string, truncated = false, type = "tool.result") => + hook(ROOT, type, { call: call(CALL, "subagent"), output, status: "success" }, truncated); + +/** + * Plays fx's ACP updates and hook events the way the adapter receives them, + * folding root tool updates into tool state as the ACP runtime model does. + */ +function play( + steps: ReadonlyArray< + | { readonly acp: EffectAcpSchema.SessionNotification } + | { readonly hook: ReturnType } + >, +) { + const router = makeFxSubagentHookRouter(); + const delivered: Array = []; + let tool: AcpToolCallState | undefined; + const statuses: Array = []; + for (const step of steps) { + const notifications = "acp" in step ? [router.enrich(step.acp)] : router.route(step.hook); + for (const notification of notifications) { + if (notification.sessionId !== ROOT) { + delivered.push(notification); + continue; + } + for (const event of parseSessionUpdateEvent(notification).events) { + if (event._tag !== "ToolCallUpdated") continue; + tool = mergeToolCallState(tool, event.toolCall); + statuses.push(extractFxSubagentUpdate(tool)?.status); + } + } + } + if (tool === undefined) throw new Error("fx never announced the subagent call"); + return { delivered, statuses, update: extractFxSubagentUpdate(tool) }; +} + +const childSteps = (delivered: ReadonlyArray) => + delivered.map(({ sessionId, update }) => { + assert.equal(sessionId, CHILD); + switch (update.sessionUpdate) { + case "tool_call": + case "tool_call_update": + return `${update.sessionUpdate} ${update.toolCallId} ${update.status}`; + case "agent_message": + return `agent_message ${update.messageId}: ${update.content?.map((block) => (block.type === "text" ? block.text : "")).join("")}`; + default: + return update.sessionUpdate; + } + }); + +describe("fx sub-agent hooks", () => { + it("shows a child's steps and takes its reply whole from the root call's hooks", () => { + const run = play([ + { acp: announce }, + { hook: hook(ROOT, "tool.start", { call: call(CALL, "subagent") }) }, + { acp: subagentUpdate({ status: "in_progress" }) }, + { hook: delegation("dispatched") }, + { hook: hook(CHILD, "model.start", { modelCallId: 1, purpose: "agent", messages: [] }) }, + { + hook: hook(CHILD, "model.end", { + modelCallId: 1, + output: "Reading package.json.", + toolCalls: [call("call_read", "read_file")], + }), + }, + { + hook: hook(CHILD, "tool.start", { + call: call("call_read", "read_file", '{"path":"package.json"}'), + }), + }, + { + hook: hook(CHILD, "tool.end", { + call: call("call_read", "read_file"), + output: '{"name":"fx-replay"}', + status: "success", + }), + }, + { + hook: hook(CHILD, "tool.result", { + call: call("call_read", "read_file"), + output: '{"name":"fx-replay"}', + status: "success", + }), + }, + { hook: hook(CHILD, "model.start", { modelCallId: 2, purpose: "permission_review" }) }, + { hook: hook(CHILD, "model.end", { modelCallId: 2, output: "allow", toolCalls: [{}] }) }, + { hook: hook(CHILD, "model.start", { modelCallId: 3, purpose: "agent" }) }, + { hook: hook(CHILD, "model.end", { modelCallId: 3, output: REPLY, toolCalls: [] }) }, + { hook: delegation("completed") }, + { acp: preview("completed") }, + { hook: rootResult(WHOLE_RESULT, false, "tool.end") }, + // A parallel batch reports this only once its slowest call ends. + { hook: rootResult(WHOLE_RESULT) }, + // A child step after its call settled has no turn to land in. + { hook: hook(CHILD, "tool.start", { call: call("call_late", "shell") }) }, + ]); + + assert.deepEqual(childSteps(run.delivered), [ + "agent_message exec-child:1: Reading package.json.", + "tool_call call_read in_progress", + "tool_call_update call_read completed", + "tool_call_update call_read completed", + `agent_message result: ${REPLY}`, + ]); + const read = run.delivered[1]?.update; + assert.deepInclude(read, { kind: "read", rawInput: { path: "package.json" } }); + // fx completes the ACP call before it reports the whole result. + assert.deepEqual(run.statuses, [ + "running", + "running", + "running", + "running", + "running", + "completed", + ]); + assert.deepInclude(run.update, { + nativeTaskId: CALL, + childSessionId: CHILD, + prompt: TASK, + status: "completed", + result: REPLY, + }); + }); + + it("settles a hooked call from its delegation phase and whole result", () => { + const settle = ( + phase: string, + ending: ReadonlyArray<{ readonly acp: EffectAcpSchema.SessionNotification }>, + result?: ReturnType, + ) => { + const run = play([ + { acp: announce }, + { hook: delegation("dispatched") }, + { hook: delegation(phase) }, + ...ending, + ...(result === undefined ? [] : [{ hook: result }]), + ]); + return { status: run.update?.status, result: run.update?.result, steps: run.delivered }; + }; + const failure = JSON.stringify({ ok: false, result: "No package.json", error_code: null }); + + const truncated = settle( + "completed", + [{ acp: preview("completed") }], + rootResult(WHOLE_RESULT.slice(0, 300), true), + ); + assert.equal(truncated.status, "completed"); + assert.match( + truncated.result ?? "", + /^The package is fx-replay\.\nScripts: [^"]+…\n\n\[fx truncated this reply at 64 KiB\]$/, + ); + assert.deepInclude( + settle("failed", [{ acp: preview("completed", failure) }], rootResult(failure)), + { status: "failed", result: "No package.json" }, + ); + const yielded = JSON.stringify({ ok: true, result: "Still running.", pending: true }); + const pending = settle( + "yielded", + [{ acp: preview("completed", yielded) }], + rootResult(yielded), + ); + assert.deepInclude(pending, { status: "idle", result: "Still running." }); + assert.lengthOf(pending.steps, 0, "a yielded child has not replied yet"); + assert.deepInclude(settle("cancelled", [{ acp: preview("failed", "cancelled") }]), { + status: "cancelled", + result: "cancelled", + }); + }); + + it("ignores hooks for calls fx never announced over ACP and for sessions it never dispatched", () => { + const router = makeFxSubagentHookRouter(); + assert.deepEqual(router.route(delegation("dispatched")), []); + assert.deepEqual( + router.route(hook("fx-session-09", "tool.start", { call: call("c", "shell") })), + [], + ); + assert.deepEqual( + router.route(hook(ROOT, "model.end", { modelCallId: 1, output: "hi", toolCalls: [] })), + [], + ); + }); +}); diff --git a/packages/provider-fx/src/server/subagents.ts b/packages/provider-fx/src/server/subagents.ts new file mode 100644 index 000000000000..52c20481c912 --- /dev/null +++ b/packages/provider-fx/src/server/subagents.ts @@ -0,0 +1,468 @@ +import type * as EffectAcpSchema from "effect-acp/compat"; +import * as Effect from "effect/Effect"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; + +import type * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime"; +import type { AcpAdapterV2SubagentUpdate } from "@t3tools/provider-acp/server/adapter"; +import type { AcpToolCallState } from "@t3tools/provider-acp/server/runtimeModel"; + +/** + * Opts a session into fx's `libfx/hook` events (fx 0.0.14+), which report each + * sub-agent's steps and the whole `subagent` result that ACP clips to 200 + * bytes. Capture is all-or-nothing, so unused events are dropped at decode. + */ +export const FX_HOOKS_SESSION_META = { fx: { hooks: { captureContent: true } } }; + +const FX_HOOK_METHOD = "libfx/hook"; +const FX_SUBAGENT_TOOL = "subagent"; +/** Where hook facts ride on a root `subagent` tool call, beside fx's own `_meta.fx`. */ +const HOOK_STATE_META_KEY = "t3code/fxSubagentHooks"; + +const FxSubagentHookState = Schema.Struct({ + childSessionId: Schema.optionalKey(Schema.String), + phase: Schema.optionalKey(Schema.String), + /** fx's whole `{ok, result}` reply from the root call's hooks, up to their 64 KiB cap. */ + output: Schema.optionalKey(Schema.String), + truncated: Schema.optionalKey(Schema.Boolean), +}); +type FxSubagentHookState = typeof FxSubagentHookState.Type; +const decodeHookStateMeta = Schema.decodeUnknownOption( + Schema.Struct({ [HOOK_STATE_META_KEY]: FxSubagentHookState }), +); + +const decodeHooksConfirmation = Schema.decodeUnknownOption( + Schema.Struct({ fx: Schema.Struct({ hooksVersion: Schema.Literal(1) }) }), +); + +const FxHookEvent = Schema.Struct({ + version: Schema.Literal(1), + sessionId: Schema.String, + rootSessionId: Schema.String, + executionId: Schema.String, + type: Schema.String, + data: Schema.Unknown, + contentTruncated: Schema.Boolean, +}); +type FxHookEvent = typeof FxHookEvent.Type; + +const HookCall = Schema.Struct({ + callId: Schema.String, + name: Schema.String, + argumentsJson: Schema.optionalKey(Schema.NullOr(Schema.String)), +}); +const decodeDelegation = Schema.decodeUnknownOption( + Schema.Struct({ callId: Schema.String, childSessionId: Schema.String, phase: Schema.String }), +); +const decodeToolStart = Schema.decodeUnknownOption(Schema.Struct({ call: HookCall })); +const decodeToolOutcome = Schema.decodeUnknownOption( + Schema.Struct({ + call: HookCall, + output: Schema.optionalKey(Schema.NullOr(Schema.String)), + status: Schema.String, + }), +); +const decodeModelStart = Schema.decodeUnknownOption( + Schema.Struct({ modelCallId: Schema.Number, purpose: Schema.String }), +); +const decodeModelEnd = Schema.decodeUnknownOption( + Schema.Struct({ + modelCallId: Schema.Number, + output: Schema.optionalKey(Schema.NullOr(Schema.String)), + toolCalls: Schema.optionalKey(Schema.Array(Schema.Unknown)), + }), +); +const decodeArguments = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Unknown)); + +const FxSubagentRequest = Schema.Union([ + Schema.Struct({ + action: Schema.Literal("run"), + task: Schema.String, + model: Schema.optionalKey(Schema.String), + }), + Schema.Struct({ + action: Schema.Literal("message"), + agent: Schema.String, + message: Schema.String, + model: Schema.optionalKey(Schema.String), + }), +]); +// fx 0.0.14 wraps the arguments in `request`; later fx sends them flat (vercel-labs/fx@df0878eb). +const decodeFxSubagentInput = Schema.decodeUnknownOption( + Schema.Union([FxSubagentRequest, Schema.Struct({ request: FxSubagentRequest })]), +); +const decodeFxToolContent = Schema.decodeUnknownOption( + Schema.Array(Schema.Struct({ content: Schema.Struct({ text: Schema.String }) })), +); +const decodeFxSubagentResult = Schema.decodeUnknownOption( + Schema.fromJsonString( + Schema.Struct({ ok: Schema.Boolean, result: Schema.NullOr(Schema.String) }), + ), +); +const decodeJsonString = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.String)); + +/** + * fx clips ACP tool results to 199 bytes and hook content to 64 KiB, so a long + * reply can arrive as cut-off `{"ok":…,"result":"…` JSON. Keeps the readable + * prefix and marks the cut. + */ +function fxSubagentOutcome( + text: string, +): { readonly ok: boolean; readonly result: string | null } | undefined { + const whole = decodeFxSubagentResult(text); + if (Option.isSome(whole)) return whole.value; + const clipped = /^\{"ok":(true|false),"result":"((?:[^"\\]|\\.)*)/.exec(text); + if (clipped === null) return undefined; + const prefix = decodeJsonString(`"${(clipped[2] ?? "").replace(/\\u[0-9a-fA-F]{0,3}$/, "")}"`); + const cut = text[clipped[0].length] !== '"'; + return { + ok: clipped[1] === "true", + result: Option.isSome(prefix) ? `${prefix.value}${cut ? "…" : ""}` : null, + }; +} + +function fxSubagentReply(state: FxSubagentHookState & { readonly output: string }): string { + const reply = fxSubagentOutcome(state.output)?.result ?? state.output; + return state.truncated === true ? `${reply}\n\n[fx truncated this reply at 64 KiB]` : reply; +} + +function hookedSubagentStatus( + tool: AcpToolCallState, + hooks: FxSubagentHookState, +): AcpAdapterV2SubagentUpdate["status"] { + if (tool.status === "failed") return hooks.phase === "cancelled" ? "cancelled" : "failed"; + // fx completes the ACP call before it reports the whole result. + if (hooks.output === undefined) return "running"; + if (hooks.phase === "yielded") return "idle"; + if (hooks.phase === "cancelled") return "cancelled"; + if (hooks.phase === "failed" || fxSubagentOutcome(hooks.output)?.ok === false) return "failed"; + return "completed"; +} + +/** Each `subagent` call becomes a child thread; with hooks it also gets the child's steps. */ +export function extractFxSubagentUpdate( + tool: AcpToolCallState, +): AcpAdapterV2SubagentUpdate | undefined { + if (tool.data.name !== FX_SUBAGENT_TOOL) return undefined; + const input = decodeFxSubagentInput(tool.data.rawInput); + if (Option.isNone(input)) return undefined; + const request = "request" in input.value ? input.value.request : input.value; + const update = { + nativeTaskId: tool.toolCallId, + prompt: request.action === "run" ? request.task : request.message, + title: request.action === "message" ? request.agent : null, + model: request.model ?? null, + }; + const hooks = Option.getOrUndefined(decodeHookStateMeta(tool.data.meta))?.[HOOK_STATE_META_KEY]; + const text = Option.getOrUndefined(decodeFxToolContent(tool.data.content))?.[0]?.content.text; + const outcome = text === undefined ? undefined : fxSubagentOutcome(text); + if (hooks !== undefined) { + const status = hookedSubagentStatus(tool, hooks); + return { + ...update, + childSessionId: hooks.childSessionId ?? null, + status, + result: + status === "running" + ? null + : hooks.output === undefined + ? (outcome?.result ?? text ?? null) + : fxSubagentReply({ ...hooks, output: hooks.output }), + }; + } + const status = + tool.status === "completed" + ? outcome?.ok === false + ? "failed" + : "completed" + : tool.status === "failed" + ? "failed" + : "running"; + return { + ...update, + childSessionId: null, + status, + result: status === "running" ? null : (outcome?.result ?? text ?? null), + }; +} + +/** fx's own `tool_call` kinds (src/acp/tool_call_presentation.zig). */ +function fxToolKind(name: string): EffectAcpSchema.ToolKind { + switch (name) { + case "read_file": + case "glob_files": + return "read"; + case "grep_files": + case "web_search": + return "search"; + case "web_fetch": + return "fetch"; + case "write_file": + case "edit_file": + return "edit"; + case "shell": + case "terminal": + case "run_command": + return "execute"; + default: + return "other"; + } +} + +/** Every update names the call in full; one without a title presents as a bare "Tool". */ +function childToolCall(call: typeof HookCall.Type) { + const rawInput = Option.getOrUndefined(decodeArguments(call.argumentsJson)); + return { + toolCallId: call.callId, + name: call.name, + title: call.name, + kind: fxToolKind(call.name), + ...(rawInput === undefined ? {} : { rawInput }), + }; +} + +const textContent = (text: string | null | undefined) => + text ? [{ type: "content" as const, content: { type: "text" as const, text } }] : []; + +interface RootSubagentCall { + readonly sessionId: string; + /** fx's ACP `tool_call` arrived, so a hook-made update cannot invent a tool. */ + announced: boolean; + state: FxSubagentHookState; +} + +/** + * Turns fx hook events into ACP updates the adapter already projects: facts + * about a root `subagent` call ride on that call's `_meta`, and a dispatched + * child's steps arrive as updates on the child's session. A child is followed + * only while its call runs, the only time a turn can take its steps. + */ +export function makeFxSubagentHookRouter() { + const calls = new Map(); + const childCalls = new Map(); + const agentModelCalls = new Set(); + + const rootUpdate = ( + callId: string, + call: RootSubagentCall, + ): ReadonlyArray => + call.announced + ? [ + { + sessionId: call.sessionId, + update: { + sessionUpdate: "tool_call_update", + toolCallId: callId, + _meta: { [HOOK_STATE_META_KEY]: call.state }, + }, + }, + ] + : []; + + const routeRoot = (event: FxHookEvent): ReadonlyArray => { + if (event.type === "delegation") { + const delegation = Option.getOrUndefined(decodeDelegation(event.data)); + if (delegation === undefined) return []; + let call = calls.get(delegation.callId); + if (call === undefined) { + call = { sessionId: event.sessionId, announced: false, state: {} }; + calls.set(delegation.callId, call); + } + if (delegation.phase === "dispatched") { + call.state = { ...call.state, childSessionId: delegation.childSessionId }; + childCalls.set(delegation.childSessionId, delegation.callId); + } else if (childCalls.get(delegation.childSessionId) === delegation.callId) { + childCalls.delete(delegation.childSessionId); + } + call.state = { ...call.state, phase: delegation.phase }; + return rootUpdate(delegation.callId, call); + } + // A parallel batch holds every `tool.result` until its slowest call; `tool.end` comes at once. + if (event.type !== "tool.end" && event.type !== "tool.result") return []; + const result = Option.getOrUndefined(decodeToolOutcome(event.data)); + const call = result === undefined ? undefined : calls.get(result.call.callId); + const output = result?.output; + if (result === undefined || call === undefined || output === undefined || output === null) { + return []; + } + calls.delete(result.call.callId); + call.state = { ...call.state, output, truncated: event.contentTruncated }; + const { childSessionId, phase } = call.state; + if (childSessionId !== undefined) childCalls.delete(childSessionId); + const reply = fxSubagentReply({ ...call.state, output }); + return [ + // The reply goes in before the call settles, so the child thread ends on it. + ...(childSessionId === undefined || phase === "yielded" || reply.length === 0 + ? [] + : [ + { + sessionId: childSessionId, + update: { + sessionUpdate: "agent_message" as const, + messageId: "result", + content: [{ type: "text" as const, text: reply }], + }, + }, + ]), + ...rootUpdate(result.call.callId, call), + ]; + }; + + const routeChild = (event: FxHookEvent): ReadonlyArray => { + const sessionId = event.sessionId; + switch (event.type) { + case "tool.start": { + const start = Option.getOrUndefined(decodeToolStart(event.data)); + if (start === undefined) return []; + return [ + { + sessionId, + update: { + sessionUpdate: "tool_call", + ...childToolCall(start.call), + status: "in_progress", + }, + }, + ]; + } + case "tool.end": + case "tool.result": { + const outcome = Option.getOrUndefined(decodeToolOutcome(event.data)); + if (outcome === undefined) return []; + return [ + { + sessionId, + update: { + sessionUpdate: "tool_call_update", + ...childToolCall(outcome.call), + status: outcome.status === "success" ? "completed" : "failed", + content: textContent(outcome.output), + }, + }, + ]; + } + case "model.start": { + const start = Option.getOrUndefined(decodeModelStart(event.data)); + // Review, compaction and title calls are fx's own, not the child speaking. + if (start?.purpose === "agent") { + agentModelCalls.add(`${sessionId}:${event.executionId}:${start.modelCallId}`); + } + return []; + } + case "model.end": { + const end = Option.getOrUndefined(decodeModelEnd(event.data)); + if ( + end === undefined || + !agentModelCalls.delete(`${sessionId}:${event.executionId}:${end.modelCallId}`) + ) + return []; + // A reply without tool calls is the child's answer, which the root result carries whole. + if (!end.output?.trim() || (end.toolCalls?.length ?? 0) === 0) return []; + return [ + { + sessionId, + update: { + sessionUpdate: "agent_message", + messageId: `${event.executionId}:${end.modelCallId}`, + content: [{ type: "text", text: end.output }], + }, + }, + ]; + } + default: + return []; + } + }; + + return { + reset: () => { + calls.clear(); + childCalls.clear(); + agentModelCalls.clear(); + }, + /** Records fx's `subagent` calls and attaches what hooks reported so far. */ + enrich: ( + notification: EffectAcpSchema.SessionNotification, + ): EffectAcpSchema.SessionNotification => { + const update = notification.update; + if (update.sessionUpdate !== "tool_call" && update.sessionUpdate !== "tool_call_update") { + return notification; + } + let call = calls.get(update.toolCallId); + if (call === undefined) { + if (update.sessionUpdate !== "tool_call" || update.name !== FX_SUBAGENT_TOOL) { + return notification; + } + call = { sessionId: notification.sessionId, announced: true, state: {} }; + calls.set(update.toolCallId, call); + } + call.announced = true; + return { + ...notification, + update: { ...update, _meta: { ...update._meta, [HOOK_STATE_META_KEY]: call.state } }, + }; + }, + route: (event: FxHookEvent): ReadonlyArray => { + if (event.sessionId === event.rootSessionId) return routeRoot(event); + // Grandchildren stay inside their parent's `subagent` step. + return childCalls.has(event.sessionId) ? routeChild(event) : []; + }, + }; +} + +type SessionUpdateHandler = Parameters< + AcpSessionRuntime.AcpSessionRuntime["Service"]["handleSessionUpdate"] +>[0]; + +/** + * Routes fx's hook events through the adapter's session update handlers. A + * session fx does not confirm hooks for (fx 0.0.13) keeps the ACP-only view. + */ +export function withFxSubagentHooks( + runtime: AcpSessionRuntime.AcpSessionRuntime["Service"], +): AcpSessionRuntime.AcpSessionRuntime["Service"] { + const router = makeFxSubagentHookRouter(); + const handlers: Array = []; + let hooksActive = false; + const activated = ( + effect: Effect.Effect, + ) => + Effect.sync(() => { + hooksActive = false; + router.reset(); + }).pipe( + Effect.andThen(effect), + Effect.tap((result) => + Effect.sync(() => { + hooksActive = Option.isSome(decodeHooksConfirmation(result.sessionSetupResult._meta)); + }), + ), + ); + return { + ...runtime, + start: () => activated(runtime.start()), + loadSession: (sessionId, options) => activated(runtime.loadSession(sessionId, options)), + resumeSession: (sessionId, options) => activated(runtime.resumeSession(sessionId, options)), + forkSession: (sessionId, options) => activated(runtime.forkSession(sessionId, options)), + handleSessionUpdate: Effect.fn("FxSubagentHooks.handleSessionUpdate")(function* ( + handler: SessionUpdateHandler, + ) { + if (handlers.length === 0) { + yield* runtime.handleExtNotification(FX_HOOK_METHOD, FxHookEvent, (event) => + hooksActive + ? Effect.forEach( + router.route(event), + (notification) => + Effect.forEach(handlers, (each) => each(notification), { discard: true }), + { discard: true }, + ) + : Effect.void, + ); + } + handlers.push(handler); + yield* runtime.handleSessionUpdate((notification) => + handler(hooksActive ? router.enrich(notification) : notification), + ); + }), + }; +} diff --git a/packages/provider-fx/src/testing.ts b/packages/provider-fx/src/testing.ts index 8a3258b9ce5b..d777a98b7ada 100644 --- a/packages/provider-fx/src/testing.ts +++ b/packages/provider-fx/src/testing.ts @@ -5,3 +5,4 @@ */ export { FX_DRIVER_KIND, makeFxAdapterV2 } from "./server/adapter.ts"; export { FX_ACP_CANCEL_BEHAVIOR, makeFxAcpRuntime } from "./server/acpSupport.ts"; +export { FX_HOOKS_SESSION_META } from "./server/subagents.ts"; From e7cf1e44bb175cc8f821d0b6291761db8b728449 Mon Sep 17 00:00:00 2001 From: Jake Leventhal Date: Thu, 29 Oct 2026 11:12:19 -0400 Subject: [PATCH 02/13] fix(fx): keep T3's prompt text out of recorded turn.start hooks fx echoes the whole prompt as the libfx/hook turn.start input, so the fx_subagent_hooks recording kept T3's instructions and runtime instructions there, though the transcript header says T3-owned prompt text is normalized. The recorder now applies the same placeholders to that echo as to the outbound prompt, and the fixture's one turn.start frame is rewritten the same way. Co-Authored-By: Claude Opus 5.5 --- .../scripts/record-fx-acp-replay-fixture.ts | 33 +++++++++++++++---- .../fx_subagent_hooks/fx_transcript.ndjson | 2 +- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/apps/server/scripts/record-fx-acp-replay-fixture.ts b/apps/server/scripts/record-fx-acp-replay-fixture.ts index 3c8bcf4871b2..9cbf1a0b4660 100644 --- a/apps/server/scripts/record-fx-acp-replay-fixture.ts +++ b/apps/server/scripts/record-fx-acp-replay-fixture.ts @@ -38,6 +38,7 @@ const decodeJson = Schema.decodeUnknownSync(Schema.fromJsonString(Schema.Unknown const HOME_PLACEHOLDER = "/home/fx-replay"; const FX_INSTANCE_ID = ProviderInstanceId.make("fx"); const T3_INSTRUCTIONS_BODY = /\n[\s\S]*?\n<\/t3_code_instructions>/u; +const T3_INSTRUCTIONS_PLACEHOLDER = "\n\n"; const EMAIL = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/giu; const TOKEN = /\b(?:sk-|gho_|ghp_|xai-|vercel_)[A-Za-z0-9_-]{8,}\b/gu; @@ -208,10 +209,7 @@ function normalizeOutboundFrame(frame: Record, runtimeInstructi isRecord(part) && typeof part.text === "string" ? { ...part, - text: part.text.replace( - T3_INSTRUCTIONS_BODY, - "\n\n", - ), + text: part.text.replace(T3_INSTRUCTIONS_BODY, T3_INSTRUCTIONS_PLACEHOLDER), } : part, ), @@ -226,7 +224,10 @@ function normalizeOutboundFrame(frame: Record, runtimeInstructi /** A model call's request content: up to 64 KiB per hook that T3 never reads. */ const HOOK_MODEL_REQUEST_FIELDS = new Set(["messages", "instructions", "tools", "toolsJson"]); -function normalizeInboundFrame(frame: Record): Record { +function normalizeInboundFrame( + frame: Record, + runtimeInstructions: string, +): Record { const params = isRecord(frame.params) ? frame.params : undefined; if (frame.method === "libfx/hook" && params?.type === "model.start" && isRecord(params.data)) { const data = params.data; @@ -240,6 +241,26 @@ function normalizeInboundFrame(frame: Record): Record"), + }, + }, + }; + } const update = isRecord(params?.update) ? params.update : undefined; if ( update?.sessionUpdate === "available_commands_update" && @@ -325,7 +346,7 @@ function normalizeEntries(input: { const frame = entry.type === "expect_outbound" ? normalizeOutboundFrame(entry.frame, input.runtimeInstructions) - : normalizeInboundFrame(entry.frame); + : normalizeInboundFrame(entry.frame, input.runtimeInstructions); return { ...entry, ...(entry.label === undefined ? {} : { label: replaceAll(entry.label) }), diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent_hooks/fx_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent_hooks/fx_transcript.ndjson index a3da4225d37d..177156728ad5 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent_hooks/fx_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent_hooks/fx_transcript.ndjson @@ -9,7 +9,7 @@ {"type":"expect_outbound","label":"request:session/set_config_option","frame":{"kind":"request","method":"session/set_config_option","params":{"sessionId":"fx-session-01","configId":"mode","value":"auto"}}} {"type":"emit_inbound","label":"response:session/set_config_option","frame":{"kind":"response","method":"session/set_config_option","result":{"configOptions":[{"id":"provider","name":"Provider","category":"model","type":"select","currentValue":"codex","options":[{"value":"gateway","name":"Vercel AI Gateway"},{"value":"codex","name":"Codex subscription"},{"value":"grok","name":"Grok subscription"}]},{"id":"model","name":"Model","category":"model","type":"select","currentValue":"gpt-6.1-sol","options":[{"value":"gpt-6.1-sol","name":"gpt-6.1-sol"},{"value":"gpt-6-astra","name":"gpt-6-astra"},{"value":"gpt-6.1-terra","name":"gpt-6.1-terra"},{"value":"gpt-6-sol","name":"gpt-6-sol"},{"value":"gpt-6-luna","name":"gpt-6-luna"},{"value":"gpt-5.6-sol","name":"gpt-5.6-sol"},{"value":"gpt-5.6-terra","name":"gpt-5.6-terra"},{"value":"gpt-5.6-luna","name":"gpt-5.6-luna"},{"value":"gpt-daybreak-blue-latest","name":"gpt-daybreak-blue-latest"}]},{"id":"mode","name":"Session Mode","description":"Controls how the agent requests permission","category":"mode","type":"select","currentValue":"auto","options":[{"value":"auto","name":"Auto","description":"Check actions with an automatic safety review instead of asking","_meta":{"fx":{"permissionMode":"auto"}}},{"value":"ask","name":"Ask","description":"Request permission before making any changes","_meta":{"fx":{"permissionMode":"ask"}}},{"value":"full-access","name":"Full access","description":"Run every action without asking, outside the sandbox","_meta":{"fx":{"permissionMode":"yolo"}}}]},{"id":"effort","name":"Reasoning Effort","description":"Controls how much the model thinks before responding","category":"thought_level","type":"select","currentValue":"medium","options":[{"value":"auto","name":"default"},{"value":"low","name":"low"},{"value":"medium","name":"medium"},{"value":"high","name":"high"},{"value":"xhigh","name":"xhigh"},{"value":"max","name":"max"},{"value":"ultra","name":"ultra"}]},{"id":"fast","name":"Fast Mode","description":"Uses the model's fast lane","category":"model","type":"select","currentValue":"false","options":[{"value":"false","name":"off"},{"value":"true","name":"on"}]}]}}} {"type":"expect_outbound","label":"request:session/prompt","frame":{"kind":"request","method":"session/prompt","params":{"sessionId":"fx-session-01","prompt":[{"type":"text","text":"\n\n\n\n\nSpawn 2 subagents, one to read package.json and one to read tsconfig.json\n"}]}}} -{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":0,"timestamp":1793283383909,"type":"turn.start","data":{"resumed":false,"input":"\n## T3 Code interaction mode: Default\n\nPrefer making reasonable assumptions and carrying out the user's request. Ask a concise question only when a missing user decision would materially change the result. Treat this mode as active until T3 Code supplies a different interaction-mode instruction.\n\n\n\nSpawn 2 subagents, one to read package.json and one to read tsconfig.json\n\nIn case you're asked: you are running in T3 Code through the fx harness. No need to mention this otherwise. You can embed images, video, and audio in your response using Markdown image syntax with absolute file paths. Link any other file you produce using its absolute path so the user can open it in T3 Code's files viewer.\n\n\nLink each PR you create, take over, or push to: one link_pull_request per PR, full URL, every stack layer, as your next call. Host tools do not link for you. If linking fails, say so. Skip PRs you only read or review, and PRs named only as background. Call watch_pull_request when asked to monitor or babysit, or when work you were asked to do, such as a merge, still waits on checks or reviews, even after an earlier unwatch. Finish any needed branch update and push first, then watch and end your turn. Otherwise act now or hand back. Delegated children never watch, even if the brief says babysit. After PR work, a child's last line is: PR , head , checks and reviews . Links and watches persist across pushes. Never wait with sleep, loops, gh --watch, Monitor, or ScheduleWakeup. Before handback, call unwatch_pull_request on any PR you watch.\n"},"contentCaptured":true,"contentTruncated":false}}} +{"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":0,"timestamp":1793283383909,"type":"turn.start","data":{"resumed":false,"input":"\n\n\n\n\nSpawn 2 subagents, one to read package.json and one to read tsconfig.json\n\n"},"contentCaptured":true,"contentTruncated":false}}} {"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":0,"timestamp":1793283383909,"type":"model.start","data":{"modelCallId":1,"parentCallId":null,"purpose":"title","model":"gpt-5.6-luna","toolChoice":"none","maxOutputTokens":128,"messageCount":1},"contentCaptured":true,"contentTruncated":false}}} {"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":0,"timestamp":1793283383909,"type":"model.admitted","data":{"modelCallId":1},"contentCaptured":true,"contentTruncated":false}}} {"type":"emit_inbound","label":"notification:libfx/hook","frame":{"kind":"notification","method":"libfx/hook","params":{"version":1,"sessionId":"fx-session-01","rootSessionId":"fx-session-01","parentSessionId":null,"sourceSessionId":null,"workId":null,"turnId":"d72093549e8e72f36a72d71ccbde93d6","executionId":"d72093549e8e72f36a72d71ccbde93d6","stepIndex":1,"timestamp":1793283383909,"type":"step.start","data":{},"contentCaptured":true,"contentTruncated":false}}} From 45c9a52b831bd531bee73b7b1a4ba52b6a4aa3b4 Mon Sep 17 00:00:00 2001 From: Jake Leventhal Date: Mon, 2 Nov 2026 10:10:20 -0500 Subject: [PATCH 03/13] fix(fx): settle terminal subagents before output arrives --- .../orchestration-v2/Adapters/FxAdapterV2.test.ts | 2 +- .../orchestration-v2/Adapters/FxAdapterV2.testkit.ts | 2 +- .../provider-acp/src/server/AcpSessionRuntime.ts | 8 -------- packages/provider-fx/src/server/acpSupport.ts | 2 +- packages/provider-fx/src/server/subagents.test.ts | 12 ++++++++++++ packages/provider-fx/src/server/subagents.ts | 8 ++++---- 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts index 844919e40e57..ba88225ad26c 100644 --- a/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts @@ -246,7 +246,7 @@ const openFxSession = Effect.fn("openFxSession")(function* (input: { ), childProcessSpawner, fileSystem, - sessionMeta: FX_HOOKS_SESSION_META, + sessionMeta: () => FX_HOOKS_SESSION_META, }), }); const threadId = ThreadId.make(`thread-fx-${input.scenario}`); diff --git a/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.testkit.ts b/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.testkit.ts index e41a12bf0dad..0c2d5def3738 100644 --- a/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.testkit.ts +++ b/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.testkit.ts @@ -65,7 +65,7 @@ function layerFxProviderAdapterRegistryReplay( childProcessSpawner, fileSystem, cancelBehavior: FX_ACP_CANCEL_BEHAVIOR, - sessionMeta: FX_HOOKS_SESSION_META, + sessionMeta: () => FX_HOOKS_SESSION_META, ...(replayGate === undefined ? {} : { replayGate }), })(runtimeInput), assertComplete: makeAcpReplayCompletenessAssertion(fileSystem, statusPath, transcript), diff --git a/packages/provider-acp/src/server/AcpSessionRuntime.ts b/packages/provider-acp/src/server/AcpSessionRuntime.ts index 3dd53c9e2c57..4544a4ac5ce9 100644 --- a/packages/provider-acp/src/server/AcpSessionRuntime.ts +++ b/packages/provider-acp/src/server/AcpSessionRuntime.ts @@ -103,8 +103,6 @@ export interface AcpSessionRuntimeOptions { readonly interruptPromptOnCancel?: boolean; readonly cancelMeta?: EffectAcpSchema.CancelNotification["_meta"]; readonly initializeMeta?: EffectAcpSchema.InitializeRequest["_meta"]; - /** Sent on every `session/new`, `session/load` and `session/resume`. */ - readonly sessionMeta?: EffectAcpSchema.NewSessionRequest["_meta"]; readonly ownDetachedProcessGroup?: boolean; readonly ownDescendantProcessGroups?: boolean; readonly processGroupPlatform?: NodeJS.Platform; @@ -2114,7 +2112,6 @@ export const make = Effect.fn("AcpSessionRuntime.make")(function* ( } return activationOptions?.mcpServers ?? options.mcpServers ?? []; }; - const sessionMeta = options.sessionMeta === undefined ? {} : { _meta: options.sessionMeta }; const sessionSetupFields = (initializeResult: EffectAcpSchema.InitializeResponse) => { const meta = options.sessionMeta?.(initializeResult); @@ -2193,7 +2190,6 @@ export const make = Effect.fn("AcpSessionRuntime.make")(function* ( cwd: options.cwd, ...additionalDirectories, mcpServers: sessionMcpServers(initializeResult), - ...sessionMeta, } satisfies EffectAcpSchema.LoadSessionRequest; sessionSetupResult = yield* runLoadSessionWithReplayIdle(loadPayload, initializeResult); } else if (initializeResult.agentCapabilities?.sessionCapabilities?.resume != null) { @@ -2202,7 +2198,6 @@ export const make = Effect.fn("AcpSessionRuntime.make")(function* ( cwd: options.cwd, ...additionalDirectories, mcpServers: sessionMcpServers(initializeResult), - ...sessionMeta, } satisfies EffectAcpSchema.ResumeSessionRequest; sessionSetupResult = yield* runLoggedRequest( "session/resume", @@ -2220,7 +2215,6 @@ export const make = Effect.fn("AcpSessionRuntime.make")(function* ( cwd: options.cwd, mcpServers: sessionMcpServers(initializeResult), ...additionalDirectories, - ...sessionMeta, } satisfies EffectAcpSchema.NewSessionRequest; const created = yield* runLoggedRequest( "session/new", @@ -2421,7 +2415,6 @@ export const make = Effect.fn("AcpSessionRuntime.make")(function* ( cwd: options.cwd, ...sessionSetupFields(started.initializeResult), mcpServers: sessionMcpServers(started.initializeResult, activationOptions), - ...sessionMeta, } satisfies EffectAcpSchema.LoadSessionRequest; return runLoadSessionWithReplayIdle(requestPayload, started.initializeResult); }), @@ -2435,7 +2428,6 @@ export const make = Effect.fn("AcpSessionRuntime.make")(function* ( cwd: options.cwd, ...sessionSetupFields(started.initializeResult), mcpServers: sessionMcpServers(started.initializeResult, activationOptions), - ...sessionMeta, } satisfies EffectAcpSchema.ResumeSessionRequest; return runLoggedRequest( "session/resume", diff --git a/packages/provider-fx/src/server/acpSupport.ts b/packages/provider-fx/src/server/acpSupport.ts index 7da2e6dec053..63d2b2eb4cd8 100644 --- a/packages/provider-fx/src/server/acpSupport.ts +++ b/packages/provider-fx/src/server/acpSupport.ts @@ -233,7 +233,7 @@ export const makeFxAcpRuntime = Effect.fn("makeFxAcpRuntime")(function* ( env: { ...environment }, }, cancelBehavior: FX_ACP_CANCEL_BEHAVIOR, - sessionMeta: FX_HOOKS_SESSION_META, + sessionMeta: () => FX_HOOKS_SESSION_META, }), ); return yield* Effect.service(AcpSessionRuntime.AcpSessionRuntime).pipe(Effect.provide(context)); diff --git a/packages/provider-fx/src/server/subagents.test.ts b/packages/provider-fx/src/server/subagents.test.ts index f6a81071885d..c67b57757de0 100644 --- a/packages/provider-fx/src/server/subagents.test.ts +++ b/packages/provider-fx/src/server/subagents.test.ts @@ -205,6 +205,18 @@ describe("fx sub-agent hooks", () => { ]); return { status: run.update?.status, result: run.update?.result, steps: run.delivered }; }; + assert.deepEqual( + ["failed", "yielded", "cancelled"].map((phase) => ({ + phase, + withoutOutput: settle(phase, []).status, + withPreview: settle(phase, [{ acp: preview("completed") }]).status, + })), + [ + { phase: "failed", withoutOutput: "failed", withPreview: "failed" }, + { phase: "yielded", withoutOutput: "idle", withPreview: "idle" }, + { phase: "cancelled", withoutOutput: "cancelled", withPreview: "cancelled" }, + ], + ); const failure = JSON.stringify({ ok: false, result: "No package.json", error_code: null }); const truncated = settle( diff --git a/packages/provider-fx/src/server/subagents.ts b/packages/provider-fx/src/server/subagents.ts index 52c20481c912..95bf731e42d8 100644 --- a/packages/provider-fx/src/server/subagents.ts +++ b/packages/provider-fx/src/server/subagents.ts @@ -131,12 +131,12 @@ function hookedSubagentStatus( hooks: FxSubagentHookState, ): AcpAdapterV2SubagentUpdate["status"] { if (tool.status === "failed") return hooks.phase === "cancelled" ? "cancelled" : "failed"; - // fx completes the ACP call before it reports the whole result. - if (hooks.output === undefined) return "running"; if (hooks.phase === "yielded") return "idle"; if (hooks.phase === "cancelled") return "cancelled"; - if (hooks.phase === "failed" || fxSubagentOutcome(hooks.output)?.ok === false) return "failed"; - return "completed"; + if (hooks.phase === "failed") return "failed"; + // fx completes the ACP call before it reports the whole result. + if (hooks.output === undefined) return "running"; + return fxSubagentOutcome(hooks.output)?.ok === false ? "failed" : "completed"; } /** Each `subagent` call becomes a child thread; with hooks it also gets the child's steps. */ From 7973145e5dec33cc02fc601a818619061ebd5f14 Mon Sep 17 00:00:00 2001 From: Jake Leventhal Date: Mon, 2 Nov 2026 10:20:50 -0500 Subject: [PATCH 04/13] fix(fx): retain nested delegation steps in child threads --- .../provider-fx/src/server/subagents.test.ts | 30 +++++++++++++++++++ packages/provider-fx/src/server/subagents.ts | 3 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/provider-fx/src/server/subagents.test.ts b/packages/provider-fx/src/server/subagents.test.ts index c67b57757de0..5fa60ce78d84 100644 --- a/packages/provider-fx/src/server/subagents.test.ts +++ b/packages/provider-fx/src/server/subagents.test.ts @@ -150,6 +150,18 @@ describe("fx sub-agent hooks", () => { status: "success", }), }, + { + hook: hook(CHILD, "tool.start", { + call: call("call_nested", "subagent", '{"action":"run","task":"Check scripts"}'), + }), + }, + { + hook: hook(CHILD, "tool.end", { + call: call("call_nested", "subagent"), + output: '{"ok":true,"result":"Scripts checked"}', + status: "success", + }), + }, { hook: hook(CHILD, "model.start", { modelCallId: 2, purpose: "permission_review" }) }, { hook: hook(CHILD, "model.end", { modelCallId: 2, output: "allow", toolCalls: [{}] }) }, { hook: hook(CHILD, "model.start", { modelCallId: 3, purpose: "agent" }) }, @@ -168,10 +180,28 @@ describe("fx sub-agent hooks", () => { "tool_call call_read in_progress", "tool_call_update call_read completed", "tool_call_update call_read completed", + "tool_call call_nested in_progress", + "tool_call_update call_nested completed", `agent_message result: ${REPLY}`, ]); const read = run.delivered[1]?.update; assert.deepInclude(read, { kind: "read", rawInput: { path: "package.json" } }); + let nested: AcpToolCallState | undefined; + for (const notification of run.delivered.slice(4, 6)) { + for (const event of parseSessionUpdateEvent(notification).events) { + if (event._tag !== "ToolCallUpdated") continue; + nested = mergeToolCallState(nested, event.toolCall); + assert.isUndefined(extractFxSubagentUpdate(nested)); + } + } + assert.deepInclude(nested, { + toolCallId: "call_nested", + status: "completed", + title: "subagent", + }); + assert.deepInclude(nested?.data, { + rawInput: { action: "run", task: "Check scripts" }, + }); // fx completes the ACP call before it reports the whole result. assert.deepEqual(run.statuses, [ "running", diff --git a/packages/provider-fx/src/server/subagents.ts b/packages/provider-fx/src/server/subagents.ts index 95bf731e42d8..d07a3cb922c6 100644 --- a/packages/provider-fx/src/server/subagents.ts +++ b/packages/provider-fx/src/server/subagents.ts @@ -214,7 +214,8 @@ function childToolCall(call: typeof HookCall.Type) { const rawInput = Option.getOrUndefined(decodeArguments(call.argumentsJson)); return { toolCallId: call.callId, - name: call.name, + // Nested delegations are child steps, not new top-level subagent tasks. + ...(call.name === FX_SUBAGENT_TOOL ? {} : { name: call.name }), title: call.name, kind: fxToolKind(call.name), ...(rawInput === undefined ? {} : { rawInput }), From 64bb64b95e485cc3db520442b718c92c087412e3 Mon Sep 17 00:00:00 2001 From: Jake Leventhal Date: Mon, 2 Nov 2026 10:40:17 -0500 Subject: [PATCH 05/13] fix(fx): retain full results after child settlement --- .../Adapters/FxAdapterV2.test.ts | 66 +++++++++++++++++++ docs/user/providers-fx.md | 2 +- packages/provider-acp/src/server/adapter.ts | 11 +++- packages/provider-fx/src/server/subagents.ts | 2 +- 4 files changed, 76 insertions(+), 5 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts index ba88225ad26c..7138431ccc5f 100644 --- a/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts @@ -433,6 +433,72 @@ describe("FxAdapterV2", () => { ], }).pipe(Effect.provide(testLayer), Effect.scoped), ); + it.effect("retains full replies arriving after failed or cancelled children settle", () => + Effect.gen(function* () { + for (const phase of ["failed", "cancelled"] as const) { + for (const withPreview of [false, true]) { + const reply = `Child ${phase}: ${"The complete diagnostic. ".repeat(20)}`; + const childSessionId = "fx-session-child"; + const events = yield* openFxSession({ + scenario: `late-${phase}-${withPreview}`, + runtimeMode: "approval-required", + turnModel: "default", + frames: [ + ...initialize, + outbound("session/new"), + answer("session/new", { + ...fxSession({ model: FX_DEFAULT_MODEL, modes: FX_MAIN_MODES }), + _meta: { fx: { hooksVersion: 1 } }, + }), + ...setConfig( + "mode", + "ask", + fxConfig({ model: FX_DEFAULT_MODEL, modes: FX_MAIN_MODES }), + ), + outbound("session/prompt"), + subagentCall("call_child", "Read package.json"), + hook(SESSION_ID, "delegation", { + callId: "call_child", + childSessionId, + phase: "dispatched", + }), + ...(withPreview ? [subagentPreview("call_child", reply)] : []), + hook(SESSION_ID, "delegation", { + callId: "call_child", + childSessionId, + phase, + }), + hook(SESSION_ID, "tool.end", { + call: { callId: "call_child", name: "subagent", provenance: "model" }, + output: JSON.stringify({ ok: false, result: reply, error_code: null }), + status: "success", + }), + answer("session/prompt", { stopReason: "end_turn" }), + ], + }); + const subagents = events.flatMap((event) => + event.type === "subagent.updated" ? [event.subagent] : [], + ); + const terminal = subagents.filter((subagent) => subagent.status === phase); + assert.isAtLeast(terminal.length, 2); + assert.notEqual(terminal[0]?.result, reply); + const settled = terminal.at(-1); + assert.equal(settled?.result, reply); + assert.equal(settled?.completedAt, terminal[0]?.completedAt); + assert.isTrue( + events.some( + (event) => + event.type === "turn_item.updated" && + event.turnItem.type === "subagent" && + event.turnItem.status === phase && + event.turnItem.result === reply, + ), + ); + } + } + }).pipe(Effect.provide(testLayer), Effect.scoped), + ); + // A restart reloads the session: fx replays the finished call from history // with its 200-byte preview and none of the child's steps, which T3 already // holds. The reload confirms hooks, so the next sub-agent shows its steps. diff --git a/docs/user/providers-fx.md b/docs/user/providers-fx.md index 0a321995405a..ac09545ecbb4 100644 --- a/docs/user/providers-fx.md +++ b/docs/user/providers-fx.md @@ -46,7 +46,7 @@ A message steered into a running turn stops that turn, and fx answers the new me When fx hands work to a sub-agent, it opens in its own thread under the parent. That thread shows the sub-agent's task, each tool it runs and what it says between them, and its full reply. A reply longer than 64 KiB is cut off with a note. Steps from a sub-agent's own sub-agents aren't shown. -fx versions before 0.0.14 don't report sub-agent steps, so there the thread shows only the task and a reply shortened to a couple of lines. +When fx does not confirm hook support, the thread shows only the task and a reply shortened to a couple of lines. fx doesn't pass on a sub-agent's permission requests, so a sub-agent that needs one waits until you press **Stop**. This mostly happens in **Supervised**. In **Auto**, sub-agents can finish routine work by themselves. diff --git a/packages/provider-acp/src/server/adapter.ts b/packages/provider-acp/src/server/adapter.ts index b7c77c404959..e7bdba92544e 100644 --- a/packages/provider-acp/src/server/adapter.ts +++ b/packages/provider-acp/src/server/adapter.ts @@ -2878,7 +2878,8 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( existing !== undefined && existing.task.status === update.status && updateIsTerminal && - existing.terminalStatusProjected + existing.terminalStatusProjected && + (update.result === null || update.result === existing.task.result) ) { return; } @@ -2931,7 +2932,9 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( model: update.model?.trim() || existing?.task.model || null, status: taskStatus, result: update.result ?? existing?.task.result ?? null, - completedAt: acpSubagentStatusIsTerminal(taskStatus) ? now : null, + completedAt: acpSubagentStatusIsTerminal(taskStatus) + ? (existing?.task.completedAt ?? now) + : null, updatedAt: now, }; const subagent: ActiveAcpSubagent = existing ?? { @@ -3058,7 +3061,9 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( ...subagent.task, status: taskStatus, result, - completedAt: acpSubagentStatusIsTerminal(taskStatus) ? now : null, + completedAt: acpSubagentStatusIsTerminal(taskStatus) + ? (subagent.task.completedAt ?? now) + : null, updatedAt: now, }; const providerThreadId = subagent.task.providerThreadId; diff --git a/packages/provider-fx/src/server/subagents.ts b/packages/provider-fx/src/server/subagents.ts index d07a3cb922c6..fee26a1b1223 100644 --- a/packages/provider-fx/src/server/subagents.ts +++ b/packages/provider-fx/src/server/subagents.ts @@ -417,7 +417,7 @@ type SessionUpdateHandler = Parameters< /** * Routes fx's hook events through the adapter's session update handlers. A - * session fx does not confirm hooks for (fx 0.0.13) keeps the ACP-only view. + * session fx does not confirm hooks for keeps the ACP-only view. */ export function withFxSubagentHooks( runtime: AcpSessionRuntime.AcpSessionRuntime["Service"], From 1a05dfb92be541317fe1bc1b905cc3784cdef826 Mon Sep 17 00:00:00 2001 From: Jake Leventhal Date: Mon, 2 Nov 2026 12:24:40 -0500 Subject: [PATCH 06/13] fix(fx): emit child tool completion only once --- packages/provider-fx/src/server/subagents.test.ts | 3 +-- packages/provider-fx/src/server/subagents.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/provider-fx/src/server/subagents.test.ts b/packages/provider-fx/src/server/subagents.test.ts index 5fa60ce78d84..cdca87f91dca 100644 --- a/packages/provider-fx/src/server/subagents.test.ts +++ b/packages/provider-fx/src/server/subagents.test.ts @@ -179,7 +179,6 @@ describe("fx sub-agent hooks", () => { "agent_message exec-child:1: Reading package.json.", "tool_call call_read in_progress", "tool_call_update call_read completed", - "tool_call_update call_read completed", "tool_call call_nested in_progress", "tool_call_update call_nested completed", `agent_message result: ${REPLY}`, @@ -187,7 +186,7 @@ describe("fx sub-agent hooks", () => { const read = run.delivered[1]?.update; assert.deepInclude(read, { kind: "read", rawInput: { path: "package.json" } }); let nested: AcpToolCallState | undefined; - for (const notification of run.delivered.slice(4, 6)) { + for (const notification of run.delivered.slice(3, 5)) { for (const event of parseSessionUpdateEvent(notification).events) { if (event._tag !== "ToolCallUpdated") continue; nested = mergeToolCallState(nested, event.toolCall); diff --git a/packages/provider-fx/src/server/subagents.ts b/packages/provider-fx/src/server/subagents.ts index fee26a1b1223..d0758b53ddd5 100644 --- a/packages/provider-fx/src/server/subagents.ts +++ b/packages/provider-fx/src/server/subagents.ts @@ -326,8 +326,7 @@ export function makeFxSubagentHookRouter() { }, ]; } - case "tool.end": - case "tool.result": { + case "tool.end": { const outcome = Option.getOrUndefined(decodeToolOutcome(event.data)); if (outcome === undefined) return []; return [ From 3ca3a2aba83782957841b3d72f076b37ef941802 Mon Sep 17 00:00:00 2001 From: Jake Leventhal Date: Mon, 2 Nov 2026 13:10:53 -0500 Subject: [PATCH 07/13] fix(acp): retain full child results after finalization --- .../Adapters/AcpAdapterV2.test.ts | 79 ++++++++++++------- packages/provider-acp/src/server/adapter.ts | 6 +- 2 files changed, 54 insertions(+), 31 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts index 07c3bd731633..dd7518e7f1cc 100644 --- a/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts @@ -6092,7 +6092,7 @@ describe("AcpAdapterV2", () => { ); it.effect( - "projects a child terminal in the completed-root finalization window exactly once", + "projects changed full results in the completed-root finalization window once per result", () => Effect.gen(function* () { const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; @@ -6123,6 +6123,7 @@ describe("AcpAdapterV2", () => { const secondChildSessionId = "mock-child-session-finalize-window-second"; let firstSubagentStatus: "running" | "completed" = "running"; let secondSubagentStatus: "waiting" | "completed" = "waiting"; + let secondSubagentResult: string | null = "SECOND_DONE"; type RuntimeService = AcpSessionRuntime.AcpSessionRuntime["Service"]; let sessionUpdateHandler: | Parameters[0] @@ -6160,7 +6161,7 @@ describe("AcpAdapterV2", () => { model: null, status: secondSubagentStatus, childSessionId: secondChildSessionId, - result: secondSubagentStatus === "completed" ? "SECOND_DONE" : null, + result: secondSubagentStatus === "completed" ? secondSubagentResult : null, suppressNormalTool: true, } as AcpAdapterV2SubagentUpdate; } @@ -6233,10 +6234,6 @@ describe("AcpAdapterV2", () => { rawOutput: { content: "FIRST_DONE" }, }, }).pipe(Effect.provideService(Clock.Clock, blockingClock)); - for (let attempt = 0; attempt < 10; attempt += 1) { - yield* Effect.yieldNow; - } - yield* sessionUpdateHandler!({ sessionId: "mock-session-1", update: { @@ -6250,8 +6247,6 @@ describe("AcpAdapterV2", () => { }); assert.isTrue(yield* hasPendingBackgroundWork); - while (Option.isSome(yield* Queue.poll(events))) {} - blockNextClockRead = true; const adjustFiber = yield* TestClock.adjust("3 seconds").pipe(Effect.forkScoped); yield* Deferred.await(finalizationClockRead); @@ -6267,35 +6262,59 @@ describe("AcpAdapterV2", () => { rawOutput: { content: "SECOND_DONE" }, }, }); + for (const result of ["SECOND_FULL_RESULT", "SECOND_FULL_RESULT", null]) { + secondSubagentResult = result; + yield* sessionUpdateHandler!({ + sessionId: secondChildSessionId, + update: { + sessionUpdate: "tool_call_update", + toolCallId: "tool-call-generic-2", + title: "second background subagent", + kind: "other", + status: "completed", + rawOutput: { content: result }, + }, + }); + } yield* Deferred.succeed(releaseFinalizationClockRead, undefined); yield* Fiber.join(adjustFiber); - yield* Effect.yieldNow; - yield* Effect.yieldNow; const providerTurnId = idAllocator.derive.providerTurn({ driver: ACP_TEST_DRIVER, nativeTurnId: acpScopedNativeId(instanceId, "mock-session-1:turn:1"), }); - let completedSubagentUpdates = 0; - let rootTerminalStatus: string | null = null; - let polled = yield* Queue.poll(events); - while (Option.isSome(polled)) { - const event = polled.value; - if ( - event.type === "turn_item.updated" && - event.turnItem.type === "subagent" && - event.turnItem.nativeItemRef?.nativeId === "task-finalize-window-second" && - event.turnItem.status === "completed" - ) { - completedSubagentUpdates += 1; - } - if (event.type === "turn.terminal" && event.providerTurnId === providerTurnId) { - rootTerminalStatus = event.status; - } - polled = yield* Queue.poll(events); - } - assert.equal(rootTerminalStatus, "completed"); - assert.equal(completedSubagentUpdates, 1); + const observed = yield* Stream.fromQueue(events).pipe( + Stream.takeUntil( + (event) => event.type === "turn.terminal" && event.providerTurnId === providerTurnId, + ), + Stream.runCollect, + ); + const terminal = observed.at(-1); + assert.equal( + terminal?.type === "turn.terminal" ? terminal.status : undefined, + "completed", + ); + const results = observed.flatMap((event) => + event.type === "turn_item.updated" && + event.turnItem.type === "subagent" && + event.turnItem.nativeItemRef?.nativeId === "task-finalize-window-second" && + event.turnItem.status === "completed" + ? [event.turnItem.result] + : [], + ); + assert.deepEqual(results, ["SECOND_DONE", "SECOND_FULL_RESULT"]); + const childUpdates = observed.flatMap((event) => + event.type === "subagent.updated" && + event.subagent.nativeTaskRef?.nativeId === "task-finalize-window-second" && + event.subagent.status === "completed" + ? [event.subagent] + : [], + ); + assert.deepEqual( + childUpdates.map(({ result }) => result), + results, + ); + assert.equal(childUpdates[1]?.completedAt, childUpdates[0]?.completedAt); assert.isFalse( yield* hasPendingBackgroundWork, "finalize-window terminal must clear the carryover pin", diff --git a/packages/provider-acp/src/server/adapter.ts b/packages/provider-acp/src/server/adapter.ts index e7bdba92544e..9035633bc928 100644 --- a/packages/provider-acp/src/server/adapter.ts +++ b/packages/provider-acp/src/server/adapter.ts @@ -5868,7 +5868,11 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( ) { if (!acpSubagentStatusIsTerminal(update.status)) return false; if (acpSubagentStatusIsTerminal(subagent.task.status)) { - if (subagent.terminalStatusProjected || context.finalizedStatus !== "completed") { + if ( + context.finalizedStatus !== "completed" || + (subagent.terminalStatusProjected && + (update.result === null || update.result === subagent.task.result)) + ) { return true; } } From 1fa593a3c42e5ad718845e3c4e58b10930cf4322 Mon Sep 17 00:00:00 2001 From: Jake Leventhal Date: Mon, 2 Nov 2026 13:21:07 -0500 Subject: [PATCH 08/13] fix(acp): replace child reply when full result arrives --- .../orchestration-v2/Adapters/AcpAdapterV2.test.ts | 12 ++++++++++++ packages/provider-acp/src/server/adapter.ts | 10 +++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts index dd7518e7f1cc..2ece8ad0b68b 100644 --- a/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts @@ -6315,6 +6315,18 @@ describe("AcpAdapterV2", () => { results, ); assert.equal(childUpdates[1]?.completedAt, childUpdates[0]?.completedAt); + const replies = observed.flatMap((event) => + event.type === "message.updated" && + event.message.threadId === childUpdates[0]?.childThreadId && + event.message.role === "assistant" + ? [event.message] + : [], + ); + assert.deepEqual( + replies.map(({ text }) => text), + results, + ); + assert.equal(replies[1]?.id, replies[0]?.id); assert.isFalse( yield* hasPendingBackgroundWork, "finalize-window terminal must clear the carryover pin", diff --git a/packages/provider-acp/src/server/adapter.ts b/packages/provider-acp/src/server/adapter.ts index 9035633bc928..ce065df1e006 100644 --- a/packages/provider-acp/src/server/adapter.ts +++ b/packages/provider-acp/src/server/adapter.ts @@ -3049,12 +3049,16 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( ); } + const previousResultMessage = subagent.assistantMessages.get( + `${nativeTaskId}:message:result`, + ); if ( taskStatus !== "running" && - subagent.assistantText.length === 0 && - update.result !== null + update.result !== null && + (subagent.assistantText.length === 0 || + (previousResultMessage !== undefined && previousResultMessage !== update.result)) ) { - yield* emitSubagentAssistant(subagent, update.result); + yield* emitSubagentAssistant(subagent, update.result, "replace"); } const result = update.result ?? subagent.task.result ?? (subagent.assistantText || null); subagent.task = { From 0c9e646c47ebc62075b848e998404adcb82994fe Mon Sep 17 00:00:00 2001 From: Jake Leventhal Date: Mon, 2 Nov 2026 13:26:08 -0500 Subject: [PATCH 09/13] fix(acp): preserve native child narration when results settle --- packages/provider-acp/src/server/adapter.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/provider-acp/src/server/adapter.ts b/packages/provider-acp/src/server/adapter.ts index ce065df1e006..c76c7c4a5cd1 100644 --- a/packages/provider-acp/src/server/adapter.ts +++ b/packages/provider-acp/src/server/adapter.ts @@ -1402,6 +1402,7 @@ interface ActiveAcpSubagent { readonly parentProviderThreadId: ProviderThreadId; childSessionId: string | null; assistantText: string; + hasSyntheticResultMessage: boolean; readonly assistantMessages: Map; readonly childItemOrdinals: Map; nextChildOrdinal: number; @@ -2712,6 +2713,10 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( ) { if (text.length === 0 && mode === "append") return; const nativeItemId = `${subagent.task.nativeTaskRef?.nativeId ?? subagent.task.id}:message:${messageId ?? "result"}`; + // Unidentified native chunks share the fallback result slot. + if (messageId == null || messageId === "result") { + subagent.hasSyntheticResultMessage = false; + } const previous = subagent.assistantMessages.get(nativeItemId) ?? ""; const messageText = mode === "replace" ? text : `${previous}${text}`; subagent.assistantMessages.set(nativeItemId, messageText); @@ -2947,6 +2952,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( parentProviderThreadId: context.input.providerThread.id, childSessionId: null, assistantText: "", + hasSyntheticResultMessage: false, assistantMessages: new Map(), childItemOrdinals: new Map(), nextChildOrdinal: 101, @@ -3049,9 +3055,9 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( ); } - const previousResultMessage = subagent.assistantMessages.get( - `${nativeTaskId}:message:result`, - ); + const previousResultMessage = subagent.hasSyntheticResultMessage + ? subagent.assistantMessages.get(`${nativeTaskId}:message:result`) + : undefined; if ( taskStatus !== "running" && update.result !== null && @@ -3059,6 +3065,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( (previousResultMessage !== undefined && previousResultMessage !== update.result)) ) { yield* emitSubagentAssistant(subagent, update.result, "replace"); + subagent.hasSyntheticResultMessage = true; } const result = update.result ?? subagent.task.result ?? (subagent.assistantText || null); subagent.task = { @@ -3174,6 +3181,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( // A new execution replaces its result; live ownership transfers keep their progress. child.subagent.task = { ...child.subagent.task, result: null }; child.subagent.assistantText = ""; + child.subagent.hasSyntheticResultMessage = false; child.subagent.assistantMessages.clear(); } child.status = status; From b8aca54175bc58204ffb0fed470116b04f4d5d92 Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Fri, 6 Nov 2026 08:16:07 +0000 Subject: [PATCH 10/13] fix(fx): keep late subagent results parseable after the turn ends --- packages/provider-fx/src/server/subagents.test.ts | 11 +++++++++++ packages/provider-fx/src/server/subagents.ts | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/packages/provider-fx/src/server/subagents.test.ts b/packages/provider-fx/src/server/subagents.test.ts index cdca87f91dca..b6f4dde3d931 100644 --- a/packages/provider-fx/src/server/subagents.test.ts +++ b/packages/provider-fx/src/server/subagents.test.ts @@ -274,6 +274,17 @@ describe("fx sub-agent hooks", () => { status: "cancelled", result: "cancelled", }); + + // After the parent turn ends, the adapter parses a late root update without prior tool state. + const router = makeFxSubagentHookRouter(); + router.enrich(announce); + router.route(delegation("dispatched")); + const late = router.route(rootResult(WHOLE_RESULT)).at(-1); + if (late === undefined) throw new Error("the whole result produced no root update"); + const alone = parseSessionUpdateEvent(late).events.flatMap((event) => + event._tag === "ToolCallUpdated" ? [extractFxSubagentUpdate(event.toolCall)] : [], + ); + assert.deepInclude(alone[0], { status: "completed", result: REPLY }); }); it("ignores hooks for calls fx never announced over ACP and for sessions it never dispatched", () => { diff --git a/packages/provider-fx/src/server/subagents.ts b/packages/provider-fx/src/server/subagents.ts index 68411a1bed3c..5af1123ebabc 100644 --- a/packages/provider-fx/src/server/subagents.ts +++ b/packages/provider-fx/src/server/subagents.ts @@ -232,6 +232,8 @@ interface RootSubagentCall { readonly sessionId: string; /** fx's ACP `tool_call` arrived, so a hook-made update cannot invent a tool. */ announced: boolean; + /** Repeated on hook-made updates: one landing after the turn is parsed alone. */ + rawInput?: unknown; state: FxSubagentHookState; } @@ -257,6 +259,8 @@ export function makeFxSubagentHookRouter() { update: { sessionUpdate: "tool_call_update", toolCallId: callId, + name: FX_SUBAGENT_TOOL, + rawInput: call.rawInput, _meta: { [HOOK_STATE_META_KEY]: call.state }, }, }, @@ -400,6 +404,7 @@ export function makeFxSubagentHookRouter() { calls.set(update.toolCallId, call); } call.announced = true; + if (update.rawInput !== undefined) call.rawInput = update.rawInput; return { ...notification, update: { ...update, _meta: { ...update._meta, [HOOK_STATE_META_KEY]: call.state } }, From 065b3c62e5b3ca6dc0c07869fdc5ad9911896407 Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Sun, 15 Nov 2026 02:37:57 +0000 Subject: [PATCH 11/13] fix(fx): keep failed child diagnostics out of replies --- .../src/orchestration-v2/Adapters/FxAdapterV2.test.ts | 11 +++++++++++ packages/provider-fx/src/server/subagents.test.ts | 5 +++++ packages/provider-fx/src/server/subagents.ts | 6 +++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts index 90a66b7995f6..fd11ce2b6572 100644 --- a/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts @@ -489,6 +489,17 @@ describe("FxAdapterV2", () => { const settled = terminal.at(-1); assert.equal(settled?.result, reply); assert.equal(settled?.completedAt, terminal[0]?.completedAt); + if (phase === "failed") { + assert.isFalse( + events.some( + (event) => + event.type === "message.updated" && + event.message.threadId === settled?.childThreadId && + event.message.role === "assistant", + ), + "a failed result stays on the task without inventing an assistant reply", + ); + } assert.isTrue( events.some( (event) => diff --git a/packages/provider-fx/src/server/subagents.test.ts b/packages/provider-fx/src/server/subagents.test.ts index b6f4dde3d931..b6df0c3dbeb6 100644 --- a/packages/provider-fx/src/server/subagents.test.ts +++ b/packages/provider-fx/src/server/subagents.test.ts @@ -262,6 +262,11 @@ describe("fx sub-agent hooks", () => { settle("failed", [{ acp: preview("completed", failure) }], rootResult(failure)), { status: "failed", result: "No package.json" }, ); + for (const phase of ["failed", "cancelled", "completed"]) { + const failed = settle(phase, [], rootResult(failure)); + assert.lengthOf(failed.steps, 0, "a failed result is a diagnostic, not an assistant reply"); + assert.equal(failed.result, "No package.json"); + } const yielded = JSON.stringify({ ok: true, result: "Still running.", pending: true }); const pending = settle( "yielded", diff --git a/packages/provider-fx/src/server/subagents.ts b/packages/provider-fx/src/server/subagents.ts index 5af1123ebabc..7dfd1263b821 100644 --- a/packages/provider-fx/src/server/subagents.ts +++ b/packages/provider-fx/src/server/subagents.ts @@ -300,7 +300,11 @@ export function makeFxSubagentHookRouter() { const reply = fxSubagentReply({ ...call.state, output }); return [ // The reply goes in before the call settles, so the child thread ends on it. - ...(childSessionId === undefined || phase === "yielded" || reply.length === 0 + ...(childSessionId === undefined || + phase === "yielded" || + phase === "failed" || + fxSubagentOutcome(output)?.ok === false || + reply.length === 0 ? [] : [ { From 64d1ced6f279cfa36f6597934cb08973fb886447 Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Sun, 15 Nov 2026 02:49:25 +0000 Subject: [PATCH 12/13] test(fx): align hook replay with compaction capability --- .../testkit/fixtures/fx_subagent_hooks/fx_transcript.ndjson | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent_hooks/fx_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent_hooks/fx_transcript.ndjson index 177156728ad5..4784392346da 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent_hooks/fx_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/fx_subagent_hooks/fx_transcript.ndjson @@ -1,5 +1,5 @@ {"type":"transcript_start","provider":"fx","protocol":"acp.ndjson-jsonrpc","version":"1","scenario":"fx_subagent_hooks","metadata":{"generatedBy":"live-fx-recorder","fxVersion":"0.0.15","normalization":"Session ids are fx-session-NN, the workspace is , HOME is /home/fx-replay and the recording user is fx-replay. T3-owned prompt text, MCP servers and initialize params other than clientCapabilities and _meta are . User skill names, emails and token-shaped strings are removed. libfx/hook model.start events keep their ids and purpose but not their request content. Timestamps are kept as recorded.","droppedFrames":0}} -{"type":"expect_outbound","label":"request:initialize","frame":{"kind":"request","method":"initialize","params":{"protocolVersion":2,"info":"","capabilities":"","clientCapabilities":{"fs":{"readTextFile":false,"writeTextFile":false},"terminal":false,"session":{"notices":{}},"elicitation":{"form":{}}},"clientInfo":""}}} +{"type":"expect_outbound","label":"request:initialize","frame":{"kind":"request","method":"initialize","params":{"protocolVersion":2,"info":"","capabilities":"","clientCapabilities":{"fs":{"readTextFile":false,"writeTextFile":false},"terminal":false,"session":{"notices":{},"compaction":{}},"elicitation":{"form":{}}},"clientInfo":""}}} {"type":"emit_inbound","label":"response:initialize","frame":{"kind":"response","method":"initialize","result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"promptCapabilities":{"image":true,"audio":false,"embeddedContext":true},"mcpCapabilities":{"http":true,"sse":false,"acp":false},"sessionCapabilities":{"list":{},"resume":{},"close":{},"systemPrompt":{}},"_meta":{"fx":{"steering":true}}},"agentInfo":{"name":"fx","title":"fx","version":"0.0.15"},"authMethods":[]}}} {"type":"expect_outbound","label":"request:session/new","frame":{"kind":"request","method":"session/new","params":{"cwd":"","mcpServers":"","_meta":{"fx":{"hooks":{"captureContent":true}}}}}} {"type":"emit_inbound","label":"response:session/new","frame":{"kind":"response","method":"session/new","result":{"sessionId":"fx-session-01","configOptions":[{"id":"provider","name":"Provider","category":"model","type":"select","currentValue":"codex","options":[{"value":"gateway","name":"Vercel AI Gateway"},{"value":"codex","name":"Codex subscription"},{"value":"grok","name":"Grok subscription"}]},{"id":"model","name":"Model","category":"model","type":"select","currentValue":"gpt-6.1-sol","options":[{"value":"gpt-6.1-sol","name":"gpt-6.1-sol"},{"value":"gpt-6-astra","name":"gpt-6-astra"},{"value":"gpt-6.1-terra","name":"gpt-6.1-terra"},{"value":"gpt-6-sol","name":"gpt-6-sol"},{"value":"gpt-6-luna","name":"gpt-6-luna"},{"value":"gpt-5.6-sol","name":"gpt-5.6-sol"},{"value":"gpt-5.6-terra","name":"gpt-5.6-terra"},{"value":"gpt-5.6-luna","name":"gpt-5.6-luna"},{"value":"gpt-daybreak-blue-latest","name":"gpt-daybreak-blue-latest"}]},{"id":"mode","name":"Session Mode","description":"Controls how the agent requests permission","category":"mode","type":"select","currentValue":"auto","options":[{"value":"auto","name":"Auto","description":"Check actions with an automatic safety review instead of asking","_meta":{"fx":{"permissionMode":"auto"}}},{"value":"ask","name":"Ask","description":"Request permission before making any changes","_meta":{"fx":{"permissionMode":"ask"}}},{"value":"full-access","name":"Full access","description":"Run every action without asking, outside the sandbox","_meta":{"fx":{"permissionMode":"yolo"}}}]},{"id":"effort","name":"Reasoning Effort","description":"Controls how much the model thinks before responding","category":"thought_level","type":"select","currentValue":"auto","options":[{"value":"auto","name":"default"},{"value":"low","name":"low"},{"value":"medium","name":"medium"},{"value":"high","name":"high"},{"value":"xhigh","name":"xhigh"},{"value":"max","name":"max"},{"value":"ultra","name":"ultra"}]},{"id":"fast","name":"Fast Mode","description":"Uses the model's fast lane","category":"model","type":"select","currentValue":"false","options":[{"value":"false","name":"off"},{"value":"true","name":"on"}]}],"modes":{"currentModeId":"auto","availableModes":[{"id":"auto","name":"Auto","description":"Check actions with an automatic safety review instead of asking"},{"id":"ask","name":"Ask","description":"Request permission before making any changes"},{"id":"full-access","name":"Full access","description":"Run every action without asking, outside the sandbox"}]},"_meta":{"fx":{"hooksVersion":1}}}}} From 1ca8f10ae652f0dab785ed4b2d78934f071469a4 Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Sun, 15 Nov 2026 06:05:17 +0000 Subject: [PATCH 13/13] fix(acp): retain full child results after turn cleanup --- .../Adapters/AcpAdapterV2.test.ts | 46 ++++++++++++++++++- packages/provider-acp/src/server/adapter.ts | 23 ++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts index ad3a60911b4b..0de06d34bc1a 100644 --- a/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts @@ -8193,7 +8193,7 @@ describe("AcpAdapterV2", () => { driver: ACP_TEST_DRIVER, nativeTurnId: acpScopedNativeId(instanceId, "mock-session-1:turn:1"), }); - const observed = yield* Stream.fromQueue(events).pipe( + let observed = yield* Stream.fromQueue(events).pipe( Stream.takeUntil( (event) => event.type === "turn.terminal" && event.providerTurnId === providerTurnId, ), @@ -8204,6 +8204,42 @@ describe("AcpAdapterV2", () => { terminal?.type === "turn.terminal" ? terminal.status : undefined, "completed", ); + yield* sessionUpdateHandler!({ + sessionId: secondChildSessionId, + update: { + sessionUpdate: "agent_message", + messageId: "late-child-message", + content: [{ type: "text", text: "LATE_CHILD_ONLY" }], + }, + }); + const snapshot = yield* runtime.readThreadSnapshot({ providerThread }); + assert.isFalse( + snapshot.messages.some((message) => message.text.includes("LATE_CHILD_ONLY")), + ); + for (const result of ["AFTER_CLEANUP_FULL_RESULT", "AFTER_CLEANUP_FULL_RESULT", null]) { + secondSubagentResult = result; + yield* sessionUpdateHandler!({ + sessionId: "mock-session-1", + update: { + sessionUpdate: "tool_call_update", + toolCallId: "tool-call-generic-2", + title: "second background subagent", + kind: "other", + status: "completed", + rawOutput: { content: result }, + }, + }); + } + const late = yield* Stream.fromQueue(events).pipe( + Stream.takeUntil( + (event) => + event.type === "turn_item.updated" && + event.turnItem.type === "subagent" && + event.turnItem.result === "AFTER_CLEANUP_FULL_RESULT", + ), + Stream.runCollect, + ); + observed = [...observed, ...late, ...(yield* Queue.clear(events))]; const results = observed.flatMap((event) => event.type === "turn_item.updated" && event.turnItem.type === "subagent" && @@ -8212,7 +8248,11 @@ describe("AcpAdapterV2", () => { ? [event.turnItem.result] : [], ); - assert.deepEqual(results, ["SECOND_DONE", "SECOND_FULL_RESULT"]); + assert.deepEqual(results, [ + "SECOND_DONE", + "SECOND_FULL_RESULT", + "AFTER_CLEANUP_FULL_RESULT", + ]); const childUpdates = observed.flatMap((event) => event.type === "subagent.updated" && event.subagent.nativeTaskRef?.nativeId === "task-finalize-window-second" && @@ -8225,6 +8265,7 @@ describe("AcpAdapterV2", () => { results, ); assert.equal(childUpdates[1]?.completedAt, childUpdates[0]?.completedAt); + assert.equal(childUpdates[2]?.completedAt, childUpdates[0]?.completedAt); const replies = observed.flatMap((event) => event.type === "message.updated" && event.message.threadId === childUpdates[0]?.childThreadId && @@ -8237,6 +8278,7 @@ describe("AcpAdapterV2", () => { results, ); assert.equal(replies[1]?.id, replies[0]?.id); + assert.equal(replies[2]?.id, replies[0]?.id); assert.isFalse( yield* hasPendingBackgroundWork, "finalize-window terminal must clear the carryover pin", diff --git a/packages/provider-acp/src/server/adapter.ts b/packages/provider-acp/src/server/adapter.ts index 8e3ab258826f..73ab3592bb16 100644 --- a/packages/provider-acp/src/server/adapter.ts +++ b/packages/provider-acp/src/server/adapter.ts @@ -2035,6 +2035,8 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( input.runtimePolicy; const events = yield* Queue.unbounded(); const activeTurn = yield* Ref.make(null); + // Keep one settled owner so late full results can enrich its existing child rows. + const completedSubagentTurn = yield* Ref.make(null); // Keyed by the agent's steer id; an injection can arrive before its send returns. const steerInjections = new Map>(); const steerInjection = (steerId: string) => { @@ -5140,6 +5142,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( let applyFinalizedActiveTurnSubagentTerminal: ( context: ActiveAcpTurn, notification: EffectAcpSchema.SessionNotification, + enrichOnly?: boolean, ) => Effect.Effect = () => Effect.succeed(false); const projectAgentTerminalUpdate = Effect.fnUntraced(function* ( @@ -5445,6 +5448,13 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( if (yield* Ref.get(stoppedRunQuarantine)) { return; } + const completed = yield* Ref.get(completedSubagentTurn); + if ( + completed !== null && + completed.nativeThreadId === (yield* Ref.get(activeSessionId)) && + (yield* applyFinalizedActiveTurnSubagentTerminal(completed, notification, true)) + ) + return; const bufferOutcome = yield* bufferPostSettleWake(notification); // Completed roots retain subscribers and can project terminals now; interrupted roots may lack runId ownership. const carryover = yield* Ref.get(carryoverSubagents); @@ -5527,6 +5537,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( if (wakeOutcome.stopProcessing) { return; } + if (completed?.subagentsBySessionId.has(notification.sessionId)) return; if ( update.sessionUpdate === "user_message_chunk" || update.sessionUpdate === "agent_message_chunk" || @@ -6499,6 +6510,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( applyFinalizedActiveTurnSubagentTerminal = Effect.fnUntraced(function* ( context: ActiveAcpTurn, notification: EffectAcpSchema.SessionNotification, + enrichOnly = false, ) { if (flavor.extractSubagentUpdate === undefined) return false; @@ -6507,6 +6519,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( update: AcpAdapterV2SubagentUpdate, ) { if (!acpSubagentStatusIsTerminal(update.status)) return false; + if (enrichOnly && !acpSubagentStatusIsTerminal(subagent.task.status)) return false; if (acpSubagentStatusIsTerminal(subagent.task.status)) { if ( context.finalizedStatus !== "completed" || @@ -7515,6 +7528,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( yield* Ref.set(midTurnUnreportedCompletedTaskIds, new Set()); yield* Ref.set(handledBackgroundTaskIdsInActiveTurn, new Set()); yield* Ref.set(carryoverSubagents, null); + yield* Ref.set(completedSubagentTurn, null); yield* Ref.set(suppressPostSettleMonitorPrompt, false); yield* Ref.set(lastTurnRoute, null); }), @@ -7579,6 +7593,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( const mcpContext = yield* readMcpContext(threadId); const activationOptions = acpMcpActivation(mcpContext); prepareTerminalEnvironment(mcpContext, sessionId); + yield* Ref.set(completedSubagentTurn, null); const activated = canLoadSession ? yield* runtime.loadSession(sessionId, activationOptions) : canResumeSession @@ -8011,6 +8026,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( yield* Ref.set(detachedBackgroundCommands, new Map()); yield* Ref.set(midTurnUnreportedCompletedTaskIds, new Set()); yield* Ref.set(carryoverSubagents, null); + yield* Ref.set(completedSubagentTurn, null); yield* Ref.set(lastTurnRoute, null); }), ); @@ -8192,6 +8208,12 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( }); } } + yield* Ref.set( + completedSubagentTurn, + settledStatus === "completed" && !directStopQuarantine && context.subagents.size > 0 + ? context + : null, + ); yield* Ref.set(activeTurn, null); if (settledStatus === "completed" && !directStopQuarantine) { yield* detachBackgroundCommands(context); @@ -9718,6 +9740,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( snapshotInput.providerThread.appThreadId, ); prepareTerminalEnvironment(mcpContext, sessionId); + yield* Ref.set(completedSubagentTurn, null); const activated = yield* runtime.loadSession( sessionId, acpMcpActivation(mcpContext),