History

Support active Claude steering and turn replay mapping

- add active steering and interrupt-restart replay fixtures - update Claude adapter/orchestrator turn handling for steering - refresh replay and integration test coverage

Julius Marminge committed 6 months ago

Commit e127dfd1 parent a6042da15 files · +837 −126Browse files

Unified · Split

15 files
  1. apps/server/scripts/record-claude-agent-sdk-replay-fixture.ts +26 −3
  2. apps/server/scripts/record-codex-app-server-replay-fixture.ts +2 −9
  3. apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.testkit.ts +440 −7
  4. apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts +138 −26
  5. apps/server/src/orchestration-v2/Orchestrator.ts +33 −3
  6. apps/server/src/orchestration-v2/testkit/CodexReplayFixtures.integration.test.ts +2 −8
  7. apps/server/src/orchestration-v2/testkit/OrchestratorScenario.ts +36 −0
  8. apps/server/src/orchestration-v2/testkit/fixtures/index.ts +16 −5
  9. apps/server/src/orchestration-v2/testkit/fixtures/message_steering/claude_output.ts +43 −0
  10. apps/server/src/orchestration-v2/testkit/fixtures/message_steering/claude_transcript.ndjson +13 −0
  11. apps/server/src/orchestration-v2/testkit/fixtures/message_steering/codex_output.ts +9 −12
  12. apps/server/src/orchestration-v2/testkit/fixtures/message_steering/codex_transcript.ndjson +44 −42
  13. apps/server/src/orchestration-v2/testkit/fixtures/message_steering/input.ts +2 −3
  14. apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/claude_transcript.ndjson +14 −0
  15. apps/server/src/orchestration-v2/testkit/fixtures/shared.ts +19 −8
