From 77b6a162fbdfe9e51622b62ebb67ed2608695b61 Mon Sep 17 00:00:00 2001 From: Jake Leventhal Date: Thu, 29 Oct 2026 10:29:15 -0400 Subject: [PATCH 1/4] 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 1782f6f072a1f..dc5c0ff887890 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/index.ts +++ b/apps/server/src/orchestration-v2/testkit/fixtures/index.ts @@ -196,6 +196,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"; @@ -1644,6 +1646,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 300a9359e40dd..ee41e92676a39 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; @@ -2100,6 +2102,7 @@ export const make = Effect.fn("AcpSessionRuntime.make")(function* ( } return activationOptions?.mcpServers ?? options.mcpServers ?? []; }; + const sessionMeta = options.sessionMeta === undefined ? {} : { _meta: options.sessionMeta }; const startOnce = Effect.gen(function* () { const initializeResult = yield* initialize; @@ -2170,6 +2173,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) { @@ -2178,6 +2182,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", @@ -2195,6 +2200,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", @@ -2394,6 +2400,7 @@ export const make = Effect.fn("AcpSessionRuntime.make")(function* ( sessionId, cwd: options.cwd, mcpServers: sessionMcpServers(started.initializeResult, activationOptions), + ...sessionMeta, } satisfies EffectAcpSchema.LoadSessionRequest; return runLoadSessionWithReplayIdle(requestPayload, started.initializeResult); }), @@ -2406,6 +2413,7 @@ export const make = Effect.fn("AcpSessionRuntime.make")(function* ( sessionId, cwd: options.cwd, 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 317f91fd698c75eae66c2733303c8e44418865e5 Mon Sep 17 00:00:00 2001 From: Jake Leventhal Date: Thu, 29 Oct 2026 11:12:19 -0400 Subject: [PATCH 2/4] 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 82385c6177c9c80c0fece2b736b57d8be3b752f7 Mon Sep 17 00:00:00 2001 From: Jake Leventhal Date: Thu, 29 Oct 2026 10:37:25 -0400 Subject: [PATCH 3/4] feat(fx): label a sub-agent's approval with that sub-agent An fx change in review forwards a sub-agent's permission requests to the ACP client on the parent session and names the child in `_meta.fx.subagent.sessionId`. Without a label, the approval looked like the parent's own. When that child maps to a sub-agent T3 is following through fx's hooks, the approval on the parent carries "Sub-agent: " as its reason, which web and mobile already show. The answer still goes back on the parent session. A request without the field, or for a child T3 has not mapped, stays a plain parent approval. Approvals stay on the parent because a runtime request belongs to a run, and child threads have none. The field is provisional until the fx change merges, so one schema in subagents.ts reads it. Co-Authored-By: Claude Opus 5.5 --- .../Adapters/FxAdapterV2.test.ts | 127 ++++++++++++++++++ packages/provider-acp/src/server/adapter.ts | 19 +++ packages/provider-fx/src/server/adapter.ts | 7 +- packages/provider-fx/src/server/subagents.ts | 13 ++ 4 files changed, 165 insertions(+), 1 deletion(-) diff --git a/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts index 844919e40e57c..5445d1f587619 100644 --- a/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts @@ -177,6 +177,51 @@ const subagentPreview = (callId: string, reply: string) => ], }); +/** fx asks, and takes the answer, on the parent session; a sub-agent's request names its child. */ +const writeApproval = (toolCallId: string, subagentSessionId?: string): ReadonlyArray => [ + { + type: "emit_inbound", + label: `request:session/request_permission:${toolCallId}`, + frame: { + kind: "request", + method: "session/request_permission", + params: { + sessionId: SESSION_ID, + toolCall: { + toolCallId: + subagentSessionId === undefined ? toolCallId : `${subagentSessionId}/${toolCallId}`, + name: "write_file", + title: "Write notes.md", + kind: "edit", + status: "pending", + rawInput: { path: "notes.md", content: "notes" }, + }, + options: [ + { optionId: "allow_once", name: "Allow once", kind: "allow_once" }, + { optionId: "allow_always", name: "Allow for this session", kind: "allow_always" }, + { optionId: "reject_once", name: "Reject", kind: "reject_once" }, + ], + ...(subagentSessionId === undefined + ? {} + : { + _meta: { + fx: { subagent: { sessionId: subagentSessionId, workId: "work-1", toolCallId } }, + }, + }), + }, + }, + }, + { + type: "expect_outbound", + label: `response:session/request_permission:${toolCallId}`, + frame: { + kind: "response", + method: "session/request_permission", + result: { outcome: { outcome: "selected", optionId: "allow_once" } }, + }, + }, +]; + const initialize: ReadonlyArray = [ outbound("initialize"), answer("initialize", { @@ -316,6 +361,15 @@ const openFxSession = Effect.fn("openFxSession")(function* (input: { runtimePolicy, }); return yield* session.events.pipe( + // Every approval is answered with Allow once. + Stream.tap((event) => + event.type === "runtime_request.updated" && event.runtimeRequest.status === "pending" + ? session.respondToRuntimeRequest({ + requestId: event.runtimeRequest.id, + decision: "accept", + }) + : Effect.void, + ), Stream.takeUntil((event) => event.type === "turn.terminal"), Stream.runCollect, ); @@ -547,4 +601,77 @@ describe("FxAdapterV2", () => { ); }).pipe(Effect.provide(testLayer), Effect.scoped), ); + // vercel-labs/fx (unmerged): fx forwards a sub-agent's approval on the + // parent session and names the child in `_meta.fx.subagent`. + it.effect("labels a sub-agent's approval with that sub-agent on the parent", () => + Effect.gen(function* () { + const childSessionId = "fx-session-02"; + const events = yield* openFxSession({ + scenario: "subagent-approval", + 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_sub", "Write notes.md for the release"), + hook(SESSION_ID, "delegation", { + callId: "call_sub", + childSessionId, + workId: "work-1", + operationId: null, + phase: "dispatched", + }), + ...writeApproval("call_child_write", childSessionId), + // No child is mapped yet, as without hooks. + ...writeApproval("call_unknown_write", "fx-session-09"), + ...writeApproval("call_parent_write"), + hook(SESSION_ID, "delegation", { + callId: "call_sub", + childSessionId, + workId: "work-1", + operationId: null, + phase: "completed", + }), + subagentPreview("call_sub", "Wrote notes.md."), + hook(SESSION_ID, "tool.end", { + call: { callId: "call_sub", name: "subagent", provenance: "model" }, + output: subagentReply("Wrote notes.md."), + status: "success", + error: null, + }), + answer("session/prompt", { stopReason: "end_turn" }), + ], + }); + + const approvals = new Map( + events.flatMap((event) => + event.type === "turn_item.updated" && event.turnItem.type === "approval_request" + ? [[event.turnItem.nativeItemRef?.nativeId, event.turnItem] as const] + : [], + ), + ); + assert.deepEqual( + [...approvals].map(([nativeId, item]) => [nativeId, item.threadId, item.reason]), + [ + [ + `${childSessionId}/call_child_write`, + ThreadId.make("thread-fx-subagent-approval"), + "Sub-agent: Write notes.md for the release", + ], + [ + "fx-session-09/call_unknown_write", + ThreadId.make("thread-fx-subagent-approval"), + undefined, + ], + ["call_parent_write", ThreadId.make("thread-fx-subagent-approval"), undefined], + ], + ); + }).pipe(Effect.provide(testLayer), Effect.scoped), + ); }); diff --git a/packages/provider-acp/src/server/adapter.ts b/packages/provider-acp/src/server/adapter.ts index 398eac2170e70..445508d3c143f 100644 --- a/packages/provider-acp/src/server/adapter.ts +++ b/packages/provider-acp/src/server/adapter.ts @@ -309,6 +309,10 @@ export interface AcpAdapterV2Flavor { readonly approvalOptions?: ( request: EffectAcpSchema.RequestPermissionRequest, ) => ReadonlyArray; + /** The sub-agent session behind a request the agent sends, and takes answers for, on the parent. */ + readonly permissionSubagentSessionId?: ( + request: EffectAcpSchema.RequestPermissionRequest, + ) => string | undefined; readonly onSessionEvent?: ( event: AcpSessionRuntime.AcpSessionRuntimeEvent, ) => Effect.Effect; @@ -5104,6 +5108,20 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( yield* closeTextStreams(context); const parsed = parsePermissionRequest(params); const approvalOptions = flavor.approvalOptions?.(params); + const subagentSessionId = flavor.permissionSubagentSessionId?.(params); + const subagent = + subagentSessionId === undefined + ? undefined + : context.subagentsBySessionId.get(subagentSessionId); + const reason = + subagent === undefined + ? undefined + : `Sub-agent: ${subagentThreadTitle({ + parentTitle: context.input.appThread.title, + title: subagent.task.title, + prompt: subagent.task.prompt, + ordinal: [...context.subagents.values()].indexOf(subagent) + 1, + })}`; const nativeRequestId = params.toolCall.toolCallId; const requestId = yield* idAllocator.allocate.runtimeRequest({ driver, @@ -5178,6 +5196,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( requestId, requestKind, ...(parsed.detail === undefined ? {} : { prompt: parsed.detail }), + ...(reason === undefined ? {} : { reason }), ...(approvalOptions === undefined ? {} : { options: approvalOptions }), }; yield* Ref.update(pendingRuntimeRequests, (current) => { diff --git a/packages/provider-fx/src/server/adapter.ts b/packages/provider-fx/src/server/adapter.ts index 7cd46dd02aaa2..1dbe364c1702b 100644 --- a/packages/provider-fx/src/server/adapter.ts +++ b/packages/provider-fx/src/server/adapter.ts @@ -38,7 +38,11 @@ import { type AcpAdapterV2Flavor, type AcpAdapterV2RuntimeInput, } from "@t3tools/provider-acp/server/adapter"; -import { extractFxSubagentUpdate, withFxSubagentHooks } from "./subagents.ts"; +import { + extractFxSubagentUpdate, + fxPermissionSubagentSessionId, + withFxSubagentHooks, +} from "./subagents.ts"; export { FX_DRIVER_KIND } from "./acpSupport.ts"; @@ -112,6 +116,7 @@ function makeFxAcpAdapterFlavor(options: FxAdapterV2Options): AcpAdapterV2Flavor ), sessionModeForPolicy: fxSessionModeForPolicy, permissionDisposition: fxPermissionDisposition, + permissionSubagentSessionId: fxPermissionSubagentSessionId, extractSubagentUpdate: extractFxSubagentUpdate, ...(options.assertComplete === undefined ? {} : { assertComplete: options.assertComplete }), }; diff --git a/packages/provider-fx/src/server/subagents.ts b/packages/provider-fx/src/server/subagents.ts index 52c20481c912b..5653549071fe5 100644 --- a/packages/provider-fx/src/server/subagents.ts +++ b/packages/provider-fx/src/server/subagents.ts @@ -186,6 +186,19 @@ export function extractFxSubagentUpdate( }; } +// Provisional: fx names the child behind a forwarded approval here in an +// unmerged PR, so a rename changes this schema alone. +const decodePermissionSubagent = Schema.decodeUnknownOption( + Schema.Struct({ fx: Schema.Struct({ subagent: Schema.Struct({ sessionId: Schema.String }) }) }), +); + +/** The child session behind a sub-agent's approval, which fx asks and takes answers for on the parent. */ +export function fxPermissionSubagentSessionId( + request: EffectAcpSchema.RequestPermissionRequest, +): string | undefined { + return Option.getOrUndefined(decodePermissionSubagent(request._meta))?.fx.subagent.sessionId; +} + /** fx's own `tool_call` kinds (src/acp/tool_call_presentation.zig). */ function fxToolKind(name: string): EffectAcpSchema.ToolKind { switch (name) { From 3df1d72cb2ab79370cddefc188a67d1d5ccd1988 Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Fri, 6 Nov 2026 14:05:33 +0000 Subject: [PATCH 4/4] fix(fx): use main's per-agent sessionMeta for the hooks opt-in --- .../src/orchestration-v2/Adapters/FxAdapterV2.test.ts | 2 +- .../src/orchestration-v2/Adapters/FxAdapterV2.testkit.ts | 2 +- packages/provider-acp/src/server/AcpSessionRuntime.ts | 8 -------- packages/provider-fx/src/server/acpSupport.ts | 2 +- 4 files changed, 3 insertions(+), 11 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts index 4481dc0d50921..4c4d06a629ac1 100644 --- a/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.test.ts @@ -295,7 +295,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 c04851d40e7a5..630e9a20584bc 100644 --- a/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.testkit.ts +++ b/apps/server/src/orchestration-v2/Adapters/FxAdapterV2.testkit.ts @@ -68,7 +68,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 4c48fc3103675..514ac91164153 100644 --- a/packages/provider-acp/src/server/AcpSessionRuntime.ts +++ b/packages/provider-acp/src/server/AcpSessionRuntime.ts @@ -104,8 +104,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; @@ -2150,7 +2148,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); @@ -2229,7 +2226,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) { @@ -2238,7 +2234,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", @@ -2256,7 +2251,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", @@ -2458,7 +2452,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); }), @@ -2472,7 +2465,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 74046b37cf6f9..9c8034eed40e1 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));