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
14import { makeCheckpointWorkspace } from "../src/orchestration-v2/testkit/ReplayFixtureWorkspace.ts";14import { makeCheckpointWorkspace } from "../src/orchestration-v2/testkit/ReplayFixtureWorkspace.ts";
15import { CLAUDE_MODEL_SELECTION } from "../src/orchestration-v2/testkit/fixtures/shared.ts";15import { CLAUDE_MODEL_SELECTION } from "../src/orchestration-v2/testkit/fixtures/shared.ts";
16import {16import {
17 MESSAGE_STEERING_INITIAL_PROMPT,
17 MULTI_TURN_FIRST_PROMPT,18 MULTI_TURN_FIRST_PROMPT,
19 MESSAGE_STEERING_STEER_PROMPT,
18 READ_ONLY_NEVER_POLICY,20 READ_ONLY_NEVER_POLICY,
19 READ_ONLY_ON_REQUEST_POLICY,21 READ_ONLY_ON_REQUEST_POLICY,
20 RESTRICTED_GRANULAR_POLICY,22 RESTRICTED_GRANULAR_POLICY,
@@ -46,6 +48,18 @@ const CLAUDE_RECORDINGS = { · 25 unmodified lines
46 queryMode: "restart",48 queryMode: "restart",
47 enableTools: true,49 enableTools: true,
48 },50 },
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 },
49 tool_call_read_only: {63 tool_call_read_only: {
50 prompts: [TOOL_CALL_READ_ONLY_PROMPT],64 prompts: [TOOL_CALL_READ_ONLY_PROMPT],
51 defaultTranscriptFile: "fixtures/tool_call_read_only/claude_transcript.ndjson",65 defaultTranscriptFile: "fixtures/tool_call_read_only/claude_transcript.ndjson",
@@ -88,15 +102,24 @@ function readArgValue(name: string): string | undefined { · 36 unmodified lines
88 return index >= 0 ? args[index + 1] : undefined;102 return index >= 0 ? args[index + 1] : undefined;
89}103}
90 104
91function selectedQueryMode(defaultMode: "streaming" | "restart"): "streaming" | "restart" {105type ClaudeRecordingQueryMode = "streaming" | "restart" | "active_steering" | "interrupt_restart";
106
107function selectedQueryMode(defaultMode: ClaudeRecordingQueryMode): ClaudeRecordingQueryMode {
92 const raw = readArgValue("--query-mode") ?? process.env.T3_CLAUDE_REPLAY_QUERY_MODE;108 const raw = readArgValue("--query-mode") ?? process.env.T3_CLAUDE_REPLAY_QUERY_MODE;
93 if (raw === undefined) {109 if (raw === undefined) {
94 return defaultMode;110 return defaultMode;
95 }111 }
96 if (raw === "streaming" || raw === "restart") {112 if (
113 raw === "streaming" ||
114 raw === "restart" ||
115 raw === "active_steering" ||
116 raw === "interrupt_restart"
117 ) {
97 return raw;118 return raw;
98 }119 }
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 );
100}123}
101 124
102const scenario = readArgValue("--scenario") ?? process.env.T3_CLAUDE_REPLAY_SCENARIO ?? "simple";125const 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
8import type * as CodexSchema from "effect-codex-app-server/schema";8import type * as CodexSchema from "effect-codex-app-server/schema";
9 9
10import {10import {
11 MESSAGE_STEERING_INITIAL_PROMPT,
11 MESSAGE_STEERING_STEER_PROMPT,12 MESSAGE_STEERING_STEER_PROMPT,
12 MULTI_TURN_FIRST_PROMPT,13 MULTI_TURN_FIRST_PROMPT,
13 MULTI_TURN_SECOND_PROMPT,14 MULTI_TURN_SECOND_PROMPT,
@@ -491,20 +492,12 @@ function scenarios(): ReadonlyArray<ReplayScenario> { · 477 unmodified lines
491 {492 {
492 name: "immediate-steer",493 name: "immediate-steer",
493 description: "Start a turn, then immediately steer the active root turn.",494 description: "Start a turn, then immediately steer the active root turn.",
494 turnDefaults: {
495 approvalPolicy: "never",
496 sandboxPolicy: workspaceWriteSandbox(),
497 },
498 steps: [495 steps: [
499 {496 {
500 type: "steeredTurn",497 type: "steeredTurn",
501 label: "steered",498 label: "steered",
502 prompt: TOOL_CALL_WRITE_PROMPT,499 prompt: MESSAGE_STEERING_INITIAL_PROMPT,
503 steer: MESSAGE_STEERING_STEER_PROMPT,500 steer: MESSAGE_STEERING_STEER_PROMPT,
504 turnOverrides: {
505 approvalPolicy: "on-request",
506 sandboxPolicy: readOnlyFullAccessSandbox(),
507 },
508 },501 },
509 ],502 ],
510 },503 },
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.testkit.ts+440−7
@@ -1,3 +1,5 @@
1import path from "node:path";
2
1import {3import {
2 query,4 query,
3 type CanUseTool,5 type CanUseTool,
@@ -376,7 +378,7 @@ function makeReplayQueryRunner(transcript: ClaudeAgentSdkReplayTranscript): Clau · 372 unmodified lines
376 378
377 if (entry.type === "runtime_exit") {379 if (entry.type === "runtime_exit") {
378 advance();380 advance();
379 if (entry.status === "success") {381 if (entry.status === "success" || entry.status === "cancelled") {
380 return;382 return;
381 }383 }
382 fail(384 fail(
@@ -572,11 +574,14 @@ export async function replayClaudeAgentSdkTranscript(input: { · 189 unmodified lines
572 );574 );
573}575}
574 576
575function serializeReplayError(error: unknown): unknown {577function serializeReplayError(error: unknown, scenario?: string): unknown {
576 return error instanceof Error578 return error instanceof Error
577 ? {579 ? {
578 name: error.name,580 name: error.name,
579 message: error.message,581 message:
582 scenario === undefined
583 ? error.message
584 : sanitizeReplayText({ text: error.message, scenario }),
580 }585 }
581 : error;586 : error;
582}587}
@@ -648,6 +653,13 @@ function sanitizedReplayCwd(scenario: string): string { · 65 unmodified lines
648 return `/tmp/claude-replay-${scenario}`;653 return `/tmp/claude-replay-${scenario}`;
649}654}
650 655
656function 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
651function sanitizeSdkMessageForReplay(input: {663function sanitizeSdkMessageForReplay(input: {
652 readonly message: SDKMessage;664 readonly message: SDKMessage;
653 readonly scenario: string;665 readonly scenario: string;
@@ -685,6 +697,14 @@ function sanitizeSdkMessageForReplay(input: { · 31 unmodified lines
685 },697 },
686 };698 };
687 }699 }
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 }
688 return message;708 return message;
689}709}
690 710
@@ -763,6 +783,81 @@ async function recordMessagesUntilTurnResult(input: { · 72 unmodified lines
763 }783 }
764}784}
765 785
786async 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
816async 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
838async 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
766async function recordClaudeStreamingQuery(input: {861async function recordClaudeStreamingQuery(input: {
767 readonly scenario: string;862 readonly scenario: string;
768 readonly prompts: ReadonlyArray<string>;863 readonly prompts: ReadonlyArray<string>;
@@ -869,7 +964,142 @@ async function recordClaudeStreamingQuery(input: { · 100 unmodified lines
869 input.entries.push({964 input.entries.push({
870 type: "runtime_exit",965 type: "runtime_exit",
871 status: "error",966 status: "error",
872 error: serializeReplayError(error),967 error: serializeReplayError(error, input.scenario),
968 });
969 throw error;
970 }
971}
972
973async 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),
873 });1103 });
874 throw error;1104 throw error;
875 }1105 }
@@ -955,20 +1185,183 @@ async function recordClaudeRestartingQueries(input: { · 79 unmodified lines
955 input.entries.push({1185 input.entries.push({
956 type: "runtime_exit",1186 type: "runtime_exit",
957 status: "error",1187 status: "error",
958 error: serializeReplayError(error),1188 error: serializeReplayError(error, input.scenario),
959 });1189 });
960 throw error;1190 throw error;
961 }1191 }
962 }1192 }
963}1193}
964 1194
1195async 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
965export async function recordClaudeAgentSdkReplayTranscript(input: {1358export async function recordClaudeAgentSdkReplayTranscript(input: {
966 readonly scenario: string;1359 readonly scenario: string;
967 readonly prompts: ReadonlyArray<string>;1360 readonly prompts: ReadonlyArray<string>;
968 readonly modelSelection: ModelSelection;1361 readonly modelSelection: ModelSelection;
969 readonly cwd: string;1362 readonly cwd: string;
970 readonly sessionId?: string;1363 readonly sessionId?: string;
971 readonly queryMode?: "streaming" | "restart";1364 readonly queryMode?: "streaming" | "restart" | "active_steering" | "interrupt_restart";
972 readonly enableTools?: boolean;1365 readonly enableTools?: boolean;
973 readonly tools?: ClaudeAgentSdkQueryTools;1366 readonly tools?: ClaudeAgentSdkQueryTools;
974 readonly permissionMode?: ClaudeAgentSdkQueryOptions["permissionMode"];1367 readonly permissionMode?: ClaudeAgentSdkQueryOptions["permissionMode"];
@@ -1010,7 +1403,30 @@ export async function recordClaudeAgentSdkReplayTranscript(input: { · 35 unmodified lines
1010 ? {}1403 ? {}
1011 : { permissionDecision: input.permissionDecision }),1404 : { permissionDecision: input.permissionDecision }),
1012 });1405 });
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") {
1014 await recordClaudeRestartingQueries({1430 await recordClaudeRestartingQueries({
1015 scenario: input.scenario,1431 scenario: input.scenario,
1016 prompts: input.prompts,1432 prompts: input.prompts,
@@ -1027,6 +1443,23 @@ export async function recordClaudeAgentSdkReplayTranscript(input: { · 10 unmodified lines
1027 ? {}1443 ? {}
1028 : { allowDangerouslySkipPermissions: input.allowDangerouslySkipPermissions }),1444 : { allowDangerouslySkipPermissions: input.allowDangerouslySkipPermissions }),
1029 });1445 });
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 });
1030 }1463 }
1031 1464
1032 return {1465 return {
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts+138−26
@@ -37,6 +37,7 @@ import * as Deferred from "effect/Deferred"; · 36 unmodified lines
37import * as Effect from "effect/Effect";37import * as Effect from "effect/Effect";
38import * as Exit from "effect/Exit";38import * as Exit from "effect/Exit";
39import * as Layer from "effect/Layer";39import * as Layer from "effect/Layer";
40import * as Option from "effect/Option";
40import * as Path from "effect/Path";41import * as Path from "effect/Path";
41import * as Queue from "effect/Queue";42import * as Queue from "effect/Queue";
42import * as Random from "effect/Random";43import * as Random from "effect/Random";
@@ -55,7 +56,7 @@ import { · 12 unmodified lines
55 ProviderAdapterResumeThreadError,56 ProviderAdapterResumeThreadError,
56 ProviderAdapterRollbackThreadError,57 ProviderAdapterRollbackThreadError,
57 ProviderAdapterRuntimeRequestResponseError,58 ProviderAdapterRuntimeRequestResponseError,
58 ProviderAdapterSteerRunUnsupportedError,59 ProviderAdapterSteerRunError,
59 ProviderAdapterTurnStartError,60 ProviderAdapterTurnStartError,
60 ProviderAdapterV2,61 ProviderAdapterV2,
61 type ProviderAdapterV2EnsureThreadInput,62 type ProviderAdapterV2EnsureThreadInput,
@@ -65,6 +66,7 @@ import { · 3 unmodified lines
65 type ProviderAdapterV2RuntimePolicy,66 type ProviderAdapterV2RuntimePolicy,
66 type ProviderAdapterV2Shape,67 type ProviderAdapterV2Shape,
67 type ProviderAdapterV2SessionRuntime,68 type ProviderAdapterV2SessionRuntime,
69 type ProviderAdapterV2SteerInput,
68 type ProviderAdapterV2TurnInput,70 type ProviderAdapterV2TurnInput,
69} from "../ProviderAdapter.ts";71} from "../ProviderAdapter.ts";
70import {72import {
@@ -97,8 +99,8 @@ export const ClaudeProviderCapabilitiesV2 = { · 26 unmodified lines
97 emitsTurnStarted: true,99 emitsTurnStarted: true,
98 emitsTurnCompleted: true,100 emitsTurnCompleted: true,
99 supportsInterrupt: true,101 supportsInterrupt: true,
100 supportsActiveSteering: false,102 supportsActiveSteering: true,
101 supportsSteeringByInterruptRestart: true,103 supportsSteeringByInterruptRestart: false,
102 supportsQueuedMessages: true,104 supportsQueuedMessages: true,
103 terminalStatusQuality: "strong",105 terminalStatusQuality: "strong",
104 },106 },
@@ -444,11 +446,6 @@ const getNativeThreadId = Effect.fnUntraced(function* ( · 339 unmodified lines
444 return nativeThreadId;446 return nativeThreadId;
445});447});
446 448
447function shouldResumeClaudeThread(turnInput: ProviderAdapterV2TurnInput): boolean {
448 const firstRunOrdinal = turnInput.providerThread.firstRunOrdinal;
449 return firstRunOrdinal !== null && firstRunOrdinal < turnInput.runOrdinal;
450}
451
452export function makeClaudeUserMessage(input: {449export function makeClaudeUserMessage(input: {
453 readonly text: string;450 readonly text: string;
454 readonly priority?: SDKUserMessage["priority"];451 readonly priority?: SDKUserMessage["priority"];
@@ -1067,6 +1064,10 @@ function terminalStatusFromResult( · 612 unmodified lines
1067 return "failed";1064 return "failed";
1068}1065}
1069 1066
1067function isClaudeActiveSteeringAbortResult(message: SDKResultMessage): boolean {
1068 return message.terminal_reason === "aborted_streaming";
1069}
1070
1070function buildAssistantArtifacts(input: {1071function buildAssistantArtifacts(input: {
1071 readonly idAllocator: IdAllocatorV2Shape;1072 readonly idAllocator: IdAllocatorV2Shape;
1072 readonly turnInput: ProviderAdapterV2TurnInput;1073 readonly turnInput: ProviderAdapterV2TurnInput;
@@ -1156,6 +1157,7 @@ interface ActiveClaudeTurnContext { · 83 unmodified lines
1156 readonly input: ProviderAdapterV2TurnInput;1157 readonly input: ProviderAdapterV2TurnInput;
1157 readonly nativeTurnId: string;1158 readonly nativeTurnId: string;
1158 readonly providerTurnId: OrchestrationV2ProviderTurn["id"];1159 readonly providerTurnId: OrchestrationV2ProviderTurn["id"];
1160 readonly providerTurnOrdinal: number;
1159 readonly startedAt: DateTime.Utc;1161 readonly startedAt: DateTime.Utc;
1160 readonly assistant: {1162 readonly assistant: {
1161 text: string;1163 text: string;
@@ -1168,6 +1170,7 @@ interface ClaudeLiveQueryContext { · 6 unmodified lines
1168 readonly nativeThreadId: string;1170 readonly nativeThreadId: string;
1169 readonly query: ClaudeAgentSdkQuerySession;1171 readonly query: ClaudeAgentSdkQuerySession;
1170 readonly queryPolicyKey: string;1172 readonly queryPolicyKey: string;
1173 readonly closed: Deferred.Deferred<void, never>;
1171 currentModel: string;1174 currentModel: string;
1172}1175}
1173 1176
@@ -1215,7 +1218,10 @@ export function makeClaudeAdapterV2( · 41 unmodified lines
1215 const events = yield* Queue.unbounded<ProviderAdapterV2Event>();1218 const events = yield* Queue.unbounded<ProviderAdapterV2Event>();
1216 const activeTurn = yield* Ref.make<ActiveClaudeTurnContext | null>(null);1219 const activeTurn = yield* Ref.make<ActiveClaudeTurnContext | null>(null);
1217 const interruptedTurns = yield* Ref.make(new Set<OrchestrationV2ProviderTurn["id"]>());1220 const interruptedTurns = yield* Ref.make(new Set<OrchestrationV2ProviderTurn["id"]>());
1221 const steeredTurns = yield* Ref.make(new Set<OrchestrationV2ProviderTurn["id"]>());
1218 const queryContext = yield* Ref.make<ClaudeLiveQueryContext | null>(null);1222 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>());
1219 const itemOrdinals = yield* Ref.make(new Map<string, number>());1225 const itemOrdinals = yield* Ref.make(new Map<string, number>());
1220 const nextItemOrdinalsByTurn = yield* Ref.make(new Map<string, number>());1226 const nextItemOrdinalsByTurn = yield* Ref.make(new Map<string, number>());
1221 const pendingRuntimeRequests = yield* Ref.make(1227 const pendingRuntimeRequests = yield* Ref.make(
@@ -1266,7 +1272,7 @@ export function makeClaudeAdapterV2( · 44 unmodified lines
1266 nativeId: input.context.nativeTurnId,1272 nativeId: input.context.nativeTurnId,
1267 strength: "weak",1273 strength: "weak",
1268 },1274 },
1269 ordinal: input.context.input.runOrdinal,1275 ordinal: input.context.providerTurnOrdinal,
1270 status: input.status,1276 status: input.status,
1271 startedAt: input.context.startedAt,1277 startedAt: input.context.startedAt,
1272 completedAt: input.completedAt,1278 completedAt: input.completedAt,
@@ -1651,7 +1657,16 @@ export function makeClaudeAdapterV2( · 378 unmodified lines
1651 }1657 }
1652 });1658 });
1653 1659
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;
1655 const context = yield* Ref.get(activeTurn);1670 const context = yield* Ref.get(activeTurn);
1656 if (context === null) {1671 if (context === null) {
1657 return;1672 return;
@@ -1710,9 +1725,19 @@ export function makeClaudeAdapterV2( · 52 unmodified lines
1710 1725
1711 if (message.type === "result") {1726 if (message.type === "result") {
1712 const completedAt = yield* DateTime.now;1727 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 });
1713 yield* finalizeActiveTurn({1738 yield* finalizeActiveTurn({
1714 context,1739 context,
1715 status: terminalStatusFromResult(message),1740 status: interrupted ? "interrupted" : terminalStatusFromResult(message),
1716 completedAt,1741 completedAt,
1717 });1742 });
1718 }1743 }
@@ -1836,7 +1861,15 @@ export function makeClaudeAdapterV2( · 117 unmodified lines
1836 yield* existing.query.close.pipe(Effect.ignore);1861 yield* existing.query.close.pipe(Effect.ignore);
1837 }1862 }
1838 1863
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 });
1840 const querySession = yield* queryRunner.open({1873 const querySession = yield* queryRunner.open({
1841 options: makeClaudeQueryOptions({1874 options: makeClaudeQueryOptions({
1842 modelSelection: turnInput.modelSelection,1875 modelSelection: turnInput.modelSelection,
@@ -1856,26 +1889,31 @@ export function makeClaudeAdapterV2( · 13 unmodified lines
1856 ...(shouldInstallClaudePermissionCallback(queryPolicy) ? { canUseTool } : {}),1889 ...(shouldInstallClaudePermissionCallback(queryPolicy) ? { canUseTool } : {}),
1857 }),1890 }),
1858 });1891 });
1892 const closed = yield* Deferred.make<void, never>();
1859 const context: ClaudeLiveQueryContext = {1893 const context: ClaudeLiveQueryContext = {
1860 nativeThreadId,1894 nativeThreadId,
1861 query: querySession,1895 query: querySession,
1862 queryPolicyKey,1896 queryPolicyKey,
1897 closed,
1863 currentModel: turnInput.modelSelection.model,1898 currentModel: turnInput.modelSelection.model,
1864 };1899 };
1865 yield* Ref.set(queryContext, context);1900 yield* Ref.set(queryContext, context);
1866 yield* querySession.messages.pipe(1901 yield* querySession.messages.pipe(
1867 Stream.runForEach(handleSdkMessage),1902 Stream.runForEach((message) => handleSdkMessage({ query: querySession, message })),
1868 Effect.exit,1903 Effect.exit,
1869 Effect.flatMap(1904 Effect.flatMap(
1870 Effect.fnUntraced(function* (exit: ClaudeQueryStreamExit) {1905 Effect.fnUntraced(function* (exit: ClaudeQueryStreamExit) {
1871 yield* Ref.update(queryContext, (current) =>1906 const ownsLiveQuery = yield* Ref.modify(queryContext, (current) =>
1872 current?.query === querySession ? null : current,1907 current?.query === querySession ? [true, null] : [false, current],
1873 );
1874 yield* finalizeActiveTurnAfterQueryExit(
1875 exit._tag === "Failure" ? exit.cause : undefined,
1876 );1908 );
1909 if (ownsLiveQuery) {
1910 yield* finalizeActiveTurnAfterQueryExit(
1911 exit._tag === "Failure" ? exit.cause : undefined,
1912 );
1913 }
1877 }),1914 }),
1878 ),1915 ),
1916 Effect.ensuring(Deferred.succeed(closed, undefined)),
1879 Effect.forkIn(sessionScope),1917 Effect.forkIn(sessionScope),
1880 );1918 );
1881 return context;1919 return context;
@@ -1885,11 +1923,21 @@ export function makeClaudeAdapterV2( · 3 unmodified lines
1885 function* (turnInput: ProviderAdapterV2TurnInput) {1923 function* (turnInput: ProviderAdapterV2TurnInput) {
1886 const startedAt = yield* DateTime.now;1924 const startedAt = yield* DateTime.now;
1887 const nativeThreadId = yield* getNativeThreadId(turnInput.providerThread);1925 const nativeThreadId = yield* getNativeThreadId(turnInput.providerThread);
1888 const nativeTurnId = `turn:${turnInput.runId}`;1926 const nativeTurnId = `turn:${turnInput.attemptId}`;
1889 const providerTurnId = idAllocator.derive.providerTurn({1927 const providerTurnId = idAllocator.derive.providerTurn({
1890 provider: CLAUDE_PROVIDER,1928 provider: CLAUDE_PROVIDER,
1891 nativeTurnId,1929 nativeTurnId,
1892 });1930 });
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 });
1893 const currentTurn = yield* Ref.get(activeTurn);1941 const currentTurn = yield* Ref.get(activeTurn);
1894 if (currentTurn !== null) {1942 if (currentTurn !== null) {
1895 return yield* new ProviderAdapterProtocolError({1943 return yield* new ProviderAdapterProtocolError({
@@ -1901,6 +1949,7 @@ export function makeClaudeAdapterV2( · 5 unmodified lines
1901 input: turnInput,1949 input: turnInput,
1902 nativeTurnId,1950 nativeTurnId,
1903 providerTurnId,1951 providerTurnId,
1952 providerTurnOrdinal,
1904 startedAt,1953 startedAt,
1905 assistant: {1954 assistant: {
1906 text: "",1955 text: "",
@@ -1947,12 +1996,42 @@ export function makeClaudeAdapterV2( · 40 unmodified lines
1947 detail: `Claude provider thread ${turnInput.providerThread.id} has no live query.`,1996 detail: `Claude provider thread ${turnInput.providerThread.id} has no live query.`,
1948 });1997 });
1949 }1998 }
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 }
1950 yield* Ref.update(interruptedTurns, (current) => {2006 yield* Ref.update(interruptedTurns, (current) => {
1951 const next = new Set(current);2007 const next = new Set(current);
1952 next.add(turnInput.providerTurnId);2008 next.add(turnInput.providerTurnId);
1953 return next;2009 return next;
1954 });2010 });
1955 yield* existing.query.interrupt;2011 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);
1956 },2035 },
1957 (effect, turnInput) =>2036 (effect, turnInput) =>
1958 effect.pipe(2037 effect.pipe(
@@ -1968,6 +2047,45 @@ export function makeClaudeAdapterV2( · 9 unmodified lines
1968 ),2047 ),
1969 );2048 );
1970 2049
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
1971 const closeSession = Effect.fnUntraced(function* () {2089 const closeSession = Effect.fnUntraced(function* () {
1972 const existing = yield* Ref.get(queryContext);2090 const existing = yield* Ref.get(queryContext);
1973 if (existing !== null) {2091 if (existing !== null) {
@@ -2040,13 +2158,7 @@ export function makeClaudeAdapterV2( · 66 unmodified lines
2040 ),2158 ),
2041 ),2159 ),
2042 startTurn,2160 startTurn,
2043 steerTurn: (turnInput) =>2161 steerTurn,
2044 Effect.fail(
2045 new ProviderAdapterSteerRunUnsupportedError({
2046 provider: CLAUDE_PROVIDER,
2047 providerThreadId: turnInput.providerThread.id,
2048 }),
2049 ),
2050 interruptTurn,2162 interruptTurn,
2051 respondToRuntimeRequest: Effect.fn("ClaudeAdapterV2.respondToRuntimeRequest")(2163 respondToRuntimeRequest: Effect.fn("ClaudeAdapterV2.respondToRuntimeRequest")(
2052 function* (requestInput) {2164 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
1099 ),1099 ),
1100 );1100 );
1101 1101
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(
1103 (candidate) => candidate.id === targetRun.activeAttemptId,1133 (candidate) => candidate.id === targetRun.activeAttemptId,
1104 );1134 );
1105 const currentRootNode = input.projection.nodes.find(1135 const currentRootNode = postInterruptProjection.nodes.find(
1106 (candidate) => candidate.id === rootNodeId,1136 (candidate) => candidate.id === rootNodeId,
1107 );1137 );
1108 const attemptOrdinal =1138 const attemptOrdinal =
1109 Math.max(1139 Math.max(
1110 0,1140 0,
1111 ...input.projection.attempts1141 ...postInterruptProjection.attempts
1112 .filter((candidate) => candidate.runId === targetRun.id)1142 .filter((candidate) => candidate.runId === targetRun.id)
1113 .map((candidate) => candidate.attemptOrdinal),1143 .map((candidate) => candidate.attemptOrdinal),
1114 ) + 1;1144 ) + 1;
apps/server/src/orchestration-v2/testkit/CodexReplayFixtures.integration.test.ts+2−8
@@ -123,16 +123,10 @@ const scenarioExpectations = { · 122 unmodified lines
123 },123 },
124 message_steering: {124 message_steering: {
125 outgoing: ["initialize", "initialized", "thread/start", "turn/start", "turn/steer"],125 outgoing: ["initialize", "initialized", "thread/start", "turn/start", "turn/steer"],
126 incoming: [126 incoming: ["turn/started", "turn/completed", "item/agentMessage/delta"],
127 "turn/started",
128 "turn/completed",
129 "item/agentMessage/delta",
130 "item/commandExecution/requestApproval",
131 "serverRequest/resolved",
132 ],
133 turnStartCount: 1,127 turnStartCount: 1,
134 turnCompletedCount: 1,128 turnCompletedCount: 1,
135 approvalRequestCount: 1,129 approvalRequestCount: 0,
136 },130 },
137 turn_interrupt: {131 turn_interrupt: {
138 outgoing: ["initialize", "initialized", "thread/start", "turn/start", "turn/interrupt"],132 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
2 OrchestrationV2Command,2 OrchestrationV2Command,
3 OrchestrationV2DomainEvent,3 OrchestrationV2DomainEvent,
4 OrchestrationV2RuntimeRequest,4 OrchestrationV2RuntimeRequest,
5 OrchestrationV2Run,
5 OrchestrationV2ShellSnapshot,6 OrchestrationV2ShellSnapshot,
6 OrchestrationV2StoredEvent,7 OrchestrationV2StoredEvent,
7 OrchestrationV2ThreadProjection,8 OrchestrationV2ThreadProjection,
@@ -37,6 +38,11 @@ export type OrchestratorV2ScenarioStep = · 29 unmodified lines
37 readonly type: "await_thread_idle";38 readonly type: "await_thread_idle";
38 readonly threadId: ThreadId;39 readonly threadId: ThreadId;
39 }40 }
41 | {
42 readonly type: "await_run_steerable";
43 readonly threadId: ThreadId;
44 readonly runId: OrchestrationV2Run["id"];
45 }
40 | {46 | {
41 readonly type: "respond_to_next_runtime_request";47 readonly type: "respond_to_next_runtime_request";
42 readonly threadId: ThreadId;48 readonly threadId: ThreadId;
@@ -208,6 +214,33 @@ export function runOrchestratorV2Scenario( · 165 unmodified lines
208 return yield* waitForThreadIdle(threadId, attemptsRemaining - 1);214 return yield* waitForThreadIdle(threadId, attemptsRemaining - 1);
209 });215 });
210 216
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
211 for (const step of scenarioSteps(scenario)) {244 for (const step of scenarioSteps(scenario)) {
212 switch (step.type) {245 switch (step.type) {
213 case "dispatch": {246 case "dispatch": {
@@ -242,6 +275,9 @@ export function runOrchestratorV2Scenario( · 28 unmodified lines
242 case "await_thread_idle":275 case "await_thread_idle":
243 yield* waitForThreadIdle(step.threadId);276 yield* waitForThreadIdle(step.threadId);
244 break;277 break;
278 case "await_run_steerable":
279 yield* waitForRunSteerable(step.threadId, step.runId);
280 break;
245 case "respond_to_next_runtime_request": {281 case "respond_to_next_runtime_request": {
246 const request = yield* waitForPendingRuntimeRequest(step.threadId);282 const request = yield* waitForPendingRuntimeRequest(step.threadId);
247 const result = yield* orchestrator.dispatch({283 const result = yield* orchestrator.dispatch({
apps/server/src/orchestration-v2/testkit/fixtures/index.ts+16−5
@@ -1,3 +1,4 @@
1import { assertClaudeMessageSteeringOutput } from "./message_steering/claude_output.ts";
1import { assertMessageSteeringOutput } from "./message_steering/codex_output.ts";2import { assertMessageSteeringOutput } from "./message_steering/codex_output.ts";
2import { messageSteeringInput } from "./message_steering/input.ts";3import { messageSteeringInput } from "./message_steering/input.ts";
3import { assertMultiTurnClaudeOutput } from "./multi_turn/claude_output.ts";4import { assertMultiTurnClaudeOutput } from "./multi_turn/claude_output.ts";
@@ -207,6 +208,12 @@ export const ORCHESTRATOR_REPLAY_FIXTURES = [ · 203 unmodified lines
207 modelSelection: CODEX_MODEL_SELECTION,208 modelSelection: CODEX_MODEL_SELECTION,
208 assertOutput: assertQueuedTurnOutput,209 assertOutput: assertQueuedTurnOutput,
209 },210 },
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 },
210 ],217 ],
211 },218 },
212 {219 {
@@ -274,9 +281,14 @@ export const ORCHESTRATOR_REPLAY_FIXTURES = [ · 61 unmodified lines
274 provider: "codex",281 provider: "codex",
275 transcriptFile: new URL("./message_steering/codex_transcript.ndjson", import.meta.url),282 transcriptFile: new URL("./message_steering/codex_transcript.ndjson", import.meta.url),
276 modelSelection: CODEX_MODEL_SELECTION,283 modelSelection: CODEX_MODEL_SELECTION,
277 runtimePolicyOverride: READ_ONLY_ON_REQUEST_POLICY,
278 assertOutput: assertMessageSteeringOutput,284 assertOutput: assertMessageSteeringOutput,
279 },285 },
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 },
280 ],292 ],
281 },293 },
282 {294 {
@@ -313,10 +325,9 @@ export const ORCHESTRATOR_REPLAY_FIXTURES = [ · 30 unmodified lines
313// `tool_call_restricted_granular/claude_transcript.ndjson`, and325// `tool_call_restricted_granular/claude_transcript.ndjson`, and
314// docs/orchestration-v2/provider-capability-system.md.326// docs/orchestration-v2/provider-capability-system.md.
315 327
316// TODO(claude-v2/control): add Claude providers to queued-turn, interrupt, and steering fixtures328// TODO(claude-v2/control): add a Claude interrupt fixture once explicit run interrupt behavior
317// once the real adapter exposes those behaviors through capability-checked V2 paths.329// is fully hardened. Cross-reference `turn_interrupt/codex_transcript.ndjson` and
318// Cross-reference `queued_turn/codex_transcript.ndjson`, `turn_interrupt/codex_transcript.ndjson`,330// docs/orchestration-v2/feature-lifecycles.md.
319// `message_steering/codex_transcript.ndjson`, and docs/orchestration-v2/feature-lifecycles.md.
320 331
321// TODO(claude-v2/context-transfer): add provider-switch handoff and return fixtures when portable332// TODO(claude-v2/context-transfer): add provider-switch handoff and return fixtures when portable
322// context handoff is implemented. Cross-reference docs/orchestration-v2/provider-switching-and-context.md333// 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 @@
1import { assert } from "@effect/vitest";
2import type { ProviderReplayTranscript } from "@t3tools/contracts";
3
4import type { OrchestratorV2ScenarioResult } from "../../OrchestratorScenario.ts";
5import {
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
19export 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
3 3
4import type { OrchestratorV2ScenarioResult } from "../../OrchestratorScenario.ts";4import type { OrchestratorV2ScenarioResult } from "../../OrchestratorScenario.ts";
5import {5import {
6 assertAllRuntimeRequestsResolved,6 assertAssistantTextIncludes,
7 assertBaseProjection,7 assertBaseProjection,
8 assertRuntimeRequestCounts,8 assertRuntimeRequestCounts,
9 assertRuntimeRequestKinds,
10 assertSemanticProjectionIntegrity,9 assertSemanticProjectionIntegrity,
11 assertTurnItemTypes,10 assertTurnItemTypes,
12 assertUserMessageInputIntents,11 assertUserMessageInputIntents,
13 assertUserMessagesInclude,12 assertUserMessagesInclude,
14 assertVisibleTurnItemsMirrorLocalTurnItems,13 assertVisibleTurnItemsMirrorLocalTurnItems,
14 MESSAGE_STEERING_INITIAL_PROMPT,
15 MESSAGE_STEERING_STEER_PROMPT,15 MESSAGE_STEERING_STEER_PROMPT,
16 projectionFor,16 projectionFor,
17 TOOL_CALL_WRITE_PROMPT,
18} from "../shared.ts";17} from "../shared.ts";
19 18
20export function assertMessageSteeringOutput(19export function assertMessageSteeringOutput(
21 result: OrchestratorV2ScenarioResult,20 result: OrchestratorV2ScenarioResult,
22 transcript: ProviderReplayTranscript,21 transcript: ProviderReplayTranscript,
23) {22) {
23 assert.equal(transcript.provider, "codex");
24 assertBaseProjection({ result, transcript, runCount: 1, runStatuses: ["completed"] });24 assertBaseProjection({ result, transcript, runCount: 1, runStatuses: ["completed"] });
25 25
26 const projection = projectionFor(result, transcript.scenario);26 const projection = projectionFor(result, transcript.scenario);
27 assertSemanticProjectionIntegrity(projection);27 assertSemanticProjectionIntegrity(projection);
28 assertVisibleTurnItemsMirrorLocalTurnItems(projection);28 assertVisibleTurnItemsMirrorLocalTurnItems(projection);
29 assertTurnItemTypes(projection, [29 assertTurnItemTypes(projection, ["user_message", "assistant_message"]);
30 "user_message",30 assertRuntimeRequestCounts(projection, { total: 0 });
31 "command_execution",31 assertUserMessagesInclude(projection, [
32 "approval_request",32 MESSAGE_STEERING_INITIAL_PROMPT,
33 "assistant_message",33 MESSAGE_STEERING_STEER_PROMPT,
34 ]);34 ]);
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]);
39 assertUserMessageInputIntents(projection, ["turn_start", "steer"]);35 assertUserMessageInputIntents(projection, ["turn_start", "steer"]);
36 assertAssistantTextIncludes(projection, "steering fixture observed");
40 assert.equal(projection.runs.length, 1, "steering must attach to the active run");37 assert.equal(projection.runs.length, 1, "steering must attach to the active run");
41 assert.equal(38 assert.equal(
42 projection.providerTurns.length,39 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."}}
2{"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"}}}}2{"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"}}}
4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}4{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
5{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}}5{"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"}}}6{"type":"emit_inbound","label":"remoteControl/status/changed","frame":{"method":"remoteControl/status/changed","params":{"status":"disabled","environmentId":null}}}
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"}}}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":"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":[]}}}}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":[]}}}}
9{"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":"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."}}}
11{"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"uidotsh","status":"starting","error":null}}}12{"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":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"codex_apps","status":"starting","error":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":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"computer-use","status":"starting","error":null}}}
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":"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}}}}
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":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","status":{"type":"active","activeFlags":[]}}}}
16{"type":"emit_inbound","label":"turn/steer","frame":{"id":4,"result":{"turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}}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}}}}
17{"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"codex_apps","status":"ready","error":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}}}
18{"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"uidotsh","status":"ready","error":null}}}21{"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"}}}22{"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"codex_apps","status":"ready","error":null}}}
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"}}}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"}}}
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"}}}}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"}}}
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"}}}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}}}}
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"}}}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"}}}
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"]}}}}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"}}}
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"}}}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"}}}
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"]}}}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"}}}
27{"type":"expect_outbound","label":"item/commandExecution/requestApproval","frame":{"id":0,"result":{"decision":"accept"}}}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"}}}
28{"type":"emit_inbound","label":"serverRequest/resolved","frame":{"method":"serverRequest/resolved","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","requestId":0}}}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"}}}
29{"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","status":{"type":"active","activeFlags":[]}}}}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"}}}
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}}}}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"}}}
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"}}}}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"}}}
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"}}}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}}}}
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"}}}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}}}}
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"}}}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"}}}
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}}}}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"}}}
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"}}}}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}}}}
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"}}}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}}}}
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"}}}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"}}}
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"}}}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"}}}
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"}}}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"}}}
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"}}}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"}}}
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"}}}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"}}}
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"}}}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"}}}
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"}}}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}}}}
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}}}}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}}}}
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"}}}}49{"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","status":{"type":"idle"}}}}
47{"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","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}}}}
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}}}}
49{"type":"runtime_exit","status":"success"}51{"type":"runtime_exit","status":"success"}
apps/server/src/orchestration-v2/testkit/fixtures/message_steering/input.ts+2−3
@@ -1,19 +1,18 @@
1import {1import {
2 MESSAGE_STEERING_INITIAL_PROMPT,
2 MESSAGE_STEERING_STEER_PROMPT,3 MESSAGE_STEERING_STEER_PROMPT,
3 TOOL_CALL_WRITE_PROMPT,
4 type OrchestratorFixtureInput,4 type OrchestratorFixtureInput,
5} from "../shared.ts";5} from "../shared.ts";
6 6
7export function messageSteeringInput(): OrchestratorFixtureInput {7export function messageSteeringInput(): OrchestratorFixtureInput {
8 return {8 return {
9 steps: [9 steps: [
10 { type: "message", text: TOOL_CALL_WRITE_PROMPT },10 { type: "message", text: MESSAGE_STEERING_INITIAL_PROMPT },
11 {11 {
12 type: "steer",12 type: "steer",
13 text: MESSAGE_STEERING_STEER_PROMPT,13 text: MESSAGE_STEERING_STEER_PROMPT,
14 targetRunIndex: 1,14 targetRunIndex: 1,
15 },15 },
16 { type: "approve_next_runtime_request" },
17 ],16 ],
18 };17 };
19}18}
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
37export 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`;37export 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`;
38export const TOOL_CALL_WRITE_PROMPT =38export const TOOL_CALL_WRITE_PROMPT =
39 "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.";39 "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.";
40export const MESSAGE_STEERING_INITIAL_PROMPT =
41 "Respond with exactly: steering fixture initial response";
40export const SUBAGENT_PROMPT =42export const SUBAGENT_PROMPT =
41 "Spawn 2 subagents, one to read package.json and one to read tsconfig.json";43 "Spawn 2 subagents, one to read package.json and one to read tsconfig.json";
42export const TURN_INTERRUPT_PROMPT =44export const TURN_INTERRUPT_PROMPT =
@@ -296,8 +298,7 @@ export function materializeFixtureInput(input: { · 253 unmodified lines
296 const nextStep = input.fixtureInput.steps[stepIndex + 1];298 const nextStep = input.fixtureInput.steps[stepIndex + 1];
297 const shouldRunInBackground =299 const shouldRunInBackground =
298 (nextStep !== undefined &&300 (nextStep !== undefined &&
299 (((nextStep.type === "steer" || nextStep.type === "interrupt") &&301 ((nextStep.type === "interrupt" && nextStep.targetRunIndex === messageIndex) ||
300 nextStep.targetRunIndex === messageIndex) ||
301 nextStep.type === "queue_message")) ||302 nextStep.type === "queue_message")) ||
302 nextStep?.type === "approve_next_runtime_request" ||303 nextStep?.type === "approve_next_runtime_request" ||
303 nextStep?.type === "answer_next_user_input_request";304 nextStep?.type === "answer_next_user_input_request";
@@ -321,7 +322,13 @@ export function materializeFixtureInput(input: { · 17 unmodified lines
321 );322 );
322 if (shouldRunInBackground) {323 if (shouldRunInBackground) {
323 activeRunDispatchKeys.add(key);324 activeRunDispatchKeys.add(key);
324 } else {325 } else if (
326 !(
327 nextStep !== undefined &&
328 nextStep.type === "steer" &&
329 nextStep.targetRunIndex === messageIndex
330 )
331 ) {
325 steps.push({ type: "await_thread_idle", threadId: ids.threadId });332 steps.push({ type: "await_thread_idle", threadId: ids.threadId });
326 }333 }
327 }334 }
@@ -401,6 +408,11 @@ export function materializeFixtureInput(input: { · 73 unmodified lines
401 break;408 break;
402 case "steer":409 case "steer":
403 messageIndex += 1;410 messageIndex += 1;
411 steps.push({
412 type: "await_run_steerable",
413 threadId: ids.threadId,
414 runId: runIdFor(step.targetRunIndex),
415 });
404 pushDispatch(416 pushDispatch(
405 dispatchMessageCommand({417 dispatchMessageCommand({
406 commandId: yield* idAllocator.allocate.command({418 commandId: yield* idAllocator.allocate.command({
@@ -421,11 +433,10 @@ export function materializeFixtureInput(input: { · 14 unmodified lines
421 },433 },
422 }),434 }),
423 );435 );
424 if (436 if (input.fixtureInput.steps[stepIndex + 1]?.type !== "approve_next_runtime_request") {
425 input.fixtureInput.steps[stepIndex + 1]?.type !== "approve_next_runtime_request" &&437 if (activeRunDispatchKeys.delete(`run:${step.targetRunIndex}`)) {
426 activeRunDispatchKeys.delete(`run:${step.targetRunIndex}`)438 steps.push({ type: "await", key: `run:${step.targetRunIndex}` });
427 ) {439 }
428 steps.push({ type: "await", key: `run:${step.targetRunIndex}` });
429 steps.push({ type: "await_thread_idle", threadId: ids.threadId });440 steps.push({ type: "await_thread_idle", threadId: ids.threadId });
430 }441 }
431 break;442 break;