apps/server/scripts/record-claude-agent-sdk-replay-fixture.ts+26−3
@@ -14,7 +14,9 @@ import type { RuntimePolicyV2Override } from "../src/orchestration-v2/RuntimePol · 13 unmodified lines
1414 import { makeCheckpointWorkspace } from "../src/orchestration-v2/testkit/ReplayFixtureWorkspace.ts";
1515 import { CLAUDE_MODEL_SELECTION } from "../src/orchestration-v2/testkit/fixtures/shared.ts";
1616 import {
17+ MESSAGE_STEERING_INITIAL_PROMPT,
1718 MULTI_TURN_FIRST_PROMPT,
19+ MESSAGE_STEERING_STEER_PROMPT,
1820 READ_ONLY_NEVER_POLICY,
1921 READ_ONLY_ON_REQUEST_POLICY,
2022 RESTRICTED_GRANULAR_POLICY,
@@ -46,6 +48,18 @@ const CLAUDE_RECORDINGS = { · 25 unmodified lines
4648 queryMode: "restart",
4749 enableTools: true,
4850 },
51+ queued_turn: {
52+ prompts: [MULTI_TURN_FIRST_PROMPT, MULTI_TURN_SECOND_PROMPT],
53+ defaultTranscriptFile: "fixtures/queued_turn/claude_transcript.ndjson",
54+ queryMode: "streaming",
55+ enableTools: true,
56+ },
57+ message_steering: {
58+ prompts: [MESSAGE_STEERING_INITIAL_PROMPT, MESSAGE_STEERING_STEER_PROMPT],
59+ defaultTranscriptFile: "fixtures/message_steering/claude_transcript.ndjson",
60+ queryMode: "active_steering",
61+ enableTools: true,
62+ },
4963 tool_call_read_only: {
5064 prompts: [TOOL_CALL_READ_ONLY_PROMPT],
5165 defaultTranscriptFile: "fixtures/tool_call_read_only/claude_transcript.ndjson",
@@ -88,15 +102,24 @@ function readArgValue(name: string): string | undefined { · 36 unmodified lines
88102 return index >= 0 ? args[index + 1] : undefined;
89103 }
90104
91−function selectedQueryMode(defaultMode: "streaming" | "restart"): "streaming" | "restart" {
105+type ClaudeRecordingQueryMode = "streaming" | "restart" | "active_steering" | "interrupt_restart";
106+
107+function selectedQueryMode(defaultMode: ClaudeRecordingQueryMode): ClaudeRecordingQueryMode {
92108 const raw = readArgValue("--query-mode") ?? process.env.T3_CLAUDE_REPLAY_QUERY_MODE;
93109 if (raw === undefined) {
94110 return defaultMode;
95111 }
96− if (raw === "streaming" || raw === "restart") {
112+ if (
113+ raw === "streaming" ||
114+ raw === "restart" ||
115+ raw === "active_steering" ||
116+ raw === "interrupt_restart"
117+ ) {
97118 return raw;
98119 }
99− throw new Error(`Unsupported Claude replay query mode '${raw}'. Use 'streaming' or 'restart'.`);
120+ throw new Error(
121+ `Unsupported Claude replay query mode '${raw}'. Use 'streaming', 'restart', 'active_steering', or 'interrupt_restart'.`,
122+ );
100123 }
101124
102125 const scenario = readArgValue("--scenario") ?? process.env.T3_CLAUDE_REPLAY_SCENARIO ?? "simple";
apps/server/scripts/record-codex-app-server-replay-fixture.ts+2−9
@@ -8,6 +8,7 @@ import * as CodexClient from "effect-codex-app-server/client"; · 7 unmodified lines
88 import type * as CodexSchema from "effect-codex-app-server/schema";
99
1010 import {
11+ MESSAGE_STEERING_INITIAL_PROMPT,
1112 MESSAGE_STEERING_STEER_PROMPT,
1213 MULTI_TURN_FIRST_PROMPT,
1314 MULTI_TURN_SECOND_PROMPT,
@@ -491,20 +492,12 @@ function scenarios(): ReadonlyArray<ReplayScenario> { · 477 unmodified lines
491492 {
492493 name: "immediate-steer",
493494 description: "Start a turn, then immediately steer the active root turn.",
494− turnDefaults: {
495− approvalPolicy: "never",
496− sandboxPolicy: workspaceWriteSandbox(),
497− },
498495 steps: [
499496 {
500497 type: "steeredTurn",
501498 label: "steered",
502− prompt: TOOL_CALL_WRITE_PROMPT,
499+ prompt: MESSAGE_STEERING_INITIAL_PROMPT,
503500 steer: MESSAGE_STEERING_STEER_PROMPT,
504− turnOverrides: {
505− approvalPolicy: "on-request",
506− sandboxPolicy: readOnlyFullAccessSandbox(),
507− },
508501 },
509502 ],
510503 },
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.testkit.ts+440−7
@@ -1,3 +1,5 @@
1+import path from "node:path";
2+
13 import {
24 query,
35 type CanUseTool,
@@ -376,7 +378,7 @@ function makeReplayQueryRunner(transcript: ClaudeAgentSdkReplayTranscript): Clau · 372 unmodified lines
376378
377379 if (entry.type === "runtime_exit") {
378380 advance();
379− if (entry.status === "success") {
381+ if (entry.status === "success" || entry.status === "cancelled") {
380382 return;
381383 }
382384 fail(
@@ -572,11 +574,14 @@ export async function replayClaudeAgentSdkTranscript(input: { · 189 unmodified lines
572574 );
573575 }
574576
575−function serializeReplayError(error: unknown): unknown {
577+function serializeReplayError(error: unknown, scenario?: string): unknown {
576578 return error instanceof Error
577579 ? {
578580 name: error.name,
579− message: error.message,
581+ message:
582+ scenario === undefined
583+ ? error.message
584+ : sanitizeReplayText({ text: error.message, scenario }),
580585 }
581586 : error;
582587 }
@@ -648,6 +653,13 @@ function sanitizedReplayCwd(scenario: string): string { · 65 unmodified lines
648653 return `/tmp/claude-replay-${scenario}`;
649654 }
650655
656+function sanitizeReplayText(input: { readonly text: string; readonly scenario: string }): string {
657+ const sanitizedCwd = sanitizedReplayCwd(input.scenario);
658+ return [path.resolve(process.cwd(), "../.."), process.cwd()]
659+ .toSorted((left, right) => right.length - left.length)
660+ .reduce((text, localPath) => text.replaceAll(localPath, sanitizedCwd), input.text);
661+}
662+
651663 function sanitizeSdkMessageForReplay(input: {
652664 readonly message: SDKMessage;
653665 readonly scenario: string;
@@ -685,6 +697,14 @@ function sanitizeSdkMessageForReplay(input: { · 31 unmodified lines
685697 },
686698 };
687699 }
700+ if (message.type === "result" && message.subtype !== "success" && message.errors.length > 0) {
701+ return {
702+ ...message,
703+ errors: message.errors.map((error) =>
704+ sanitizeReplayText({ text: error, scenario: input.scenario }),
705+ ),
706+ };
707+ }
688708 return message;
689709 }
690710
@@ -763,6 +783,81 @@ async function recordMessagesUntilTurnResult(input: { · 72 unmodified lines
763783 }
764784 }
765785
786+async function recordMessagesUntilTurnResults(input: {
787+ readonly iterator: AsyncIterator<SDKMessage>;
788+ readonly entries: Array<ProviderReplayEntry>;
789+ readonly scenario: string;
790+ readonly resultCount: number;
791+}): Promise<boolean> {
792+ let seenResults = 0;
793+ while (true) {
794+ const next = await input.iterator.next();
795+ if (next.done === true) {
796+ return false;
797+ }
798+ const replayMessage = sanitizeSdkMessageForReplay({
799+ message: next.value,
800+ scenario: input.scenario,
801+ });
802+ input.entries.push({
803+ type: "emit_inbound",
804+ label: replayMessage.type,
805+ frame: replayMessage,
806+ });
807+ if (replayMessage.type === "result") {
808+ seenResults += 1;
809+ if (seenResults >= input.resultCount) {
810+ return true;
811+ }
812+ }
813+ }
814+}
815+
816+async function recordMessagesUntilIteratorDone(input: {
817+ readonly iterator: AsyncIterator<SDKMessage>;
818+ readonly entries: Array<ProviderReplayEntry>;
819+ readonly scenario: string;
820+}): Promise<void> {
821+ while (true) {
822+ const next = await input.iterator.next();
823+ if (next.done === true) {
824+ return;
825+ }
826+ const replayMessage = sanitizeSdkMessageForReplay({
827+ message: next.value,
828+ scenario: input.scenario,
829+ });
830+ input.entries.push({
831+ type: "emit_inbound",
832+ label: replayMessage.type,
833+ frame: replayMessage,
834+ });
835+ }
836+}
837+
838+async function recordFirstInboundMessage(input: {
839+ readonly iterator: AsyncIterator<SDKMessage>;
840+ readonly entries: Array<ProviderReplayEntry>;
841+ readonly scenario: string;
842+}): Promise<void> {
843+ const next = await input.iterator.next();
844+ if (next.done === true) {
845+ throw new Error(`Claude query ended before ${input.scenario} could be interrupted.`);
846+ }
847+ const replayMessage = sanitizeSdkMessageForReplay({
848+ message: next.value,
849+ scenario: input.scenario,
850+ });
851+ input.entries.push({
852+ type: "emit_inbound",
853+ label: replayMessage.type,
854+ frame: replayMessage,
855+ });
856+ if (replayMessage.type === "result") {
857+ throw new Error(`Claude query completed before ${input.scenario} could be interrupted.`);
858+ }
859+}
860+
766861 async function recordClaudeStreamingQuery(input: {
767862 readonly scenario: string;
768863 readonly prompts: ReadonlyArray<string>;
@@ -869,7 +964,142 @@ async function recordClaudeStreamingQuery(input: { · 100 unmodified lines
869964 input.entries.push({
870965 type: "runtime_exit",
871966 status: "error",
872− error: serializeReplayError(error),
967+ error: serializeReplayError(error, input.scenario),
968+ });
969+ throw error;
970+ }
971+}
972+
973+async function recordClaudeActiveSteeringQuery(input: {
974+ readonly scenario: string;
975+ readonly prompts: ReadonlyArray<string>;
976+ readonly modelSelection: ModelSelection;
977+ readonly cwd: string;
978+ readonly sessionId: string;
979+ readonly entries: Array<ProviderReplayEntry>;
980+ readonly enableTools?: boolean;
981+ readonly tools?: ClaudeAgentSdkQueryTools;
982+ readonly permissionMode?: ClaudeAgentSdkQueryOptions["permissionMode"];
983+ readonly allowedTools?: ReadonlyArray<string>;
984+ readonly disallowedTools?: ReadonlyArray<string>;
985+ readonly allowDangerouslySkipPermissions?: boolean;
986+ readonly enablePermissionCallback?: boolean;
987+ readonly permissionDecision?: ProviderApprovalDecision;
988+}): Promise<void> {
989+ if (input.prompts.length < 2) {
990+ throw new Error("Claude active steering replay recording requires at least two prompts.");
991+ }
992+
993+ const promptQueue = new RecordingPromptQueue();
994+ const offeredPrompts = new Set<number>();
995+ const offerPrompt = (index: number, priority?: SDKUserMessage["priority"]) => {
996+ const message = makeClaudeUserMessage({
997+ text: input.prompts[index]!,
998+ ...(priority === undefined ? {} : { priority }),
999+ });
1000+ input.entries.push({
1001+ type: "expect_outbound",
1002+ label: `prompt.offer:${index + 1}`,
1003+ frame: makeClaudePromptOfferFrame(message),
1004+ });
1005+ promptQueue.offer(message);
1006+ offeredPrompts.add(index);
1007+ };
1008+ const offerSteeringPrompts = () => {
1009+ for (let index = 1; index < input.prompts.length; index += 1) {
1010+ if (!offeredPrompts.has(index)) {
1011+ offerPrompt(index, "now");
1012+ }
1013+ }
1014+ };
1015+
1016+ const canUseTool: CanUseTool | undefined =
1017+ input.enablePermissionCallback === true
1018+ ? async (toolName, toolInput, callbackOptions) => {
1019+ const requestFrame = permissionRequestFrame({
1020+ toolName,
1021+ toolInput,
1022+ callbackOptions,
1023+ });
1024+ input.entries.push({
1025+ type: "emit_inbound",
1026+ label: `permission.request:${toolName}`,
1027+ frame: requestFrame,
1028+ });
1029+ const result = permissionResultForRecording({
1030+ decision: input.permissionDecision ?? "accept",
1031+ toolInput,
1032+ toolUseID: callbackOptions.toolUseID,
1033+ ...(callbackOptions.suggestions === undefined
1034+ ? {}
1035+ : { suggestions: callbackOptions.suggestions }),
1036+ });
1037+ input.entries.push({
1038+ type: "expect_outbound",
1039+ label: `permission.response:${toolName}`,
1040+ frame: makeClaudePermissionResponseFrame(result),
1041+ });
1042+ return result;
1043+ }
1044+ : undefined;
1045+ const options = makeClaudeQueryOptions({
1046+ modelSelection: input.modelSelection,
1047+ nativeThreadId: input.sessionId,
1048+ resume: false,
1049+ cwd: input.cwd,
1050+ ...(input.enableTools === true
1051+ ? {
1052+ tools: input.tools ?? { type: "preset", preset: "claude_code" },
1053+ permissionMode: input.permissionMode ?? "default",
1054+ ...(input.allowedTools === undefined ? {} : { allowedTools: input.allowedTools }),
1055+ ...(input.disallowedTools === undefined
1056+ ? {}
1057+ : { disallowedTools: input.disallowedTools }),
1058+ ...(input.allowDangerouslySkipPermissions === true
1059+ ? { allowDangerouslySkipPermissions: true }
1060+ : {}),
1061+ ...(canUseTool === undefined ? {} : { canUseTool }),
1062+ }
1063+ : {}),
1064+ });
1065+ input.entries.push({
1066+ type: "expect_outbound",
1067+ label: "query.open",
1068+ frame: makeClaudeQueryOpenFrame({ options }),
1069+ });
1070+ const queryRuntime = query({
1071+ prompt: promptQueue,
1072+ options,
1073+ });
1074+ const iterator = queryRuntime[Symbol.asyncIterator]();
1075+ try {
1076+ offerPrompt(0);
1077+ offerSteeringPrompts();
1078+ const completed = await recordMessagesUntilTurnResults({
1079+ iterator,
1080+ entries: input.entries,
1081+ scenario: input.scenario,
1082+ resultCount: input.prompts.length,
1083+ });
1084+ if (!completed) {
1085+ throw new Error("Claude active steering query ended before the turn completed.");
1086+ }
1087+ if (offeredPrompts.size < input.prompts.length) {
1088+ throw new Error("Claude active steering prompts were not all offered before completion.");
1089+ }
1090+ promptQueue.close();
1091+ queryRuntime.close();
1092+ input.entries.push({
1093+ type: "runtime_exit",
1094+ status: "success",
1095+ });
1096+ } catch (error) {
1097+ promptQueue.close();
1098+ queryRuntime.close();
1099+ input.entries.push({
1100+ type: "runtime_exit",
1101+ status: "error",
1102+ error: serializeReplayError(error, input.scenario),
8731103 });
8741104 throw error;
8751105 }
@@ -955,20 +1185,183 @@ async function recordClaudeRestartingQueries(input: { · 79 unmodified lines
9551185 input.entries.push({
9561186 type: "runtime_exit",
9571187 status: "error",
958− error: serializeReplayError(error),
1188+ error: serializeReplayError(error, input.scenario),
9591189 });
9601190 throw error;
9611191 }
9621192 }
9631193 }
9641194
1195+async function recordClaudeInterruptRestartQuery(input: {
1196+ readonly scenario: string;
1197+ readonly prompts: ReadonlyArray<string>;
1198+ readonly modelSelection: ModelSelection;
1199+ readonly cwd: string;
1200+ readonly sessionId: string;
1201+ readonly entries: Array<ProviderReplayEntry>;
1202+ readonly enableTools?: boolean;
1203+ readonly tools?: ClaudeAgentSdkQueryTools;
1204+ readonly permissionMode?: ClaudeAgentSdkQueryOptions["permissionMode"];
1205+ readonly allowedTools?: ReadonlyArray<string>;
1206+ readonly disallowedTools?: ReadonlyArray<string>;
1207+ readonly allowDangerouslySkipPermissions?: boolean;
1208+}): Promise<void> {
1209+ if (input.prompts.length !== 2) {
1210+ throw new Error(
1211+ `Claude interrupt-restart replay scenario ${input.scenario} requires exactly two prompts.`,
1212+ );
1213+ }
1214+
1215+ const firstPromptQueue = new RecordingPromptQueue();
1216+ const firstOptions = makeClaudeQueryOptions({
1217+ modelSelection: input.modelSelection,
1218+ nativeThreadId: input.sessionId,
1219+ resume: false,
1220+ cwd: input.cwd,
1221+ ...(input.enableTools === true
1222+ ? {
1223+ tools: input.tools ?? { type: "preset", preset: "claude_code" },
1224+ permissionMode: input.permissionMode ?? "default",
1225+ ...(input.allowedTools === undefined ? {} : { allowedTools: input.allowedTools }),
1226+ ...(input.disallowedTools === undefined
1227+ ? {}
1228+ : { disallowedTools: input.disallowedTools }),
1229+ ...(input.allowDangerouslySkipPermissions === true
1230+ ? { allowDangerouslySkipPermissions: true }
1231+ : {}),
1232+ }
1233+ : {}),
1234+ });
1235+ input.entries.push({
1236+ type: "expect_outbound",
1237+ label: "query.open:1",
1238+ frame: makeClaudeQueryOpenFrame({ options: firstOptions }),
1239+ });
1240+ const firstRuntime = query({
1241+ prompt: firstPromptQueue,
1242+ options: firstOptions,
1243+ });
1244+ const firstIterator = firstRuntime[Symbol.asyncIterator]();
1245+ const firstMessage = makeClaudeUserMessage({ text: input.prompts[0]! });
1246+ input.entries.push({
1247+ type: "expect_outbound",
1248+ label: "prompt.offer:1",
1249+ frame: makeClaudePromptOfferFrame(firstMessage),
1250+ });
1251+ firstPromptQueue.offer(firstMessage);
1252+ await recordFirstInboundMessage({
1253+ iterator: firstIterator,
1254+ entries: input.entries,
1255+ scenario: input.scenario,
1256+ });
1257+ input.entries.push({
1258+ type: "expect_outbound",
1259+ label: "query.interrupt:1",
1260+ frame: { type: "query.interrupt" },
1261+ });
1262+ try {
1263+ let cancelledError: unknown;
1264+ try {
1265+ await firstRuntime.interrupt();
1266+ } catch (error) {
1267+ cancelledError = error;
1268+ }
1269+ firstPromptQueue.close();
1270+ try {
1271+ await recordMessagesUntilIteratorDone({
1272+ iterator: firstIterator,
1273+ entries: input.entries,
1274+ scenario: input.scenario,
1275+ });
1276+ } catch (error) {
1277+ cancelledError = error;
1278+ }
1279+ firstRuntime.close();
1280+ input.entries.push({
1281+ type: "runtime_exit",
1282+ status: cancelledError === undefined ? "success" : "cancelled",
1283+ ...(cancelledError === undefined
1284+ ? {}
1285+ : { error: serializeReplayError(cancelledError, input.scenario) }),
1286+ });
1287+ } catch (error) {
1288+ firstPromptQueue.close();
1289+ input.entries.push({
1290+ type: "runtime_exit",
1291+ status: "error",
1292+ error: serializeReplayError(error, input.scenario),
1293+ });
1294+ throw error;
1295+ }
1296+
1297+ const secondPromptQueue = new RecordingPromptQueue();
1298+ const secondOptions = makeClaudeQueryOptions({
1299+ modelSelection: input.modelSelection,
1300+ nativeThreadId: input.sessionId,
1301+ resume: true,
1302+ cwd: input.cwd,
1303+ ...(input.enableTools === true
1304+ ? {
1305+ tools: input.tools ?? { type: "preset", preset: "claude_code" },
1306+ permissionMode: input.permissionMode ?? "default",
1307+ ...(input.allowedTools === undefined ? {} : { allowedTools: input.allowedTools }),
1308+ ...(input.disallowedTools === undefined
1309+ ? {}
1310+ : { disallowedTools: input.disallowedTools }),
1311+ ...(input.allowDangerouslySkipPermissions === true
1312+ ? { allowDangerouslySkipPermissions: true }
1313+ : {}),
1314+ }
1315+ : {}),
1316+ });
1317+ input.entries.push({
1318+ type: "expect_outbound",
1319+ label: "query.open:2",
1320+ frame: makeClaudeQueryOpenFrame({ options: secondOptions }),
1321+ });
1322+ const secondMessage = makeClaudeUserMessage({ text: input.prompts[1]! });
1323+ input.entries.push({
1324+ type: "expect_outbound",
1325+ label: "prompt.offer:2",
1326+ frame: makeClaudePromptOfferFrame(secondMessage),
1327+ });
1328+
1329+ try {
1330+ const secondRuntime = query({
1331+ prompt: secondPromptQueue,
1332+ options: secondOptions,
1333+ });
1334+ secondPromptQueue.offer(secondMessage);
1335+ secondPromptQueue.close();
1336+ const secondIterator = secondRuntime[Symbol.asyncIterator]();
1337+ await recordMessagesUntilIteratorDone({
1338+ iterator: secondIterator,
1339+ entries: input.entries,
1340+ scenario: input.scenario,
1341+ });
1342+ secondRuntime.close();
1343+ input.entries.push({
1344+ type: "runtime_exit",
1345+ status: "success",
1346+ });
1347+ } catch (error) {
1348+ secondPromptQueue.close();
1349+ input.entries.push({
1350+ type: "runtime_exit",
1351+ status: "error",
1352+ error: serializeReplayError(error, input.scenario),
1353+ });
1354+ throw error;
1355+ }
1356+}
1357+
9651358 export async function recordClaudeAgentSdkReplayTranscript(input: {
9661359 readonly scenario: string;
9671360 readonly prompts: ReadonlyArray<string>;
9681361 readonly modelSelection: ModelSelection;
9691362 readonly cwd: string;
9701363 readonly sessionId?: string;
971− readonly queryMode?: "streaming" | "restart";
1364+ readonly queryMode?: "streaming" | "restart" | "active_steering" | "interrupt_restart";
9721365 readonly enableTools?: boolean;
9731366 readonly tools?: ClaudeAgentSdkQueryTools;
9741367 readonly permissionMode?: ClaudeAgentSdkQueryOptions["permissionMode"];
@@ -1010,7 +1403,30 @@ export async function recordClaudeAgentSdkReplayTranscript(input: { · 35 unmodified lines
10101403 ? {}
10111404 : { permissionDecision: input.permissionDecision }),
10121405 });
1013− } else {
1406+ } else if (queryMode === "active_steering") {
1407+ await recordClaudeActiveSteeringQuery({
1408+ scenario: input.scenario,
1409+ prompts: input.prompts,
1410+ modelSelection: input.modelSelection,
1411+ cwd: input.cwd,
1412+ sessionId,
1413+ entries,
1414+ ...(input.enableTools === undefined ? {} : { enableTools: input.enableTools }),
1415+ ...(input.tools === undefined ? {} : { tools: input.tools }),
1416+ ...(input.permissionMode === undefined ? {} : { permissionMode: input.permissionMode }),
1417+ ...(input.allowedTools === undefined ? {} : { allowedTools: input.allowedTools }),
1418+ ...(input.disallowedTools === undefined ? {} : { disallowedTools: input.disallowedTools }),
1419+ ...(input.allowDangerouslySkipPermissions === undefined
1420+ ? {}
1421+ : { allowDangerouslySkipPermissions: input.allowDangerouslySkipPermissions }),
1422+ ...(input.enablePermissionCallback === undefined
1423+ ? {}
1424+ : { enablePermissionCallback: input.enablePermissionCallback }),
1425+ ...(input.permissionDecision === undefined
1426+ ? {}
1427+ : { permissionDecision: input.permissionDecision }),
1428+ });
1429+ } else if (queryMode === "restart") {
10141430 await recordClaudeRestartingQueries({
10151431 scenario: input.scenario,
10161432 prompts: input.prompts,
@@ -1027,6 +1443,23 @@ export async function recordClaudeAgentSdkReplayTranscript(input: { · 10 unmodified lines
10271443 ? {}
10281444 : { allowDangerouslySkipPermissions: input.allowDangerouslySkipPermissions }),
10291445 });
1446+ } else {
1447+ await recordClaudeInterruptRestartQuery({
1448+ scenario: input.scenario,
1449+ prompts: input.prompts,
1450+ modelSelection: input.modelSelection,
1451+ cwd: input.cwd,
1452+ sessionId,
1453+ entries,
1454+ ...(input.enableTools === undefined ? {} : { enableTools: input.enableTools }),
1455+ ...(input.tools === undefined ? {} : { tools: input.tools }),
1456+ ...(input.permissionMode === undefined ? {} : { permissionMode: input.permissionMode }),
1457+ ...(input.allowedTools === undefined ? {} : { allowedTools: input.allowedTools }),
1458+ ...(input.disallowedTools === undefined ? {} : { disallowedTools: input.disallowedTools }),
1459+ ...(input.allowDangerouslySkipPermissions === undefined
1460+ ? {}
1461+ : { allowDangerouslySkipPermissions: input.allowDangerouslySkipPermissions }),
1462+ });
10301463 }
10311464
10321465 return {
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts+138−26
@@ -37,6 +37,7 @@ import * as Deferred from "effect/Deferred"; · 36 unmodified lines
3737 import * as Effect from "effect/Effect";
3838 import * as Exit from "effect/Exit";
3939 import * as Layer from "effect/Layer";
40+import * as Option from "effect/Option";
4041 import * as Path from "effect/Path";
4142 import * as Queue from "effect/Queue";
4243 import * as Random from "effect/Random";
@@ -55,7 +56,7 @@ import { · 12 unmodified lines
5556 ProviderAdapterResumeThreadError,
5657 ProviderAdapterRollbackThreadError,
5758 ProviderAdapterRuntimeRequestResponseError,
58− ProviderAdapterSteerRunUnsupportedError,
59+ ProviderAdapterSteerRunError,
5960 ProviderAdapterTurnStartError,
6061 ProviderAdapterV2,
6162 type ProviderAdapterV2EnsureThreadInput,
@@ -65,6 +66,7 @@ import { · 3 unmodified lines
6566 type ProviderAdapterV2RuntimePolicy,
6667 type ProviderAdapterV2Shape,
6768 type ProviderAdapterV2SessionRuntime,
69+ type ProviderAdapterV2SteerInput,
6870 type ProviderAdapterV2TurnInput,
6971 } from "../ProviderAdapter.ts";
7072 import {
@@ -97,8 +99,8 @@ export const ClaudeProviderCapabilitiesV2 = { · 26 unmodified lines
9799 emitsTurnStarted: true,
98100 emitsTurnCompleted: true,
99101 supportsInterrupt: true,
100− supportsActiveSteering: false,
101− supportsSteeringByInterruptRestart: true,
102+ supportsActiveSteering: true,
103+ supportsSteeringByInterruptRestart: false,
102104 supportsQueuedMessages: true,
103105 terminalStatusQuality: "strong",
104106 },
@@ -444,11 +446,6 @@ const getNativeThreadId = Effect.fnUntraced(function* ( · 339 unmodified lines
444446 return nativeThreadId;
445447 });
446448
447−function shouldResumeClaudeThread(turnInput: ProviderAdapterV2TurnInput): boolean {
448− const firstRunOrdinal = turnInput.providerThread.firstRunOrdinal;
449− return firstRunOrdinal !== null && firstRunOrdinal < turnInput.runOrdinal;
450−}
451−
452449 export function makeClaudeUserMessage(input: {
453450 readonly text: string;
454451 readonly priority?: SDKUserMessage["priority"];
@@ -1067,6 +1064,10 @@ function terminalStatusFromResult( · 612 unmodified lines
10671064 return "failed";
10681065 }
10691066
1067+function isClaudeActiveSteeringAbortResult(message: SDKResultMessage): boolean {
1068+ return message.terminal_reason === "aborted_streaming";
1069+}
1070+
10701071 function buildAssistantArtifacts(input: {
10711072 readonly idAllocator: IdAllocatorV2Shape;
10721073 readonly turnInput: ProviderAdapterV2TurnInput;
@@ -1156,6 +1157,7 @@ interface ActiveClaudeTurnContext { · 83 unmodified lines
11561157 readonly input: ProviderAdapterV2TurnInput;
11571158 readonly nativeTurnId: string;
11581159 readonly providerTurnId: OrchestrationV2ProviderTurn["id"];
1160+ readonly providerTurnOrdinal: number;
11591161 readonly startedAt: DateTime.Utc;
11601162 readonly assistant: {
11611163 text: string;
@@ -1168,6 +1170,7 @@ interface ClaudeLiveQueryContext { · 6 unmodified lines
11681170 readonly nativeThreadId: string;
11691171 readonly query: ClaudeAgentSdkQuerySession;
11701172 readonly queryPolicyKey: string;
1173+ readonly closed: Deferred.Deferred<void, never>;
11711174 currentModel: string;
11721175 }
11731176
@@ -1215,7 +1218,10 @@ export function makeClaudeAdapterV2( · 41 unmodified lines
12151218 const events = yield* Queue.unbounded<ProviderAdapterV2Event>();
12161219 const activeTurn = yield* Ref.make<ActiveClaudeTurnContext | null>(null);
12171220 const interruptedTurns = yield* Ref.make(new Set<OrchestrationV2ProviderTurn["id"]>());
1221+ const steeredTurns = yield* Ref.make(new Set<OrchestrationV2ProviderTurn["id"]>());
12181222 const queryContext = yield* Ref.make<ClaudeLiveQueryContext | null>(null);
1223+ const openedNativeThreads = yield* Ref.make(new Set<string>());
1224+ const nextProviderTurnOrdinals = yield* Ref.make(new Map<string, number>());
12191225 const itemOrdinals = yield* Ref.make(new Map<string, number>());
12201226 const nextItemOrdinalsByTurn = yield* Ref.make(new Map<string, number>());
12211227 const pendingRuntimeRequests = yield* Ref.make(
@@ -1266,7 +1272,7 @@ export function makeClaudeAdapterV2( · 44 unmodified lines
12661272 nativeId: input.context.nativeTurnId,
12671273 strength: "weak",
12681274 },
1269− ordinal: input.context.input.runOrdinal,
1275+ ordinal: input.context.providerTurnOrdinal,
12701276 status: input.status,
12711277 startedAt: input.context.startedAt,
12721278 completedAt: input.completedAt,
@@ -1651,7 +1657,16 @@ export function makeClaudeAdapterV2( · 378 unmodified lines
16511657 }
16521658 });
16531659
1654− const handleSdkMessage = Effect.fnUntraced(function* (message: SDKMessage) {
1660+ const handleSdkMessage = Effect.fnUntraced(function* (input: {
1661+ readonly query: ClaudeAgentSdkQuerySession;
1662+ readonly message: SDKMessage;
1663+ }) {
1664+ const liveQuery = yield* Ref.get(queryContext);
1665+ if (liveQuery?.query !== input.query) {
1666+ return;
1667+ }
1668+
1669+ const message = input.message;
16551670 const context = yield* Ref.get(activeTurn);
16561671 if (context === null) {
16571672 return;
@@ -1710,9 +1725,19 @@ export function makeClaudeAdapterV2( · 52 unmodified lines
17101725
17111726 if (message.type === "result") {
17121727 const completedAt = yield* DateTime.now;
1728+ const interrupted = (yield* Ref.get(interruptedTurns)).has(context.providerTurnId);
1729+ const wasSteered = (yield* Ref.get(steeredTurns)).has(context.providerTurnId);
1730+ if (!interrupted && wasSteered && isClaudeActiveSteeringAbortResult(message)) {
1731+ return;
1732+ }
1733+ yield* Ref.update(steeredTurns, (current) => {
1734+ const next = new Set(current);
1735+ next.delete(context.providerTurnId);
1736+ return next;
1737+ });
17131738 yield* finalizeActiveTurn({
17141739 context,
1715− status: terminalStatusFromResult(message),
1740+ status: interrupted ? "interrupted" : terminalStatusFromResult(message),
17161741 completedAt,
17171742 });
17181743 }
@@ -1836,7 +1861,15 @@ export function makeClaudeAdapterV2( · 117 unmodified lines
18361861 yield* existing.query.close.pipe(Effect.ignore);
18371862 }
18381863
1839− const openedWithResume = shouldResumeClaudeThread(turnInput);
1864+ const openedWithResume = yield* Ref.modify(openedNativeThreads, (current) => {
1865+ const hasOpenedThread = current.has(nativeThreadId);
1866+ if (hasOpenedThread) {
1867+ return [true, current];
1868+ }
1869+ const updated = new Set(current);
1870+ updated.add(nativeThreadId);
1871+ return [false, updated];
1872+ });
18401873 const querySession = yield* queryRunner.open({
18411874 options: makeClaudeQueryOptions({
18421875 modelSelection: turnInput.modelSelection,
@@ -1856,26 +1889,31 @@ export function makeClaudeAdapterV2( · 13 unmodified lines
18561889 ...(shouldInstallClaudePermissionCallback(queryPolicy) ? { canUseTool } : {}),
18571890 }),
18581891 });
1892+ const closed = yield* Deferred.make<void, never>();
18591893 const context: ClaudeLiveQueryContext = {
18601894 nativeThreadId,
18611895 query: querySession,
18621896 queryPolicyKey,
1897+ closed,
18631898 currentModel: turnInput.modelSelection.model,
18641899 };
18651900 yield* Ref.set(queryContext, context);
18661901 yield* querySession.messages.pipe(
1867− Stream.runForEach(handleSdkMessage),
1902+ Stream.runForEach((message) => handleSdkMessage({ query: querySession, message })),
18681903 Effect.exit,
18691904 Effect.flatMap(
18701905 Effect.fnUntraced(function* (exit: ClaudeQueryStreamExit) {
1871− yield* Ref.update(queryContext, (current) =>
1872− current?.query === querySession ? null : current,
1873− );
1874− yield* finalizeActiveTurnAfterQueryExit(
1875− exit._tag === "Failure" ? exit.cause : undefined,
1906+ const ownsLiveQuery = yield* Ref.modify(queryContext, (current) =>
1907+ current?.query === querySession ? [true, null] : [false, current],
18761908 );
1909+ if (ownsLiveQuery) {
1910+ yield* finalizeActiveTurnAfterQueryExit(
1911+ exit._tag === "Failure" ? exit.cause : undefined,
1912+ );
1913+ }
18771914 }),
18781915 ),
1916+ Effect.ensuring(Deferred.succeed(closed, undefined)),
18791917 Effect.forkIn(sessionScope),
18801918 );
18811919 return context;
@@ -1885,11 +1923,21 @@ export function makeClaudeAdapterV2( · 3 unmodified lines
18851923 function* (turnInput: ProviderAdapterV2TurnInput) {
18861924 const startedAt = yield* DateTime.now;
18871925 const nativeThreadId = yield* getNativeThreadId(turnInput.providerThread);
1888− const nativeTurnId = `turn:${turnInput.runId}`;
1926+ const nativeTurnId = `turn:${turnInput.attemptId}`;
18891927 const providerTurnId = idAllocator.derive.providerTurn({
18901928 provider: CLAUDE_PROVIDER,
18911929 nativeTurnId,
18921930 });
1931+ const providerTurnOrdinal = yield* Ref.modify(nextProviderTurnOrdinals, (current) => {
1932+ const previous = current.get(String(turnInput.providerThread.id));
1933+ const next =
1934+ previous === undefined
1935+ ? turnInput.runOrdinal
1936+ : Math.max(previous + 1, turnInput.runOrdinal);
1937+ const updated = new Map(current);
1938+ updated.set(String(turnInput.providerThread.id), next);
1939+ return [next, updated];
1940+ });
18931941 const currentTurn = yield* Ref.get(activeTurn);
18941942 if (currentTurn !== null) {
18951943 return yield* new ProviderAdapterProtocolError({
@@ -1901,6 +1949,7 @@ export function makeClaudeAdapterV2( · 5 unmodified lines
19011949 input: turnInput,
19021950 nativeTurnId,
19031951 providerTurnId,
1952+ providerTurnOrdinal,
19041953 startedAt,
19051954 assistant: {
19061955 text: "",
@@ -1947,12 +1996,42 @@ export function makeClaudeAdapterV2( · 40 unmodified lines
19471996 detail: `Claude provider thread ${turnInput.providerThread.id} has no live query.`,
19481997 });
19491998 }
1999+ const currentTurn = yield* Ref.get(activeTurn);
2000+ if (currentTurn?.providerTurnId !== turnInput.providerTurnId) {
2001+ return yield* new ProviderAdapterProtocolError({
2002+ provider: CLAUDE_PROVIDER,
2003+ detail: `Claude provider turn ${turnInput.providerTurnId} is not the active turn.`,
2004+ });
2005+ }
19502006 yield* Ref.update(interruptedTurns, (current) => {
19512007 const next = new Set(current);
19522008 next.add(turnInput.providerTurnId);
19532009 return next;
19542010 });
19552011 yield* existing.query.interrupt;
2012+ yield* existing.query.close.pipe(Effect.ignore);
2013+ const closed = yield* Deferred.await(existing.closed).pipe(
2014+ Effect.timeoutOption("10 seconds"),
2015+ );
2016+ if (Option.isSome(closed)) {
2017+ return;
2018+ }
2019+
2020+ const completedAt = yield* DateTime.now;
2021+ yield* Effect.logWarning("orchestration-v2.claude-query-interrupt-timeout", {
2022+ providerSessionId: input.providerSessionId,
2023+ providerThreadId: turnInput.providerThread.id,
2024+ providerTurnId: turnInput.providerTurnId,
2025+ });
2026+ yield* Ref.update(queryContext, (current) =>
2027+ current?.query === existing.query ? null : current,
2028+ );
2029+ yield* finalizeActiveTurn({
2030+ context: currentTurn,
2031+ status: "interrupted",
2032+ completedAt,
2033+ });
2034+ yield* Deferred.succeed(existing.closed, undefined);
19562035 },
19572036 (effect, turnInput) =>
19582037 effect.pipe(
@@ -1968,6 +2047,45 @@ export function makeClaudeAdapterV2( · 9 unmodified lines
19682047 ),
19692048 );
19702049
2050+ const steerTurn = Effect.fn("ClaudeAdapterV2.steerTurn")(
2051+ function* (turnInput: ProviderAdapterV2SteerInput) {
2052+ const existing = yield* Ref.get(queryContext);
2053+ if (existing === null) {
2054+ return yield* new ProviderAdapterProtocolError({
2055+ provider: CLAUDE_PROVIDER,
2056+ detail: `Claude provider thread ${turnInput.providerThread.id} has no live query.`,
2057+ });
2058+ }
2059+ const currentTurn = yield* Ref.get(activeTurn);
2060+ if (currentTurn?.providerTurnId !== turnInput.providerTurnId) {
2061+ return yield* new ProviderAdapterProtocolError({
2062+ provider: CLAUDE_PROVIDER,
2063+ detail: `Claude provider turn ${turnInput.providerTurnId} is not the active turn.`,
2064+ });
2065+ }
2066+ yield* Ref.update(steeredTurns, (current) => {
2067+ const next = new Set(current);
2068+ next.add(turnInput.providerTurnId);
2069+ return next;
2070+ });
2071+ yield* existing.query.offer(
2072+ makeClaudeUserMessage({ text: turnInput.message.text, priority: "now" }),
2073+ );
2074+ },
2075+ (effect, turnInput) =>
2076+ effect.pipe(
2077+ Effect.mapError(
2078+ (cause) =>
2079+ new ProviderAdapterSteerRunError({
2080+ provider: CLAUDE_PROVIDER,
2081+ providerThreadId: turnInput.providerThread.id,
2082+ providerTurnId: turnInput.providerTurnId,
2083+ cause,
2084+ }),
2085+ ),
2086+ ),
2087+ );
2088+
19712089 const closeSession = Effect.fnUntraced(function* () {
19722090 const existing = yield* Ref.get(queryContext);
19732091 if (existing !== null) {
@@ -2040,13 +2158,7 @@ export function makeClaudeAdapterV2( · 66 unmodified lines
20402158 ),
20412159 ),
20422160 startTurn,
2043− steerTurn: (turnInput) =>
2044− Effect.fail(
2045− new ProviderAdapterSteerRunUnsupportedError({
2046− provider: CLAUDE_PROVIDER,
2047− providerThreadId: turnInput.providerThread.id,
2048− }),
2049− ),
2161+ steerTurn,
20502162 interruptTurn,
20512163 respondToRuntimeRequest: Effect.fn("ClaudeAdapterV2.respondToRuntimeRequest")(
20522164 function* (requestInput) {
apps/server/src/orchestration-v2/Orchestrator.ts+33−3
@@ -1099,16 +1099,46 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio · 1098 unmodified lines
10991099 ),
11001100 );
11011101
1102− const currentAttempt = input.projection.attempts.find(
1102+ const waitForInterruptedProviderTurn = (
1103+ attemptsRemaining = 1_000,
1104+ ): Effect.Effect<OrchestrationV2ThreadProjection, OrchestratorV2Error> =>
1105+ Effect.gen(function* () {
1106+ const current = yield* projectionStore
1107+ .getThreadProjection(input.command.threadId)
1108+ .pipe(
1109+ Effect.mapError(
1110+ () => new OrchestratorProjectionError({ threadId: input.command.threadId }),
1111+ ),
1112+ );
1113+ const interruptedTurn = current.providerTurns.find(
1114+ (candidate) => candidate.id === providerTurn.id,
1115+ );
1116+ if (interruptedTurn !== undefined && interruptedTurn.status !== "running") {
1117+ return current;
1118+ }
1119+ if (attemptsRemaining <= 0) {
1120+ return yield* new OrchestratorDispatchError({
1121+ commandId: input.command.commandId,
1122+ commandType: input.command.type,
1123+ cause: `Provider turn ${providerTurn.id} did not terminalize before steering restart.`,
1124+ });
1125+ }
1126+ yield* Effect.yieldNow;
1127+ return yield* waitForInterruptedProviderTurn(attemptsRemaining - 1);
1128+ });
1129+
1130+ const postInterruptProjection = yield* waitForInterruptedProviderTurn();
1131+
1132+ const currentAttempt = postInterruptProjection.attempts.find(
11031133 (candidate) => candidate.id === targetRun.activeAttemptId,
11041134 );
1105− const currentRootNode = input.projection.nodes.find(
1135+ const currentRootNode = postInterruptProjection.nodes.find(
11061136 (candidate) => candidate.id === rootNodeId,
11071137 );
11081138 const attemptOrdinal =
11091139 Math.max(
11101140 0,
1111− ...input.projection.attempts
1141+ ...postInterruptProjection.attempts
11121142 .filter((candidate) => candidate.runId === targetRun.id)
11131143 .map((candidate) => candidate.attemptOrdinal),
11141144 ) + 1;
apps/server/src/orchestration-v2/testkit/CodexReplayFixtures.integration.test.ts+2−8
@@ -123,16 +123,10 @@ const scenarioExpectations = { · 122 unmodified lines
123123 },
124124 message_steering: {
125125 outgoing: ["initialize", "initialized", "thread/start", "turn/start", "turn/steer"],
126− incoming: [
127− "turn/started",
128− "turn/completed",
129− "item/agentMessage/delta",
130− "item/commandExecution/requestApproval",
131− "serverRequest/resolved",
132− ],
126+ incoming: ["turn/started", "turn/completed", "item/agentMessage/delta"],
133127 turnStartCount: 1,
134128 turnCompletedCount: 1,
135− approvalRequestCount: 1,
129+ approvalRequestCount: 0,
136130 },
137131 turn_interrupt: {
138132 outgoing: ["initialize", "initialized", "thread/start", "turn/start", "turn/interrupt"],
apps/server/src/orchestration-v2/testkit/OrchestratorScenario.ts+36−0
@@ -2,6 +2,7 @@ import type { · 1 unmodified line
22 OrchestrationV2Command,
33 OrchestrationV2DomainEvent,
44 OrchestrationV2RuntimeRequest,
5+ OrchestrationV2Run,
56 OrchestrationV2ShellSnapshot,
67 OrchestrationV2StoredEvent,
78 OrchestrationV2ThreadProjection,
@@ -37,6 +38,11 @@ export type OrchestratorV2ScenarioStep = · 29 unmodified lines
3738 readonly type: "await_thread_idle";
3839 readonly threadId: ThreadId;
3940 }
41+ | {
42+ readonly type: "await_run_steerable";
43+ readonly threadId: ThreadId;
44+ readonly runId: OrchestrationV2Run["id"];
45+ }
4046 | {
4147 readonly type: "respond_to_next_runtime_request";
4248 readonly threadId: ThreadId;
@@ -208,6 +214,33 @@ export function runOrchestratorV2Scenario( · 165 unmodified lines
208214 return yield* waitForThreadIdle(threadId, attemptsRemaining - 1);
209215 });
210216
217+ const waitForRunSteerable = (
218+ threadId: ThreadId,
219+ runId: OrchestrationV2Run["id"],
220+ attemptsRemaining = 1_000,
221+ ): Effect.Effect<void, OrchestratorV2Error | OrchestratorV2ScenarioStepError, never> =>
222+ Effect.gen(function* () {
223+ const projection = yield* orchestrator.getThreadProjection(threadId);
224+ const run = projection.runs.find((candidate) => candidate.id === runId);
225+ const providerTurn = projection.providerTurns.find(
226+ (candidate) =>
227+ run?.activeAttemptId !== null &&
228+ candidate.runAttemptId === run?.activeAttemptId &&
229+ candidate.status === "running",
230+ );
231+ if (run?.status === "running" && providerTurn !== undefined) {
232+ return;
233+ }
234+ if (attemptsRemaining <= 0) {
235+ return yield* new OrchestratorV2ScenarioStepError({
236+ scenario: scenario.name,
237+ step: `await_run_steerable:${runId}`,
238+ });
239+ }
240+ yield* Effect.yieldNow;
241+ return yield* waitForRunSteerable(threadId, runId, attemptsRemaining - 1);
242+ });
243+
211244 for (const step of scenarioSteps(scenario)) {
212245 switch (step.type) {
213246 case "dispatch": {
@@ -242,6 +275,9 @@ export function runOrchestratorV2Scenario( · 28 unmodified lines
242275 case "await_thread_idle":
243276 yield* waitForThreadIdle(step.threadId);
244277 break;
278+ case "await_run_steerable":
279+ yield* waitForRunSteerable(step.threadId, step.runId);
280+ break;
245281 case "respond_to_next_runtime_request": {
246282 const request = yield* waitForPendingRuntimeRequest(step.threadId);
247283 const result = yield* orchestrator.dispatch({
apps/server/src/orchestration-v2/testkit/fixtures/index.ts+16−5
@@ -1,3 +1,4 @@
1+import { assertClaudeMessageSteeringOutput } from "./message_steering/claude_output.ts";
12 import { assertMessageSteeringOutput } from "./message_steering/codex_output.ts";
23 import { messageSteeringInput } from "./message_steering/input.ts";
34 import { assertMultiTurnClaudeOutput } from "./multi_turn/claude_output.ts";
@@ -207,6 +208,12 @@ export const ORCHESTRATOR_REPLAY_FIXTURES = [ · 203 unmodified lines
207208 modelSelection: CODEX_MODEL_SELECTION,
208209 assertOutput: assertQueuedTurnOutput,
209210 },
211+ {
212+ provider: "claudeAgent",
213+ transcriptFile: new URL("./queued_turn/claude_transcript.ndjson", import.meta.url),
214+ modelSelection: CLAUDE_MODEL_SELECTION,
215+ assertOutput: assertQueuedTurnOutput,
216+ },
210217 ],
211218 },
212219 {
@@ -274,9 +281,14 @@ export const ORCHESTRATOR_REPLAY_FIXTURES = [ · 61 unmodified lines
274281 provider: "codex",
275282 transcriptFile: new URL("./message_steering/codex_transcript.ndjson", import.meta.url),
276283 modelSelection: CODEX_MODEL_SELECTION,
277− runtimePolicyOverride: READ_ONLY_ON_REQUEST_POLICY,
278284 assertOutput: assertMessageSteeringOutput,
279285 },
286+ {
287+ provider: "claudeAgent",
288+ transcriptFile: new URL("./message_steering/claude_transcript.ndjson", import.meta.url),
289+ modelSelection: CLAUDE_MODEL_SELECTION,
290+ assertOutput: assertClaudeMessageSteeringOutput,
291+ },
280292 ],
281293 },
282294 {
@@ -313,10 +325,9 @@ export const ORCHESTRATOR_REPLAY_FIXTURES = [ · 30 unmodified lines
313325 // `tool_call_restricted_granular/claude_transcript.ndjson`, and
314326 // docs/orchestration-v2/provider-capability-system.md.
315327
316−// TODO(claude-v2/control): add Claude providers to queued-turn, interrupt, and steering fixtures
317−// once the real adapter exposes those behaviors through capability-checked V2 paths.
318−// Cross-reference `queued_turn/codex_transcript.ndjson`, `turn_interrupt/codex_transcript.ndjson`,
319−// `message_steering/codex_transcript.ndjson`, and docs/orchestration-v2/feature-lifecycles.md.
328+// TODO(claude-v2/control): add a Claude interrupt fixture once explicit run interrupt behavior
329+// is fully hardened. Cross-reference `turn_interrupt/codex_transcript.ndjson` and
330+// docs/orchestration-v2/feature-lifecycles.md.
320331
321332 // TODO(claude-v2/context-transfer): add provider-switch handoff and return fixtures when portable
322333 // context handoff is implemented. Cross-reference docs/orchestration-v2/provider-switching-and-context.md
apps/server/src/orchestration-v2/testkit/fixtures/message_steering/claude_output.ts+43−0
@@ -0,0 +1,43 @@
1+import { assert } from "@effect/vitest";
2+import type { ProviderReplayTranscript } from "@t3tools/contracts";
3+
4+import type { OrchestratorV2ScenarioResult } from "../../OrchestratorScenario.ts";
5+import {
6+ assertAssistantTextIncludes,
7+ assertBaseProjection,
8+ assertRuntimeRequestCounts,
9+ assertSemanticProjectionIntegrity,
10+ assertTurnItemTypes,
11+ assertUserMessageInputIntents,
12+ assertUserMessagesInclude,
13+ assertVisibleTurnItemsMirrorLocalTurnItems,
14+ MESSAGE_STEERING_INITIAL_PROMPT,
15+ MESSAGE_STEERING_STEER_PROMPT,
16+ projectionFor,
17+} from "../shared.ts";
18+
19+export function assertClaudeMessageSteeringOutput(
20+ result: OrchestratorV2ScenarioResult,
21+ transcript: ProviderReplayTranscript,
22+) {
23+ assert.equal(transcript.provider, "claudeAgent");
24+ assertBaseProjection({ result, transcript, runCount: 1, runStatuses: ["completed"] });
25+
26+ const projection = projectionFor(result, transcript.scenario);
27+ assertSemanticProjectionIntegrity(projection);
28+ assertVisibleTurnItemsMirrorLocalTurnItems(projection);
29+ assertTurnItemTypes(projection, ["user_message", "assistant_message"]);
30+ assertRuntimeRequestCounts(projection, { total: 0 });
31+ assertUserMessagesInclude(projection, [
32+ MESSAGE_STEERING_INITIAL_PROMPT,
33+ MESSAGE_STEERING_STEER_PROMPT,
34+ ]);
35+ assertUserMessageInputIntents(projection, ["turn_start", "steer"]);
36+ assertAssistantTextIncludes(projection, "steering fixture observed");
37+ assert.equal(projection.runs.length, 1, "steering must attach to the active run");
38+ assert.equal(
39+ projection.providerTurns.length,
40+ 1,
41+ "active steering must not create a new provider turn",
42+ );
43+}
apps/server/src/orchestration-v2/testkit/fixtures/message_steering/claude_transcript.ndjson+13−0
@@ -0,0 +1,13 @@
1+{"type":"transcript_start","provider":"claudeAgent","protocol":"claude-agent-sdk.query","version":"0.2.111","scenario":"message_steering","metadata":{"prompts":["Respond with exactly: steering fixture initial response","Actually, respond with exactly: steering fixture observed"],"model":"claude-sonnet-4-6","nativeSessionId":"05f72f7d-81d5-4db9-a3cf-234191cf79f9","queryMode":"active_steering","tools":"claude_code","permissionMode":"bypassPermissions","generatedBy":"recordClaudeAgentSdkReplayTranscript"}}
2+{"type":"expect_outbound","label":"query.open","frame":{"type":"query.open","options":{"model":"claude-sonnet-4-6","tools":{"type":"preset","preset":"claude_code"},"permissionMode":"bypassPermissions","allowDangerouslySkipPermissions":true,"sessionId":"05f72f7d-81d5-4db9-a3cf-234191cf79f9"}}}
3+{"type":"expect_outbound","label":"prompt.offer:1","frame":{"type":"prompt.offer","message":{"type":"user","message":{"role":"user","content":"Respond with exactly: steering fixture initial response"},"parent_tool_use_id":null}}}
4+{"type":"expect_outbound","label":"prompt.offer:2","frame":{"type":"prompt.offer","message":{"type":"user","message":{"role":"user","content":"Actually, respond with exactly: steering fixture observed"},"parent_tool_use_id":null,"priority":"now"}}}
5+{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"hook_started","hook_id":"2961831d-241e-47c4-8099-b8779c6b3878","hook_name":"SessionStart:startup","hook_event":"SessionStart","uuid":"d45438eb-d485-490f-9668-a02c5721a6b3","session_id":"05f72f7d-81d5-4db9-a3cf-234191cf79f9"}}
6+{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"hook_response","hook_id":"2961831d-241e-47c4-8099-b8779c6b3878","hook_name":"SessionStart:startup","hook_event":"SessionStart","output":"","stdout":"","stderr":"","exit_code":0,"outcome":"success","uuid":"40ef0c52-1168-4049-99af-4c3beb507994","session_id":"05f72f7d-81d5-4db9-a3cf-234191cf79f9"}}
7+{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"init","agents":[],"apiKeySource":"none","claude_code_version":"2.1.111","cwd":"/tmp/claude-replay-message_steering","tools":[],"mcp_servers":[],"model":"claude-sonnet-4-6","permissionMode":"bypassPermissions","slash_commands":[],"output_style":"default","skills":[],"plugins":[],"fast_mode_state":"off","uuid":"71579637-9bc5-4182-b104-4ebb8f2bda0d","session_id":"05f72f7d-81d5-4db9-a3cf-234191cf79f9"}}
8+{"type":"emit_inbound","label":"result","frame":{"type":"result","subtype":"error_during_execution","duration_ms":747,"duration_api_ms":0,"is_error":true,"num_turns":1,"stop_reason":null,"session_id":"05f72f7d-81d5-4db9-a3cf-234191cf79f9","total_cost_usd":0,"usage":{"input_tokens":0,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":0,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"},"modelUsage":{},"permission_denials":[],"terminal_reason":"aborted_streaming","fast_mode_state":"off","uuid":"781fa8c7-98dd-4679-bb99-82d4c77c9b0c","errors":["[ede_diagnostic] result_type=user last_content_type=n/a stop_reason=null","Error: Request was aborted.\n at vn8 (/tmp/claude-replay-message_steering/node_modules/.bun/@anthropic-ai+claude-agent-sdk@0.2.111+3c5d820c62823f0b/node_modules/@anthropic-ai/claude-agent-sdk/cli.js:5550:1432)\n at next (native:1:11)\n at a85 (/tmp/claude-replay-message_steering/node_modules/.bun/@anthropic-ai+claude-agent-sdk@0.2.111+3c5d820c62823f0b/node_modules/@anthropic-ai/claude-agent-sdk/cli.js:8321:5086)\n at processTicksAndRejections (native:7:39)"]}}
9+{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"init","agents":[],"apiKeySource":"none","claude_code_version":"2.1.111","cwd":"/tmp/claude-replay-message_steering","tools":[],"mcp_servers":[],"model":"claude-sonnet-4-6","permissionMode":"bypassPermissions","slash_commands":[],"output_style":"default","skills":[],"plugins":[],"fast_mode_state":"off","uuid":"60494b15-57a4-417d-a0d0-0ce42590c36e","session_id":"05f72f7d-81d5-4db9-a3cf-234191cf79f9"}}
10+{"type":"emit_inbound","label":"assistant","frame":{"type":"assistant","message":{"model":"claude-sonnet-4-6","id":"msg_01Cjp5MnmN6F7wwrzNss9zVW","type":"message","role":"assistant","content":[{"type":"text","text":"steering fixture observed"}],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":2347,"cache_read_input_tokens":9455,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":2347},"output_tokens":1,"service_tier":"standard","inference_geo":"not_available"},"context_management":null},"parent_tool_use_id":null,"session_id":"05f72f7d-81d5-4db9-a3cf-234191cf79f9","uuid":"2faa9bee-1c72-4b8f-ab04-9796a7ecf6a2"}}
11+{"type":"emit_inbound","label":"rate_limit_event","frame":{"type":"rate_limit_event","rate_limit_info":{"status":"allowed"},"uuid":"b0bfc7e0-1698-4480-821d-16262ceb4448","session_id":"05f72f7d-81d5-4db9-a3cf-234191cf79f9"}}
12+{"type":"emit_inbound","label":"result","frame":{"type":"result","subtype":"success","is_error":false,"api_error_status":null,"duration_ms":3836,"duration_api_ms":1808,"num_turns":1,"result":"steering fixture observed","stop_reason":"end_turn","session_id":"05f72f7d-81d5-4db9-a3cf-234191cf79f9","total_cost_usd":0.01173675,"usage":{"input_tokens":3,"cache_creation_input_tokens":2347,"cache_read_input_tokens":9455,"output_tokens":6,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":2347,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[{"input_tokens":3,"output_tokens":6,"cache_read_input_tokens":9455,"cache_creation_input_tokens":2347,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":2347},"type":"message"}],"speed":"standard"},"modelUsage":{"claude-sonnet-4-6":{"inputTokens":3,"outputTokens":6,"cacheReadInputTokens":9455,"cacheCreationInputTokens":2347,"webSearchRequests":0,"costUSD":0.01173675,"contextWindow":200000,"maxOutputTokens":32000}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","uuid":"7ac0393a-c862-449c-95f7-bbf391264476"}}
13+{"type":"runtime_exit","status":"success"}
apps/server/src/orchestration-v2/testkit/fixtures/message_steering/codex_output.ts+9−12
@@ -3,40 +3,37 @@ import type { ProviderReplayTranscript } from "@t3tools/contracts"; · 2 unmodified lines
33
44 import type { OrchestratorV2ScenarioResult } from "../../OrchestratorScenario.ts";
55 import {
6− assertAllRuntimeRequestsResolved,
6+ assertAssistantTextIncludes,
77 assertBaseProjection,
88 assertRuntimeRequestCounts,
9− assertRuntimeRequestKinds,
109 assertSemanticProjectionIntegrity,
1110 assertTurnItemTypes,
1211 assertUserMessageInputIntents,
1312 assertUserMessagesInclude,
1413 assertVisibleTurnItemsMirrorLocalTurnItems,
14+ MESSAGE_STEERING_INITIAL_PROMPT,
1515 MESSAGE_STEERING_STEER_PROMPT,
1616 projectionFor,
17− TOOL_CALL_WRITE_PROMPT,
1817 } from "../shared.ts";
1918
2019 export function assertMessageSteeringOutput(
2120 result: OrchestratorV2ScenarioResult,
2221 transcript: ProviderReplayTranscript,
2322 ) {
23+ assert.equal(transcript.provider, "codex");
2424 assertBaseProjection({ result, transcript, runCount: 1, runStatuses: ["completed"] });
2525
2626 const projection = projectionFor(result, transcript.scenario);
2727 assertSemanticProjectionIntegrity(projection);
2828 assertVisibleTurnItemsMirrorLocalTurnItems(projection);
29− assertTurnItemTypes(projection, [
30− "user_message",
31− "command_execution",
32− "approval_request",
33− "assistant_message",
29+ assertTurnItemTypes(projection, ["user_message", "assistant_message"]);
30+ assertRuntimeRequestCounts(projection, { total: 0 });
31+ assertUserMessagesInclude(projection, [
32+ MESSAGE_STEERING_INITIAL_PROMPT,
33+ MESSAGE_STEERING_STEER_PROMPT,
3434 ]);
35− assertRuntimeRequestCounts(projection, { total: 1, resolved: 1 });
36− assertRuntimeRequestKinds(projection, ["command"]);
37− assertAllRuntimeRequestsResolved(projection);
38− assertUserMessagesInclude(projection, [TOOL_CALL_WRITE_PROMPT, MESSAGE_STEERING_STEER_PROMPT]);
3935 assertUserMessageInputIntents(projection, ["turn_start", "steer"]);
36+ assertAssistantTextIncludes(projection, "steering fixture observed");
4037 assert.equal(projection.runs.length, 1, "steering must attach to the active run");
4138 assert.equal(
4239 projection.providerTurns.length,
apps/server/src/orchestration-v2/testkit/fixtures/message_steering/codex_transcript.ndjson+44−42
@@ -1,49 +1,51 @@
1−{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"message_steering","metadata":{"source":"codex-app-server-probe","fileName":"message_steering.ndjson","description":"One active turn receives an immediate turn/steer request."}}
1+{"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.128.0","scenario":"message_steering","metadata":{"source":"record-codex-app-server-replay-fixture","fileName":"message_steering.ndjson","description":"One active turn receives an immediate turn/steer request."}}
22 {"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
3−{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}}
3+{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.128.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex_p","platformFamily":"unix","platformOs":"macos"}}}
44 {"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
55 {"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
6−{"type":"emit_inbound","label":"thread/start","frame":{"id":2,"result":{"thread":{"id":"019dadec-ebd7-75f0-812e-27a7e57bb374","forkedFromId":null,"preview":"","ephemeral":false,"modelProvider":"openai","createdAt":1776739478,"updatedAt":1776739478,"status":{"type":"idle"},"path":"/Users/julius/.codex/sessions/2026/04/20/rollout-2026-04-20T19-44-38-019dadec-ebd7-75f0-812e-27a7e57bb374.jsonl","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/packages/effect-codex-app-server","cliVersion":"0.120.0","source":"vscode","agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"turns":[]},"model":"gpt-5.4","modelProvider":"openai","serviceTier":"fast","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/packages/effect-codex-app-server","approvalPolicy":"on-request","approvalsReviewer":"user","sandbox":{"type":"workspaceWrite","writableRoots":["/Users/julius/.codex/memories"],"readOnlyAccess":{"type":"fullAccess"},"networkAccess":false,"excludeTmpdirEnvVar":false,"excludeSlashTmp":false},"reasoningEffort":"xhigh"}}}
7−{"type":"expect_outbound","label":"turn/start","frame":{"id":3,"method":"turn/start","params":{"approvalPolicy":"on-request","input":[{"text":"Create or overwrite .codex-probe-write-action.txt with exactly this text: codex app-server approval fixture. Use a local shell command or file edit only, then briefly report what happened. Do not read package metadata, use GitHub, use web, or use MCP.","type":"text"}],"sandboxPolicy":{"access":{"type":"fullAccess"},"networkAccess":false,"type":"readOnly"},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374"}}}
8−{"type":"emit_inbound","label":"thread/started","frame":{"method":"thread/started","params":{"thread":{"id":"019dadec-ebd7-75f0-812e-27a7e57bb374","forkedFromId":null,"preview":"","ephemeral":false,"modelProvider":"openai","createdAt":1776739478,"updatedAt":1776739478,"status":{"type":"idle"},"path":"/Users/julius/.codex/sessions/2026/04/20/rollout-2026-04-20T19-44-38-019dadec-ebd7-75f0-812e-27a7e57bb374.jsonl","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/packages/effect-codex-app-server","cliVersion":"0.120.0","source":"vscode","agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"turns":[]}}}}
6+{"type":"emit_inbound","label":"remoteControl/status/changed","frame":{"method":"remoteControl/status/changed","params":{"status":"disabled","environmentId":null}}}
7+{"type":"emit_inbound","label":"thread/start","frame":{"id":2,"result":{"thread":{"id":"019e014d-b214-71f0-9538-23527e8247ca","forkedFromId":null,"preview":"","ephemeral":false,"modelProvider":"openai","createdAt":1778138329,"updatedAt":1778138329,"status":{"type":"idle"},"path":"/Users/julius/.codex_p/sessions/2026/05/07/rollout-2026-05-07T00-18-49-019e014d-b214-71f0-9538-23527e8247ca.jsonl","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/apps/server","cliVersion":"0.128.0","source":"vscode","agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"turns":[]},"model":"gpt-5.5","modelProvider":"openai","serviceTier":"fast","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/apps/server","instructionSources":["/Users/julius/.codex_p/AGENTS.md","/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/AGENTS.md"],"approvalPolicy":"on-request","approvalsReviewer":"user","sandbox":{"type":"workspaceWrite","writableRoots":["/Users/julius/.codex_p/memories"],"networkAccess":false,"excludeTmpdirEnvVar":false,"excludeSlashTmp":false},"permissionProfile":{"type":"managed","network":{"enabled":false},"fileSystem":{"type":"restricted","entries":[{"path":{"type":"special","value":{"kind":"root"}},"access":"read"},{"path":{"type":"special","value":{"kind":"project_roots","subpath":null}},"access":"write"},{"path":{"type":"special","value":{"kind":"slash_tmp"}},"access":"write"},{"path":{"type":"special","value":{"kind":"tmpdir"}},"access":"write"},{"path":{"type":"special","value":{"kind":"project_roots","subpath":".git"}},"access":"read"},{"path":{"type":"special","value":{"kind":"project_roots","subpath":".agents"}},"access":"read"},{"path":{"type":"special","value":{"kind":"project_roots","subpath":".codex"}},"access":"read"},{"path":{"type":"path","path":"/Users/julius/.codex_p/memories"},"access":"write"}]}},"activePermissionProfile":{"id":":workspace","extends":null,"modifications":[]},"reasoningEffort":"medium"}}}
8+{"type":"expect_outbound","label":"turn/start","frame":{"id":3,"method":"turn/start","params":{"input":[{"text":"Respond with exactly: steering fixture initial response","type":"text"}],"threadId":"019e014d-b214-71f0-9538-23527e8247ca"}}}
9+{"type":"emit_inbound","label":"thread/started","frame":{"method":"thread/started","params":{"thread":{"id":"019e014d-b214-71f0-9538-23527e8247ca","forkedFromId":null,"preview":"","ephemeral":false,"modelProvider":"openai","createdAt":1778138329,"updatedAt":1778138329,"status":{"type":"idle"},"path":"/Users/julius/.codex_p/sessions/2026/05/07/rollout-2026-05-07T00-18-49-019e014d-b214-71f0-9538-23527e8247ca.jsonl","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/apps/server","cliVersion":"0.128.0","source":"vscode","agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"turns":[]}}}}
910 {"type":"emit_inbound","label":"deprecationNotice","frame":{"method":"deprecationNotice","params":{"summary":"`[features].collab` is deprecated. Use `[features].multi_agent` instead.","details":"Enable it with `--enable multi_agent` or `[features].multi_agent` in config.toml. See https://developers.openai.com/codex/config-basic#feature-flags for details."}}}
10−{"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"codex_apps","status":"starting","error":null}}}
11+{"type":"emit_inbound","label":"warning","frame":{"method":"warning","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","message":"Under-development features enabled: apply_patch_freeform. Under-development features are incomplete and may behave unpredictably. To suppress this warning, set `suppress_unstable_features_warning = true` in /Users/julius/.codex_p/config.toml."}}}
1112 {"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"uidotsh","status":"starting","error":null}}}
12−{"type":"emit_inbound","label":"turn/start","frame":{"id":3,"result":{"turn":{"id":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","items":[],"status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}}
13−{"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","status":{"type":"active","activeFlags":[]}}}}
14−{"type":"emit_inbound","label":"turn/started","frame":{"method":"turn/started","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turn":{"id":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","items":[],"status":"inProgress","error":null,"startedAt":1776739478,"completedAt":null,"durationMs":null}}}}
15−{"type":"expect_outbound","label":"turn/steer","frame":{"id":4,"method":"turn/steer","params":{"expectedTurnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","input":[{"text":"Actually, respond with exactly: steering fixture observed","type":"text"}],"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374"}}}
16−{"type":"emit_inbound","label":"turn/steer","frame":{"id":4,"result":{"turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}}
17−{"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"codex_apps","status":"ready","error":null}}}
13+{"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"codex_apps","status":"starting","error":null}}}
14+{"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"computer-use","status":"starting","error":null}}}
15+{"type":"emit_inbound","label":"turn/start","frame":{"id":3,"result":{"turn":{"id":"019e014d-b227-7af1-a889-6c7d828eb1a2","items":[],"status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}}
16+{"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","status":{"type":"active","activeFlags":[]}}}}
17+{"type":"emit_inbound","label":"turn/started","frame":{"method":"turn/started","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turn":{"id":"019e014d-b227-7af1-a889-6c7d828eb1a2","items":[],"status":"inProgress","error":null,"startedAt":1778138329,"completedAt":null,"durationMs":null}}}}
18+{"type":"expect_outbound","label":"turn/steer","frame":{"id":4,"method":"turn/steer","params":{"expectedTurnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","input":[{"text":"Actually, respond with exactly: steering fixture observed","type":"text"}],"threadId":"019e014d-b214-71f0-9538-23527e8247ca"}}}
19+{"type":"emit_inbound","label":"turn/steer","frame":{"id":4,"result":{"turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}}
20+{"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"computer-use","status":"ready","error":null}}}
1821 {"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"uidotsh","status":"ready","error":null}}}
19−{"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"userMessage","id":"550a4417-1843-4e5e-8334-6663461044d5","content":[{"type":"text","text":"Create or overwrite .codex-probe-write-action.txt with exactly this text: codex app-server approval fixture. Use a local shell command or file edit only, then briefly report what happened. Do not read package metadata, use GitHub, use web, or use MCP.","text_elements":[]}]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}}
20−{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"550a4417-1843-4e5e-8334-6663461044d5","content":[{"type":"text","text":"Create or overwrite .codex-probe-write-action.txt with exactly this text: codex app-server approval fixture. Use a local shell command or file edit only, then briefly report what happened. Do not read package metadata, use GitHub, use web, or use MCP.","text_elements":[]}]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}}
21−{"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":0,"windowDurationMins":300,"resetsAt":1776748632},"secondary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1777335432},"credits":null,"planType":"pro"}}}}
22−{"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"reasoning","id":"rs_0a7ba873c300712d0169e6e49aa908819b82a8cc3df84c0036","summary":[],"content":[]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}}
23−{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"reasoning","id":"rs_0a7ba873c300712d0169e6e49aa908819b82a8cc3df84c0036","summary":[],"content":[]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}}
24−{"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","status":{"type":"active","activeFlags":["waitingOnApproval"]}}}}
25−{"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"commandExecution","id":"call_SkVd4jdkKEnDTMUaNZwexNLm","command":"/bin/zsh -lc \"printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt\"","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/packages/effect-codex-app-server","processId":null,"source":"agent","status":"inProgress","commandActions":[{"type":"unknown","command":"printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt"}],"aggregatedOutput":null,"exitCode":null,"durationMs":null},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}}
26−{"type":"emit_inbound","label":"item/commandExecution/requestApproval","frame":{"method":"item/commandExecution/requestApproval","id":0,"params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","itemId":"call_SkVd4jdkKEnDTMUaNZwexNLm","reason":"Do you want to allow writing the requested fixture file in the workspace?","command":"/bin/zsh -lc \"printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt\"","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/packages/effect-codex-app-server","commandActions":[{"type":"unknown","command":"printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt"}],"proposedExecpolicyAmendment":["/bin/zsh","-lc","printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt"],"availableDecisions":["accept",{"acceptWithExecpolicyAmendment":{"execpolicy_amendment":["/bin/zsh","-lc","printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt"]}},"cancel"]}}}
27−{"type":"expect_outbound","label":"item/commandExecution/requestApproval","frame":{"id":0,"result":{"decision":"accept"}}}
28−{"type":"emit_inbound","label":"serverRequest/resolved","frame":{"method":"serverRequest/resolved","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","requestId":0}}}
29−{"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","status":{"type":"active","activeFlags":[]}}}}
30−{"type":"emit_inbound","label":"thread/tokenUsage/updated","frame":{"method":"thread/tokenUsage/updated","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","tokenUsage":{"total":{"totalTokens":28779,"inputTokens":28169,"cachedInputTokens":2432,"outputTokens":610,"reasoningOutputTokens":492},"last":{"totalTokens":28779,"inputTokens":28169,"cachedInputTokens":2432,"outputTokens":610,"reasoningOutputTokens":492},"modelContextWindow":258400}}}}
31−{"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":0,"windowDurationMins":300,"resetsAt":1776748632},"secondary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1777335432},"credits":null,"planType":"pro"}}}}
32−{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"commandExecution","id":"call_SkVd4jdkKEnDTMUaNZwexNLm","command":"/bin/zsh -lc \"printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt\"","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/packages/effect-codex-app-server","processId":"78219","source":"unifiedExecStartup","status":"completed","commandActions":[{"type":"unknown","command":"printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt"}],"aggregatedOutput":null,"exitCode":0,"durationMs":0},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}}
33−{"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"userMessage","id":"c046f20c-4ec9-4e49-9c31-bcc25ca81b1f","content":[{"type":"text","text":"Actually, respond with exactly: steering fixture observed","text_elements":[]}]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}}
34−{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"c046f20c-4ec9-4e49-9c31-bcc25ca81b1f","content":[{"type":"text","text":"Actually, respond with exactly: steering fixture observed","text_elements":[]}]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}}
35−{"type":"emit_inbound","label":"thread/tokenUsage/updated","frame":{"method":"thread/tokenUsage/updated","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","tokenUsage":{"total":{"totalTokens":28779,"inputTokens":28169,"cachedInputTokens":2432,"outputTokens":610,"reasoningOutputTokens":492},"last":{"totalTokens":28779,"inputTokens":28169,"cachedInputTokens":2432,"outputTokens":610,"reasoningOutputTokens":492},"modelContextWindow":258400}}}}
36−{"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":0,"windowDurationMins":300,"resetsAt":1776748632},"secondary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1777335432},"credits":null,"planType":"pro"}}}}
37−{"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"reasoning","id":"rs_0a7ba873c300712d0169e6e4a28490819b9549007ee1031138","summary":[],"content":[]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}}
38−{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"reasoning","id":"rs_0a7ba873c300712d0169e6e4a28490819b9549007ee1031138","summary":[],"content":[]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}}
39−{"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_0a7ba873c300712d0169e6e4a2ba50819ba77e49ab176e7a34","text":"","phase":"final_answer","memoryCitation":null},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}}
40−{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","itemId":"msg_0a7ba873c300712d0169e6e4a2ba50819ba77e49ab176e7a34","delta":"ste"}}}
41−{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","itemId":"msg_0a7ba873c300712d0169e6e4a2ba50819ba77e49ab176e7a34","delta":"ering"}}}
42−{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","itemId":"msg_0a7ba873c300712d0169e6e4a2ba50819ba77e49ab176e7a34","delta":" fixture"}}}
43−{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","itemId":"msg_0a7ba873c300712d0169e6e4a2ba50819ba77e49ab176e7a34","delta":" observed"}}}
44−{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_0a7ba873c300712d0169e6e4a2ba50819ba77e49ab176e7a34","text":"steering fixture observed","phase":"final_answer","memoryCitation":null},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}}
45−{"type":"emit_inbound","label":"thread/tokenUsage/updated","frame":{"method":"thread/tokenUsage/updated","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","tokenUsage":{"total":{"totalTokens":57642,"inputTokens":57006,"cachedInputTokens":30464,"outputTokens":636,"reasoningOutputTokens":508},"last":{"totalTokens":28863,"inputTokens":28837,"cachedInputTokens":28032,"outputTokens":26,"reasoningOutputTokens":16},"modelContextWindow":258400}}}}
46−{"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":0,"windowDurationMins":300,"resetsAt":1776748632},"secondary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1777335432},"credits":null,"planType":"pro"}}}}
47−{"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","status":{"type":"idle"}}}}
48−{"type":"emit_inbound","label":"turn/completed","frame":{"method":"turn/completed","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turn":{"id":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","items":[],"status":"completed","error":null,"startedAt":1776739478,"completedAt":1776739490,"durationMs":12401}}}}
22+{"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"codex_apps","status":"ready","error":null}}}
23+{"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"userMessage","id":"4fac8685-35e8-475c-96b6-9adb33c68158","content":[{"type":"text","text":"Respond with exactly: steering fixture initial response","text_elements":[]}]},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}}
24+{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"4fac8685-35e8-475c-96b6-9adb33c68158","content":[{"type":"text","text":"Respond with exactly: steering fixture initial response","text_elements":[]}]},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}}
25+{"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":19,"windowDurationMins":300,"resetsAt":1778142198},"secondary":{"usedPercent":31,"windowDurationMins":10080,"resetsAt":1778539136},"credits":null,"planType":"pro","rateLimitReachedType":null}}}}
26+{"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"reasoning","id":"rs_010631d904f9e19a0169fc3cdcb670819486fcc6dc7b883f58","summary":[],"content":[]},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}}
27+{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"reasoning","id":"rs_010631d904f9e19a0169fc3cdcb670819486fcc6dc7b883f58","summary":[],"content":[]},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}}
28+{"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_010631d904f9e19a0169fc3cdce8ec8194a7d8a5bb50424c09","text":"","phase":"final_answer","memoryCitation":null},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}}
29+{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cdce8ec8194a7d8a5bb50424c09","delta":"ste"}}}
30+{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cdce8ec8194a7d8a5bb50424c09","delta":"ering"}}}
31+{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cdce8ec8194a7d8a5bb50424c09","delta":" fixture"}}}
32+{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cdce8ec8194a7d8a5bb50424c09","delta":" initial"}}}
33+{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cdce8ec8194a7d8a5bb50424c09","delta":" response"}}}
34+{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_010631d904f9e19a0169fc3cdce8ec8194a7d8a5bb50424c09","text":"steering fixture initial response","phase":"final_answer","memoryCitation":null},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}}
35+{"type":"emit_inbound","label":"thread/tokenUsage/updated","frame":{"method":"thread/tokenUsage/updated","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","tokenUsage":{"total":{"totalTokens":18900,"inputTokens":18880,"cachedInputTokens":7552,"outputTokens":20,"reasoningOutputTokens":9},"last":{"totalTokens":18900,"inputTokens":18880,"cachedInputTokens":7552,"outputTokens":20,"reasoningOutputTokens":9},"modelContextWindow":258400}}}}
36+{"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":19,"windowDurationMins":300,"resetsAt":1778142198},"secondary":{"usedPercent":31,"windowDurationMins":10080,"resetsAt":1778539136},"credits":null,"planType":"pro","rateLimitReachedType":null}}}}
37+{"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"userMessage","id":"3d8198bc-53c8-4885-8dfe-c5158eb2cfb3","content":[{"type":"text","text":"Actually, respond with exactly: steering fixture observed","text_elements":[]}]},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}}
38+{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"3d8198bc-53c8-4885-8dfe-c5158eb2cfb3","content":[{"type":"text","text":"Actually, respond with exactly: steering fixture observed","text_elements":[]}]},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}}
39+{"type":"emit_inbound","label":"thread/tokenUsage/updated","frame":{"method":"thread/tokenUsage/updated","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","tokenUsage":{"total":{"totalTokens":18900,"inputTokens":18880,"cachedInputTokens":7552,"outputTokens":20,"reasoningOutputTokens":9},"last":{"totalTokens":18900,"inputTokens":18880,"cachedInputTokens":7552,"outputTokens":20,"reasoningOutputTokens":9},"modelContextWindow":258400}}}}
40+{"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":19,"windowDurationMins":300,"resetsAt":1778142198},"secondary":{"usedPercent":31,"windowDurationMins":10080,"resetsAt":1778539136},"credits":null,"planType":"pro","rateLimitReachedType":null}}}}
41+{"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_010631d904f9e19a0169fc3cddd2b08194ba097f57a2fd6332","text":"","phase":"final_answer","memoryCitation":null},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}}
42+{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cddd2b08194ba097f57a2fd6332","delta":"ste"}}}
43+{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cddd2b08194ba097f57a2fd6332","delta":"ering"}}}
44+{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cddd2b08194ba097f57a2fd6332","delta":" fixture"}}}
45+{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cddd2b08194ba097f57a2fd6332","delta":" observed"}}}
46+{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_010631d904f9e19a0169fc3cddd2b08194ba097f57a2fd6332","text":"steering fixture observed","phase":"final_answer","memoryCitation":null},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}}
47+{"type":"emit_inbound","label":"thread/tokenUsage/updated","frame":{"method":"thread/tokenUsage/updated","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","tokenUsage":{"total":{"totalTokens":37812,"inputTokens":37784,"cachedInputTokens":26368,"outputTokens":28,"reasoningOutputTokens":9},"last":{"totalTokens":18912,"inputTokens":18904,"cachedInputTokens":18816,"outputTokens":8,"reasoningOutputTokens":0},"modelContextWindow":258400}}}}
48+{"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":19,"windowDurationMins":300,"resetsAt":1778142198},"secondary":{"usedPercent":31,"windowDurationMins":10080,"resetsAt":1778539136},"credits":null,"planType":"pro","rateLimitReachedType":null}}}}
49+{"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","status":{"type":"idle"}}}}
50+{"type":"emit_inbound","label":"turn/completed","frame":{"method":"turn/completed","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turn":{"id":"019e014d-b227-7af1-a889-6c7d828eb1a2","items":[],"status":"completed","error":null,"startedAt":1778138329,"completedAt":1778138333,"durationMs":4311}}}}
4951 {"type":"runtime_exit","status":"success"}
apps/server/src/orchestration-v2/testkit/fixtures/message_steering/input.ts+2−3
@@ -1,19 +1,18 @@
11 import {
2+ MESSAGE_STEERING_INITIAL_PROMPT,
23 MESSAGE_STEERING_STEER_PROMPT,
3− TOOL_CALL_WRITE_PROMPT,
44 type OrchestratorFixtureInput,
55 } from "../shared.ts";
66
77 export function messageSteeringInput(): OrchestratorFixtureInput {
88 return {
99 steps: [
10− { type: "message", text: TOOL_CALL_WRITE_PROMPT },
10+ { type: "message", text: MESSAGE_STEERING_INITIAL_PROMPT },
1111 {
1212 type: "steer",
1313 text: MESSAGE_STEERING_STEER_PROMPT,
1414 targetRunIndex: 1,
1515 },
16− { type: "approve_next_runtime_request" },
1716 ],
1817 };
1918 }
apps/server/src/orchestration-v2/testkit/fixtures/queued_turn/claude_transcript.ndjson+14−0
@@ -0,0 +1,14 @@
1+{"type":"transcript_start","provider":"claudeAgent","protocol":"claude-agent-sdk.query","version":"0.2.111","scenario":"queued_turn","metadata":{"prompts":["Respond with exactly: first fixture turn complete","Respond with exactly: second fixture turn complete"],"model":"claude-sonnet-4-6","nativeSessionId":"df83c769-3523-4961-95ad-9739bfdf71dd","queryMode":"streaming","tools":"claude_code","permissionMode":"bypassPermissions","generatedBy":"recordClaudeAgentSdkReplayTranscript"}}
2+{"type":"expect_outbound","label":"query.open","frame":{"type":"query.open","options":{"model":"claude-sonnet-4-6","tools":{"type":"preset","preset":"claude_code"},"permissionMode":"bypassPermissions","allowDangerouslySkipPermissions":true,"sessionId":"df83c769-3523-4961-95ad-9739bfdf71dd"}}}
3+{"type":"expect_outbound","label":"prompt.offer:1","frame":{"type":"prompt.offer","message":{"type":"user","message":{"role":"user","content":"Respond with exactly: first fixture turn complete"},"parent_tool_use_id":null}}}
4+{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"hook_started","hook_id":"cbcb8e66-b64a-49c2-a192-5928cf12f605","hook_name":"SessionStart:startup","hook_event":"SessionStart","uuid":"e1ba5cbc-e83f-452d-a12e-2decc801d1df","session_id":"df83c769-3523-4961-95ad-9739bfdf71dd"}}
5+{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"hook_response","hook_id":"cbcb8e66-b64a-49c2-a192-5928cf12f605","hook_name":"SessionStart:startup","hook_event":"SessionStart","output":"","stdout":"","stderr":"","exit_code":0,"outcome":"success","uuid":"9a8d10e1-b662-44f5-873a-cddcb1f95cd0","session_id":"df83c769-3523-4961-95ad-9739bfdf71dd"}}
6+{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"init","agents":[],"apiKeySource":"none","claude_code_version":"2.1.111","cwd":"/tmp/claude-replay-queued_turn","tools":[],"mcp_servers":[],"model":"claude-sonnet-4-6","permissionMode":"bypassPermissions","slash_commands":[],"output_style":"default","skills":[],"plugins":[],"fast_mode_state":"off","uuid":"86533c51-7a5a-4034-b94f-12b1b6dd6472","session_id":"df83c769-3523-4961-95ad-9739bfdf71dd"}}
7+{"type":"emit_inbound","label":"assistant","frame":{"type":"assistant","message":{"model":"claude-sonnet-4-6","id":"msg_01NQEE1Hdycdhefjehrr9zcz","type":"message","role":"assistant","content":[{"type":"text","text":"first fixture turn complete"}],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":2337,"cache_read_input_tokens":9455,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":2337},"output_tokens":1,"service_tier":"standard","inference_geo":"not_available"},"context_management":null},"parent_tool_use_id":null,"session_id":"df83c769-3523-4961-95ad-9739bfdf71dd","uuid":"284c2284-4cd0-4f23-8e0d-32ad924e9227"}}
8+{"type":"emit_inbound","label":"rate_limit_event","frame":{"type":"rate_limit_event","rate_limit_info":{"status":"allowed"},"uuid":"ab9548dc-42c4-4c53-bfb3-765384bd3c8c","session_id":"df83c769-3523-4961-95ad-9739bfdf71dd"}}
9+{"type":"emit_inbound","label":"result","frame":{"type":"result","subtype":"success","is_error":false,"api_error_status":null,"duration_ms":1429,"duration_api_ms":1065,"num_turns":1,"result":"first fixture turn complete","stop_reason":"end_turn","session_id":"df83c769-3523-4961-95ad-9739bfdf71dd","total_cost_usd":0.01171425,"usage":{"input_tokens":3,"cache_creation_input_tokens":2337,"cache_read_input_tokens":9455,"output_tokens":7,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":2337,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[{"input_tokens":3,"output_tokens":7,"cache_read_input_tokens":9455,"cache_creation_input_tokens":2337,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":2337},"type":"message"}],"speed":"standard"},"modelUsage":{"claude-sonnet-4-6":{"inputTokens":3,"outputTokens":7,"cacheReadInputTokens":9455,"cacheCreationInputTokens":2337,"webSearchRequests":0,"costUSD":0.01171425,"contextWindow":200000,"maxOutputTokens":32000}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","uuid":"bd5d5c48-b577-45f8-a0ef-d45fce2ab84d"}}
10+{"type":"expect_outbound","label":"prompt.offer:2","frame":{"type":"prompt.offer","message":{"type":"user","message":{"role":"user","content":"Respond with exactly: second fixture turn complete"},"parent_tool_use_id":null}}}
11+{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"init","agents":[],"apiKeySource":"none","claude_code_version":"2.1.111","cwd":"/tmp/claude-replay-queued_turn","tools":[],"mcp_servers":[],"model":"claude-sonnet-4-6","permissionMode":"bypassPermissions","slash_commands":[],"output_style":"default","skills":[],"plugins":[],"fast_mode_state":"off","uuid":"9e812fd7-f51a-49f6-878e-b95360f3da63","session_id":"df83c769-3523-4961-95ad-9739bfdf71dd"}}
12+{"type":"emit_inbound","label":"assistant","frame":{"type":"assistant","message":{"model":"claude-sonnet-4-6","id":"msg_01TLFS8vZYtEwwSUSbBztEFJ","type":"message","role":"assistant","content":[{"type":"text","text":"second fixture turn complete"}],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":19,"cache_read_input_tokens":11792,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":19},"output_tokens":1,"service_tier":"standard","inference_geo":"not_available"},"context_management":null},"parent_tool_use_id":null,"session_id":"df83c769-3523-4961-95ad-9739bfdf71dd","uuid":"39376d80-6475-4fbc-a95c-b005f83dcfee"}}
13+{"type":"emit_inbound","label":"result","frame":{"type":"result","subtype":"success","is_error":false,"api_error_status":null,"duration_ms":1866,"duration_api_ms":2133,"num_turns":1,"result":"second fixture turn complete","stop_reason":"end_turn","session_id":"df83c769-3523-4961-95ad-9739bfdf71dd","total_cost_usd":0.0154371,"usage":{"input_tokens":3,"cache_creation_input_tokens":19,"cache_read_input_tokens":11792,"output_tokens":7,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":19,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[{"input_tokens":3,"output_tokens":7,"cache_read_input_tokens":11792,"cache_creation_input_tokens":19,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":19},"type":"message"}],"speed":"standard"},"modelUsage":{"claude-sonnet-4-6":{"inputTokens":6,"outputTokens":14,"cacheReadInputTokens":21247,"cacheCreationInputTokens":2356,"webSearchRequests":0,"costUSD":0.0154371,"contextWindow":200000,"maxOutputTokens":32000}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","uuid":"5e21b6c3-b1e0-4d8c-a106-d34a14e941ee"}}
14+{"type":"runtime_exit","status":"success"}
apps/server/src/orchestration-v2/testkit/fixtures/shared.ts+19−8
@@ -37,6 +37,8 @@ export const TOOL_CALL_READ_ONLY_WORKSPACE_ROOT = "/tmp/claude-replay-tool_call_ · 36 unmodified lines
3737 export const TOOL_CALL_READ_ONLY_PROMPT = `Read ${TOOL_CALL_READ_ONLY_WORKSPACE_ROOT}/package.json and ${TOOL_CALL_READ_ONLY_WORKSPACE_ROOT}/tsconfig.json, then answer exactly: read only tool fixture complete`;
3838 export const TOOL_CALL_WRITE_PROMPT =
3939 "Create or overwrite .codex-probe-write-action.txt with exactly this text: codex app-server approval fixture. Use a local shell command or file edit only, then briefly report what happened. Do not read package metadata, use GitHub, use web, or use MCP.";
40+export const MESSAGE_STEERING_INITIAL_PROMPT =
41+ "Respond with exactly: steering fixture initial response";
4042 export const SUBAGENT_PROMPT =
4143 "Spawn 2 subagents, one to read package.json and one to read tsconfig.json";
4244 export const TURN_INTERRUPT_PROMPT =
@@ -296,8 +298,7 @@ export function materializeFixtureInput(input: { · 253 unmodified lines
296298 const nextStep = input.fixtureInput.steps[stepIndex + 1];
297299 const shouldRunInBackground =
298300 (nextStep !== undefined &&
299− (((nextStep.type === "steer" || nextStep.type === "interrupt") &&
300− nextStep.targetRunIndex === messageIndex) ||
301+ ((nextStep.type === "interrupt" && nextStep.targetRunIndex === messageIndex) ||
301302 nextStep.type === "queue_message")) ||
302303 nextStep?.type === "approve_next_runtime_request" ||
303304 nextStep?.type === "answer_next_user_input_request";
@@ -321,7 +322,13 @@ export function materializeFixtureInput(input: { · 17 unmodified lines
321322 );
322323 if (shouldRunInBackground) {
323324 activeRunDispatchKeys.add(key);
324− } else {
325+ } else if (
326+ !(
327+ nextStep !== undefined &&
328+ nextStep.type === "steer" &&
329+ nextStep.targetRunIndex === messageIndex
330+ )
331+ ) {
325332 steps.push({ type: "await_thread_idle", threadId: ids.threadId });
326333 }
327334 }
@@ -401,6 +408,11 @@ export function materializeFixtureInput(input: { · 73 unmodified lines
401408 break;
402409 case "steer":
403410 messageIndex += 1;
411+ steps.push({
412+ type: "await_run_steerable",
413+ threadId: ids.threadId,
414+ runId: runIdFor(step.targetRunIndex),
415+ });
404416 pushDispatch(
405417 dispatchMessageCommand({
406418 commandId: yield* idAllocator.allocate.command({
@@ -421,11 +433,10 @@ export function materializeFixtureInput(input: { · 14 unmodified lines
421433 },
422434 }),
423435 );
424− if (
425− input.fixtureInput.steps[stepIndex + 1]?.type !== "approve_next_runtime_request" &&
426− activeRunDispatchKeys.delete(`run:${step.targetRunIndex}`)
427− ) {
428− steps.push({ type: "await", key: `run:${step.targetRunIndex}` });
436+ if (input.fixtureInput.steps[stepIndex + 1]?.type !== "approve_next_runtime_request") {
437+ if (activeRunDispatchKeys.delete(`run:${step.targetRunIndex}`)) {
438+ steps.push({ type: "await", key: `run:${step.targetRunIndex}` });
439+ }
429440 steps.push({ type: "await_thread_idle", threadId: ids.threadId });
430441 }
431442 break;