From 258980f7dc5380e64cc0a610c7dffe3576acb1ea Mon Sep 17 00:00:00 2001 From: Jake Leventhal Date: Thu, 29 Oct 2026 10:29:15 -0400 Subject: [PATCH 1/9] 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 73ab4023f732c..3c8bcf4871b2c 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 d043ac09a6aee..1c2d9d8f889dc 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 b6e78f8115d69..844919e40e57c 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 2161c30a4f51c..e41a12bf0dad7 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 5c684b21bc327..f69de75a41dac 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 8760ac7ffbec7..7ce830263f666 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 e2ddcf46159a3..e72e1dd0ec0dd 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 0000000000000..81617d839ee05 --- /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 0000000000000..a3da4225d37d4 --- /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 0000000000000..1e778bf8b3bc5 --- /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 f242c8e0a467d..d4c001cc970ab 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 d394fdf4c778a..286451afd3fd2 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 6780dd2a3ad48..61a4749cf906d 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 84d694c6a1575..8d35c26e783df 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 6a1c048f6aff9..cf3d7f8da0efb 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 34f26d8efb330..9a6ef4fe05012 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 c0e0b356fdd8d..d88418614b757 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 430f7a705a499..0a321995405ab 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 4544a4ac5ce9d..3dd53c9e2c574 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 fb6dfae56ef86..7da2e6dec0539 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 dfb956e6d13f1..7cd46dd02aaa2 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 0000000000000..f6a81071885d2 --- /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 0000000000000..52c20481c912b --- /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 8a3258b9ce5b8..d777a98b7adaa 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 2/9] 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 3c8bcf4871b2c..9cbf1a0b46607 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 a3da4225d37d4..177156728ad55 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 3/9] 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 844919e40e57c..ba88225ad26cf 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 e41a12bf0dad7..0c2d5def3738a 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 3dd53c9e2c574..4544a4ac5ce9d 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 7da2e6dec0539..63d2b2eb4cd88 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 f6a81071885d2..c67b57757de05 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 52c20481c912b..95bf731e42d86 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 4/9] 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 c67b57757de05..5fa60ce78d842 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 95bf731e42d86..d07a3cb922c6c 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 5/9] 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 ba88225ad26cf..7138431ccc5fb 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 0a321995405ab..ac09545ecbb43 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 b7c77c4049591..e7bdba92544e8 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 d07a3cb922c6c..fee26a1b1223d 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 6/9] 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 5fa60ce78d842..cdca87f91dcaa 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 fee26a1b1223d..d0758b53ddd5f 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 7/9] 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 07c3bd731633f..dd7518e7f1cc0 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 e7bdba92544e8..9035633bc928b 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 8/9] 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 dd7518e7f1cc0..2ece8ad0b68be 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 9035633bc928b..ce065df1e0062 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 9/9] 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 ce065df1e0062..c76c7c4a5cd1f 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;