diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts index bba467deec417..2611596457fa4 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts @@ -9693,7 +9693,7 @@ describe("CodexAdapterV2 post-settle continuation", () => { ), ); - it.effect("sends a /goal objective at the 4000 character limit unchanged", () => + it.effect("sends an agent /goal objective at the 4000 character limit unchanged", () => Effect.gen(function* () { const atLimit = "y".repeat(4000); const goal = (status: string) => ({ ...codexGoal(status, 0), objective: atLimit }); @@ -9713,7 +9713,7 @@ describe("CodexAdapterV2 post-settle continuation", () => { nativeThreadId, nativeTurnId: "goal-turn-limit", prompt: atLimit, - sentPrompt: `This thread's goal is already set. Do not call create_goal. Start working on it now: ${atLimit}`, + sentPrompt: `[Message from another thread's agent (thread "thread:sender"). This is agent-authored coordination, not a message from the human user.]\n\nThis thread's goal is already set. Do not call create_goal. Start working on it now: ${atLimit}`, }).slice(5), 5, ), @@ -9735,7 +9735,15 @@ describe("CodexAdapterV2 post-settle continuation", () => { ], }); const harness = yield* makeCodexReplayHarness(transcript); - yield* harness.runtime.startTurn(yield* goalTurnInput(harness, `/goal ${atLimit}`)); + const input = yield* goalTurnInput(harness, `/goal ${atLimit}`); + yield* harness.runtime.startTurn({ + ...input, + message: { + ...input.message, + createdBy: "agent", + senderThreadId: ThreadId.make("thread:sender"), + }, + }); yield* harness.firstTerminal; assert.deepEqual( diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts index e9b41ac8a5b05..a34f0b8bd8580 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts @@ -104,6 +104,7 @@ import * as Stream from "effect/Stream"; import { ChildProcess, ChildProcessSpawner } from "effect/process"; import { resolveAttachmentPath, resolveAttachmentPathById } from "../../attachmentStore.ts"; +import { withProviderMessageOrigin } from "../providerMessageOrigin.ts"; import * as PortScanner from "../../preview/PortScanner.ts"; import { getCodexServiceTierOptionValue } from "../../codexModelOptions.ts"; import { ServerConfig } from "../../config.ts"; @@ -7440,7 +7441,11 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( ...turnInput, message: { ...turnInput.message, - text: `This thread's goal is already set. Do not call create_goal. Start working on it now: ${command.objective}`, + text: withProviderMessageOrigin( + turnInput.message, + `This thread's goal is already set. Do not call create_goal. Start working on it now: ${command.objective}`, + CODEX_PROVIDER, + ), }, }) : Effect.succeed([]), diff --git a/apps/server/src/orchestration-v2/ContextHandoffDelivery.ts b/apps/server/src/orchestration-v2/ContextHandoffDelivery.ts index 8919030f4a2c1..2608466340dc2 100644 --- a/apps/server/src/orchestration-v2/ContextHandoffDelivery.ts +++ b/apps/server/src/orchestration-v2/ContextHandoffDelivery.ts @@ -13,8 +13,11 @@ import { } from "@t3tools/provider-core/server/handoffBudget"; /** The provider prompt for a turn that carries inline context ahead of the user's text. */ -export const withInlineContext = (context: string, userText: string): string => - context === "" ? userText : `${context}\n\nUser message:\n${userText}`; +export const withInlineContext = ( + context: string, + userText: string, + messageHeading = "User message:", +): string => (context === "" ? userText : `${context}\n\n${messageHeading}\n${userText}`); /** Persist around injection; ambiguous delivery requires a fresh native thread. Call unsent if refused before prompt consumption. */ export const deliverContextHandoffs = Effect.fn("orchestrationV2.deliverContextHandoffs")( diff --git a/apps/server/src/orchestration-v2/ProjectionControlReads.test.ts b/apps/server/src/orchestration-v2/ProjectionControlReads.test.ts index d63c13699fa26..2a03aed74319a 100644 --- a/apps/server/src/orchestration-v2/ProjectionControlReads.test.ts +++ b/apps/server/src/orchestration-v2/ProjectionControlReads.test.ts @@ -275,6 +275,7 @@ it.effect.each(storageCases)( const layerSessions = Layer.mock(ProviderSessionManager.ProviderSessionManagerV2)({ get: () => Effect.succeedSome({ + driver, interruptTurn: () => Effect.sync(() => { calls.push("interrupt"); @@ -308,6 +309,32 @@ it.effect.each(storageCases)( }); yield* reply.respond({ threadId, providerSessionId, requestId, decision: "accept" }); assert.deepEqual(calls, ["interrupt", "Use the smaller fix.", "reply"]); + const messageEvent = events.find((event) => event.type === "message.updated")!; + yield* store.apply({ + ...messageEvent, + id: EventId.make("control:agent-message"), + payload: { + ...messageEvent.payload, + createdBy: "agent", + creationSource: "mcp", + senderThreadId: ThreadId.make("thread:sender"), + }, + }); + yield* control.steer({ + threadId, + providerThreadId, + providerTurnId, + providerSessionId, + messageId, + }); + const agentText = calls.at(-1); + assert.include(agentText, 'thread "thread:sender"'); + assert.include(agentText, "not a message from the human user"); + assert.isTrue(agentText?.endsWith("\n\nUse the smaller fix.")); + assert.equal( + (yield* store.getProviderControlContext(threadId, target)).message?.text, + "Use the smaller fix.", + ); const wrongSession = ProviderSessionId.make("session:wrong"); assert.equal( (yield* Effect.exit( @@ -331,7 +358,7 @@ it.effect.each(storageCases)( ))._tag, "Failure", ); - assert.lengthOf(calls, 3); + assert.lengthOf(calls, 4); const turn = context.providerTurn!; yield* store.apply({ id: EventId.make("control:turn-completed"), @@ -341,7 +368,13 @@ it.effect.each(storageCases)( payload: { ...turn, status: "completed", completedAt: now }, }); yield* control.interrupt({ threadId, providerThreadId, providerTurnId, providerSessionId }); - assert.deepEqual(calls, ["interrupt", "Use the smaller fix.", "reply", "interrupt"]); + assert.deepEqual(calls, [ + "interrupt", + "Use the smaller fix.", + "reply", + agentText, + "interrupt", + ]); }).pipe( Effect.provide( Layer.merge(ProviderTurnControlService.layer, RuntimeRequestService.layer).pipe( diff --git a/apps/server/src/orchestration-v2/ProviderTurnControlService.ts b/apps/server/src/orchestration-v2/ProviderTurnControlService.ts index 0973be037e39a..d19e9cec73455 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnControlService.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnControlService.ts @@ -20,6 +20,7 @@ import * as Schema from "effect/Schema"; import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter"; import * as ProjectionStore from "./ProjectionStore.ts"; import * as ProviderSessionManager from "./ProviderSessionManager.ts"; +import { withProviderMessageOrigin } from "./providerMessageOrigin.ts"; const yieldToRuntime = Effect.yieldNow.pipe( Effect.andThen( @@ -412,10 +413,14 @@ export const layer: Layer.Layer< : { modelSelection: input.modelSelection }), message: { messageId: message.id, - text: projectComposerContextForProvider({ - text: message.text, - records: message.context?.records ?? [], - }), + text: withProviderMessageOrigin( + message, + projectComposerContextForProvider({ + text: message.text, + records: message.context?.records ?? [], + }), + loaded.session.value.driver, + ), attachments: message.attachments, createdBy: message.createdBy, creationSource: message.creationSource, diff --git a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts index 648e12e5a2aa8..1b84692675e1f 100644 --- a/apps/server/src/orchestration-v2/ProviderTurnStartService.ts +++ b/apps/server/src/orchestration-v2/ProviderTurnStartService.ts @@ -61,6 +61,7 @@ import { makeProviderFailure, } from "@t3tools/provider-core/server/failure"; import * as RunExecutionService from "./RunExecutionService.ts"; +import { isCrossThreadAgentMessage } from "./providerMessageOrigin.ts"; import * as RuntimePolicy from "./RuntimePolicy.ts"; import * as TurnItemPositionStore from "./TurnItemPositionStore.ts"; import { @@ -870,10 +871,7 @@ export const layer: Layer.Layer< records: message.context?.records ?? [], }); // Native goal commands go verbatim; handoff history waits a turn. - const goalDriver = - session.driver === "codex" || - session.driver === "claudeAgent" || - session.providerSession.capabilities.turns.supportsNativeGoals === true; + const goalDriver = RunExecutionService.acceptsNativeGoalCommands(session); const goalCommand = goalDriver && parseGoalCommand(message) !== null; // The provider turn marks cancellation work delivered; restart continuations use their own text or resume natively. const noteContinuation = isRestartNoteContinuation( @@ -1756,11 +1754,16 @@ export const layer: Layer.Layer< .filter((part) => part !== "") .join("\n\n"); const { restartContinuationOfRunId: _resumedRunId, ...promptedInput } = turnInput; + const messageHeading = isCrossThreadAgentMessage(turnInput.message) + ? "Agent message:" + : "User message:"; yield* start({ ...(noteContinuation ? promptedInput : turnInput), message: { ...turnInput.message, - text: goalCommand ? userText : withInlineContext(context, userText), + text: goalCommand + ? turnInput.message.text + : withInlineContext(context, turnInput.message.text, messageHeading), }, }).pipe( // Clear pending handoff markers for refused prompts so the next turn retains the native session. diff --git a/apps/server/src/orchestration-v2/RunExecutionService.test.ts b/apps/server/src/orchestration-v2/RunExecutionService.test.ts index 0e584bcf0c0a5..c44c0571601d5 100644 --- a/apps/server/src/orchestration-v2/RunExecutionService.test.ts +++ b/apps/server/src/orchestration-v2/RunExecutionService.test.ts @@ -1042,87 +1042,129 @@ it.effect("fails the run when its ownership check cannot be read before calling }), ); -it.effect( - "dispatches only attachment-free compact commands through the native compaction path", - () => - Effect.gen(function* () { - const runExecution = yield* RunExecutionService.RunExecutionServiceV2; - const calls: Array = []; - const attachment = ChatFileAttachment.make({ - type: "file", - id: ChatAttachmentId.make("compact-file-12345678-1234-1234-1234-123456789abc"), - name: "notes.txt", - mimeType: "text/plain", - sizeBytes: 1, - }); - const cases = [ - { text: " /compact ", attachments: [], expected: "compact" }, - { text: "/compact", attachments: [attachment], expected: "prompt" }, - { text: "Continue the work", attachments: [], expected: "prompt" }, - ]; - for (const [index, testCase] of cases.entries()) { - const threadId = ThreadId.make(`thread:compact-routing:${index}`); - const attemptId = RunAttemptId.make(`attempt:compact-routing:${index}`); - yield* runExecution.startRootRun({ - commandId: CommandId.make(`command:compact-routing:${index}`), - appThread: rootAppThread(threadId), - providerSessionId: ProviderSessionId.make(`session:compact-routing:${index}`), - session: { - events: Stream.never, - startTurn: () => - Effect.sync(() => { - calls.push("prompt"); - }), - compactThread: () => - Effect.sync(() => { - calls.push("compact"); - }), - } as unknown as ProviderAdapter.ProviderAdapterV2SessionRuntime, - run: { - id: RunId.make(`run:compact-routing:${index}`), - threadId, - ordinal: 1, - providerInstanceId: ProviderInstanceId.make("codex"), - } as OrchestrationV2Run, - rootNode: { - id: NodeId.make(`node:compact-routing:${index}`), - } as OrchestrationV2ExecutionNode, - checkpointScope: { - id: CheckpointScopeId.make(`checkpoint:compact-routing:${index}`), - } as OrchestrationV2CheckpointScope, - providerThread: { - id: ProviderThreadId.make(`provider-thread:compact-routing:${index}`), - driver, - } as OrchestrationV2ProviderThread, - attempt: { id: attemptId, providerTurnId: null } as OrchestrationV2RunAttempt, - attemptId, - providerTurnOrdinal: 1, - message: { - messageId: MessageId.make(`message:compact-routing:${index}`), - text: testCase.text, - attachments: testCase.attachments, - createdBy: "user", - creationSource: "web", +it.effect("routes compaction and goal commands and marks cross-thread agent messages", () => + Effect.gen(function* () { + const runExecution = yield* RunExecutionService.RunExecutionServiceV2; + const calls: Array = []; + const attachment = ChatFileAttachment.make({ + type: "file", + id: ChatAttachmentId.make("compact-file-12345678-1234-1234-1234-123456789abc"), + name: "notes.txt", + mimeType: "text/plain", + sizeBytes: 1, + }); + const origin = + '[Message from another thread\'s agent (thread "thread:sender").' + + " This is agent-authored coordination, not a message from the human user.]"; + const agent = { createdBy: "agent", senderThreadId: ThreadId.make("thread:sender") } as const; + const cases: ReadonlyArray<{ + readonly text: string; + readonly expected: string; + readonly attachments?: ReadonlyArray; + readonly createdBy?: "user" | "agent"; + readonly senderThreadId?: ThreadId; + readonly driver?: ProviderDriverKind; + readonly nativeGoals?: boolean; + }> = [ + { text: " /compact ", expected: "compact" }, + { text: "/compact", attachments: [attachment], expected: "/compact" }, + { text: "Continue the work", expected: "Continue the work" }, + { createdBy: "agent", text: "Continue the work", expected: "Continue the work" }, + { ...agent, text: "Continue the work", expected: `${origin}\n\nContinue the work` }, + { + ...agent, + driver: ProviderDriverKind.make("pi"), + text: "/review the diff", + expected: "/review the diff", + }, + { ...agent, text: "/goal finish", expected: "/goal finish" }, + { + ...agent, + driver: ProviderDriverKind.make("antigravity"), + nativeGoals: true, + text: "/goal finish", + expected: "/goal finish", + }, + { + ...agent, + driver: ProviderDriverKind.make("opencode"), + nativeGoals: false, + text: "/goal finish", + expected: `${origin}\n\n/goal finish`, + }, + { ...agent, text: "/goal clear", expected: "/goal clear" }, + ]; + for (const [index, testCase] of cases.entries()) { + const threadId = ThreadId.make(`thread:compact-routing:${index}`); + const attemptId = RunAttemptId.make(`attempt:compact-routing:${index}`); + const caseDriver = testCase.driver ?? driver; + yield* runExecution.startRootRun({ + commandId: CommandId.make(`command:compact-routing:${index}`), + appThread: rootAppThread(threadId), + providerSessionId: ProviderSessionId.make(`session:compact-routing:${index}`), + session: { + driver: caseDriver, + providerSession: { + capabilities: { turns: { supportsNativeGoals: testCase.nativeGoals } }, }, - modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" }, - runtimePolicy: { - runtimeMode: "full-access", - interactionMode: "default", - cwd: process.cwd(), - approvalPolicy: "never", - sandboxPolicy: { - type: "readOnly", - access: { type: "fullAccess" }, - networkAccess: false, - }, + events: Stream.never, + startTurn: (input: ProviderAdapter.ProviderAdapterV2TurnInput) => + Effect.sync(() => { + calls.push(input.message.text); + }), + compactThread: () => + Effect.sync(() => { + calls.push("compact"); + }), + } as unknown as ProviderAdapter.ProviderAdapterV2SessionRuntime, + run: { + id: RunId.make(`run:compact-routing:${index}`), + threadId, + ordinal: 1, + providerInstanceId: ProviderInstanceId.make("codex"), + } as OrchestrationV2Run, + rootNode: { + id: NodeId.make(`node:compact-routing:${index}`), + } as OrchestrationV2ExecutionNode, + checkpointScope: { + id: CheckpointScopeId.make(`checkpoint:compact-routing:${index}`), + } as OrchestrationV2CheckpointScope, + providerThread: { + id: ProviderThreadId.make(`provider-thread:compact-routing:${index}`), + driver: caseDriver, + } as OrchestrationV2ProviderThread, + attempt: { id: attemptId, providerTurnId: null } as OrchestrationV2RunAttempt, + attemptId, + providerTurnOrdinal: 1, + message: { + messageId: MessageId.make(`message:compact-routing:${index}`), + text: testCase.text, + attachments: testCase.attachments ?? [], + createdBy: testCase.createdBy ?? "user", + creationSource: "web", + ...(testCase.senderThreadId === undefined + ? {} + : { senderThreadId: testCase.senderThreadId }), + }, + modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" }, + runtimePolicy: { + runtimeMode: "full-access", + interactionMode: "default", + cwd: process.cwd(), + approvalPolicy: "never", + sandboxPolicy: { + type: "readOnly", + access: { type: "fullAccess" }, + networkAccess: false, }, - }); - } - assert.deepEqual( - calls, - cases.map((testCase) => testCase.expected), - ); - }).pipe(Effect.provide(layerRunExecutionTest)), + }, + }); + } + assert.deepEqual( + calls, + cases.map((testCase) => testCase.expected), + ); + }).pipe(Effect.provide(layerRunExecutionTest)), ); it.effect("starts the provider when checkpoint baseline capture fails", () => diff --git a/apps/server/src/orchestration-v2/RunExecutionService.ts b/apps/server/src/orchestration-v2/RunExecutionService.ts index 612a130e019aa..f6e1934d19625 100644 --- a/apps/server/src/orchestration-v2/RunExecutionService.ts +++ b/apps/server/src/orchestration-v2/RunExecutionService.ts @@ -1,7 +1,9 @@ import { makeAssistantStreamingFilter } from "./assistantStreaming.ts"; +import { isCrossThreadAgentMessage, withProviderMessageOrigin } from "./providerMessageOrigin.ts"; import { resolveProjectSettings } from "@t3tools/shared/projectSettings"; import { isOrchestrationV2WorkActive, + parseGoalCommand, CommandId, type EventId, type ModelSelection, @@ -188,6 +190,16 @@ function childTurnsContinue(terminal: ProviderTerminalEvent): boolean { } /** Launching runs still ingest running subagents; taking them over would duplicate events. */ +export function acceptsNativeGoalCommands( + session: Pick, +): boolean { + return ( + session.driver === "codex" || + session.driver === "claudeAgent" || + session.providerSession.capabilities.turns.supportsNativeGoals === true + ); +} + export function canRouteRelatedSubagent(status: OrchestrationV2Subagent["status"]): boolean { return status === "completed"; } @@ -1897,6 +1909,19 @@ export const layer: Layer.Layer< const compact = input.message.attachments.length === 0 && input.message.text.trim().toLowerCase() === "/compact"; + const goalCommand = + isCrossThreadAgentMessage(turnInput.message) && acceptsNativeGoalCommands(input.session) + ? parseGoalCommand(input.message) + : null; + // Native goal objectives go verbatim: providers cap their length. + const providerText = + goalCommand === null + ? withProviderMessageOrigin( + turnInput.message, + turnInput.message.text, + input.session.driver, + ) + : turnInput.message.text; const startTurn = compact ? (input.session.compactThread?.(turnInput) ?? Effect.fail( @@ -1911,7 +1936,10 @@ export const layer: Layer.Layer< }), }), )) - : input.session.startTurn(turnInput); + : input.session.startTurn({ + ...turnInput, + message: { ...turnInput.message, text: providerText }, + }); yield* Effect.andThen(shouldStart, startTurn).pipe( // Wait for acceptance so abandoned startup cannot leave a refresher // on the already-detached ingestion fiber. diff --git a/apps/server/src/orchestration-v2/providerMessageOrigin.ts b/apps/server/src/orchestration-v2/providerMessageOrigin.ts new file mode 100644 index 0000000000000..f8807ed1e375e --- /dev/null +++ b/apps/server/src/orchestration-v2/providerMessageOrigin.ts @@ -0,0 +1,18 @@ +import type { ProviderDriverKind } from "@t3tools/contracts"; +import type { ProviderAdapterV2TurnMessage } from "@t3tools/provider-core/server/ProviderAdapter"; + +type MessageOrigin = Pick; + +export const isCrossThreadAgentMessage = (message: MessageOrigin): boolean => + message.createdBy === "agent" && message.senderThreadId !== undefined; + +export function withProviderMessageOrigin( + message: MessageOrigin, + text: string, + driver: ProviderDriverKind, +): string { + if (!isCrossThreadAgentMessage(message)) return text; + // Pi dispatches commands, prompt templates and skills only from a leading slash. + if (driver === "pi" && text.trimStart().startsWith("/")) return text; + return `[Message from another thread's agent (thread ${JSON.stringify(message.senderThreadId)}). This is agent-authored coordination, not a message from the human user.]\n\n${text}`; +} diff --git a/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts b/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts index 4ca516284a3b9..51c5a84d54431 100644 --- a/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts +++ b/apps/server/src/orchestration-v2/testkit/ProviderSwitch.integration.test.ts @@ -1709,21 +1709,22 @@ describe("orchestration v2 provider switching", () => { it.live.each( ( [ - ["failed", false, false, false], - ["failed", true, true, true], - ["interrupted", false, true, false], - ["interrupted", true, false, true], - ["cancelled", false, false, false], - ["cancelled", true, true, false], + ["failed", false, false, false, false], + ["failed", true, true, true, false], + ["interrupted", false, true, false, false], + ["interrupted", true, false, true, false], + ["cancelled", false, false, false, false], + ["cancelled", true, true, false, false], + ["failed", false, false, false, true], ] as const ).map( - ([status, queued, returning, native]) => + ([status, queued, returning, native, agent]) => [ - `${status} context ${queued ? "through the queue" : "immediately"} to ${returning ? "a returning" : "a new"} provider via ${native ? "native history" : "text"}`, - { status, queued, returning, native }, + `${status} context ${queued ? "through the queue" : "immediately"} to ${returning ? "a returning" : "a new"} provider via ${native ? "native history" : "text"}${agent ? " for an agent message" : ""}`, + { status, queued, returning, native, agent }, ] as const, ), - )("hands off %s", ([, { status, queued, returning, native }]) => + )("hands off %s", ([, { status, queued, returning, native, agent }]) => Effect.scoped( Effect.gen(function* () { const cwd = yield* checkpointWorkspace(`handoff-${status}-${queued}-${returning}`); @@ -1797,7 +1798,10 @@ describe("orchestration v2 provider switching", () => { commandId: CommandId.make(`command:handoff:${key}`), threadId, messageId: MessageId.make(`message:handoff:${key}`), - createdBy: "user", + createdBy: key === "target" && agent ? "agent" : "user", + ...(key === "target" && agent + ? { senderThreadId: ThreadId.make("thread:agent-source") } + : {}), creationSource: "web", text, attachments: [], @@ -1864,7 +1868,18 @@ describe("orchestration v2 provider switching", () => { assert.include(deliveredHistory, '"role":"user"'); assert.equal(handoff?.delivery?.status, "injected"); } else { - assert.include(delivered.text, "User message:\nContinue"); + if (agent) { + assert.include( + delivered.text, + "Agent message:\n[Message from another thread's agent", + ); + assert.notInclude( + delivered.text, + "User message:\n[Message from another thread's agent", + ); + } else { + assert.include(delivered.text, "User message:\nContinue"); + } assert.equal(handoff?.delivery?.status, "inline"); } assert.notInclude(deliveredHistory, "Earlier successful request."); diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/delegated_task_status/codex_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/delegated_task_status/codex_transcript.ndjson index 6ba29577937a6..bafed6c5f9852 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/delegated_task_status/codex_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/delegated_task_status/codex_transcript.ndjson @@ -15,7 +15,7 @@ {"type":"emit_inbound","label":"child.thread/start","frame":{"id":5,"result":{"thread":{"id":"019f1000-0000-7000-8000-000000000003","forkedFromId":null,"preview":"","ephemeral":false,"modelProvider":"openai","createdAt":1776739468,"updatedAt":1776739468,"status":{"type":"idle"},"path":"/home/replay-user/.codex/sessions/delegated-task-status-child.jsonl","cwd":"/home/replay-user/t3code","cliVersion":"0.120.0","source":"vscode","agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"turns":[]},"model":"gpt-5.4","modelProvider":"openai","serviceTier":"fast","cwd":"/home/replay-user/t3code","approvalPolicy":"on-request","approvalsReviewer":"user","sandbox":{"type":"workspaceWrite","writableRoots":[],"readOnlyAccess":{"type":"fullAccess"},"networkAccess":false,"excludeTmpdirEnvVar":false,"excludeSlashTmp":false},"reasoningEffort":"xhigh"}}} {"type":"expect_outbound","label":"config/read","frame":{"id":6,"method":"config/read","params":{"cwd":"","includeLayers":false}}} {"type":"emit_inbound","label":"config/read","frame":{"id":6,"result":{"config":{},"origins":{}}}} -{"type":"expect_outbound","label":"child.original.turn/start","frame":{"id":7,"method":"turn/start","params":{"approvalPolicy":"never","cwd":"","input":[{"text":"Inspect the delegated API boundary and return the result.","type":"text"}],"model":"gpt-5.4","sandboxPolicy":{"type":"dangerFullAccess"},"threadId":"019f1000-0000-7000-8000-000000000003","approvalsReviewer":"user","summary":"auto","collaborationMode":{"mode":"default","settings":{"model":"gpt-5.4"}}}}} +{"type":"expect_outbound","label":"child.original.turn/start","frame":{"id":7,"method":"turn/start","params":{"approvalPolicy":"never","cwd":"","input":[{"text":"[Message from another thread's agent (thread \"thread:mcp-orchestrator-parent\"). This is agent-authored coordination, not a message from the human user.]\n\nInspect the delegated API boundary and return the result.","type":"text"}],"model":"gpt-5.4","sandboxPolicy":{"type":"dangerFullAccess"},"threadId":"019f1000-0000-7000-8000-000000000003","approvalsReviewer":"user","summary":"auto","collaborationMode":{"mode":"default","settings":{"model":"gpt-5.4"}}}}} {"type":"emit_inbound","label":"child.thread/started","frame":{"method":"thread/started","params":{"thread":{"id":"019f1000-0000-7000-8000-000000000003","forkedFromId":null,"preview":"","ephemeral":false,"modelProvider":"openai","createdAt":1776739468,"updatedAt":1776739468,"status":{"type":"idle"},"path":"/home/replay-user/.codex/sessions/delegated-task-status-child.jsonl","cwd":"/home/replay-user/t3code","cliVersion":"0.120.0","source":"vscode","agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"turns":[]}}}} {"type":"emit_inbound","label":"child.original.turn/start","frame":{"id":7,"result":{"turn":{"id":"019f1000-0000-7000-8000-000000000004","items":[],"status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} {"type":"emit_inbound","label":"child.original.thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019f1000-0000-7000-8000-000000000003","status":{"type":"active","activeFlags":[]}}}} @@ -27,7 +27,7 @@ {"type":"emit_inbound","label":"child.original.turn/completed","frame":{"method":"turn/completed","params":{"threadId":"019f1000-0000-7000-8000-000000000003","turn":{"id":"019f1000-0000-7000-8000-000000000004","items":[],"status":"completed","error":null,"startedAt":1776739468,"completedAt":1776739469,"durationMs":1000}}}} {"type":"expect_outbound","label":"config/read","frame":{"id":8,"method":"config/read","params":{"cwd":"","includeLayers":false}}} {"type":"emit_inbound","label":"config/read","frame":{"id":8,"result":{"config":{},"origins":{}}}} -{"type":"expect_outbound","label":"child.running.turn/start","frame":{"id":9,"method":"turn/start","params":{"approvalPolicy":"never","cwd":"","input":[{"text":"Remain active until the parent cancels this delegated task.","type":"text"}],"model":"gpt-5.4","sandboxPolicy":{"type":"dangerFullAccess"},"threadId":"019f1000-0000-7000-8000-000000000003","approvalsReviewer":"user","summary":"auto","collaborationMode":{"mode":"default","settings":{"model":"gpt-5.4"}}}}} +{"type":"expect_outbound","label":"child.running.turn/start","frame":{"id":9,"method":"turn/start","params":{"approvalPolicy":"never","cwd":"","input":[{"text":"[Message from another thread's agent (thread \"thread:mcp-orchestrator-parent\"). This is agent-authored coordination, not a message from the human user.]\n\nRemain active until the parent cancels this delegated task.","type":"text"}],"model":"gpt-5.4","sandboxPolicy":{"type":"dangerFullAccess"},"threadId":"019f1000-0000-7000-8000-000000000003","approvalsReviewer":"user","summary":"auto","collaborationMode":{"mode":"default","settings":{"model":"gpt-5.4"}}}}} {"type":"emit_inbound","label":"child.running.turn/start","frame":{"id":9,"result":{"turn":{"id":"019f1000-0000-7000-8000-000000000005","items":[],"status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} {"type":"emit_inbound","label":"child.running.thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019f1000-0000-7000-8000-000000000003","status":{"type":"active","activeFlags":[]}}}} {"type":"emit_inbound","label":"child.running.turn/started","frame":{"method":"turn/started","params":{"threadId":"019f1000-0000-7000-8000-000000000003","turn":{"id":"019f1000-0000-7000-8000-000000000005","items":[],"status":"inProgress","error":null,"startedAt":1776739470,"completedAt":null,"durationMs":null}}}} @@ -37,7 +37,7 @@ {"type":"emit_inbound","label":"child.running.turn/completed","frame":{"method":"turn/completed","params":{"threadId":"019f1000-0000-7000-8000-000000000003","turn":{"id":"019f1000-0000-7000-8000-000000000005","items":[],"status":"interrupted","error":null,"startedAt":1776739470,"completedAt":1776739471,"durationMs":1000}}}} {"type":"expect_outbound","label":"config/read","frame":{"id":11,"method":"config/read","params":{"cwd":"","includeLayers":false}}} {"type":"emit_inbound","label":"config/read","frame":{"id":11,"result":{"config":{},"origins":{}}}} -{"type":"expect_outbound","label":"child.queued.turn/start","frame":{"id":12,"method":"turn/start","params":{"approvalPolicy":"never","cwd":"","input":[{"text":"Complete the queued follow-up and return the final result.","type":"text"}],"model":"gpt-5.4","sandboxPolicy":{"type":"dangerFullAccess"},"threadId":"019f1000-0000-7000-8000-000000000003","approvalsReviewer":"user","summary":"auto","collaborationMode":{"mode":"default","settings":{"model":"gpt-5.4"}}}}} +{"type":"expect_outbound","label":"child.queued.turn/start","frame":{"id":12,"method":"turn/start","params":{"approvalPolicy":"never","cwd":"","input":[{"text":"[Message from another thread's agent (thread \"thread:mcp-orchestrator-parent\"). This is agent-authored coordination, not a message from the human user.]\n\nComplete the queued follow-up and return the final result.","type":"text"}],"model":"gpt-5.4","sandboxPolicy":{"type":"dangerFullAccess"},"threadId":"019f1000-0000-7000-8000-000000000003","approvalsReviewer":"user","summary":"auto","collaborationMode":{"mode":"default","settings":{"model":"gpt-5.4"}}}}} {"type":"emit_inbound","label":"child.queued.turn/start","frame":{"id":12,"result":{"turn":{"id":"019f1000-0000-7000-8000-000000000006","items":[],"status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} {"type":"emit_inbound","label":"child.queued.thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019f1000-0000-7000-8000-000000000003","status":{"type":"active","activeFlags":[]}}}} {"type":"emit_inbound","label":"child.queued.turn/started","frame":{"method":"turn/started","params":{"threadId":"019f1000-0000-7000-8000-000000000003","turn":{"id":"019f1000-0000-7000-8000-000000000006","items":[],"status":"inProgress","error":null,"startedAt":1776739472,"completedAt":null,"durationMs":null}}}}