- add active steering and interrupt-restart replay fixtures - update Claude adapter/orchestrator turn handling for steering - refresh replay and integration test coverage
Unified · Split
| @@ -14,7 +14,9 @@ import type { RuntimePolicyV2Override } from "../src/orchestration-v2/RuntimePol · 13 unmodified lines | ||
| 14 | 14 | import { makeCheckpointWorkspace } from "../src/orchestration-v2/testkit/ReplayFixtureWorkspace.ts"; |
| 15 | 15 | import { CLAUDE_MODEL_SELECTION } from "../src/orchestration-v2/testkit/fixtures/shared.ts"; |
| 16 | 16 | import { |
| 17 | + MESSAGE_STEERING_INITIAL_PROMPT, | |
| 17 | 18 | MULTI_TURN_FIRST_PROMPT, |
| 19 | + MESSAGE_STEERING_STEER_PROMPT, | |
| 18 | 20 | READ_ONLY_NEVER_POLICY, |
| 19 | 21 | READ_ONLY_ON_REQUEST_POLICY, |
| 20 | 22 | RESTRICTED_GRANULAR_POLICY, |
| @@ -46,6 +48,18 @@ const CLAUDE_RECORDINGS = { · 25 unmodified lines | ||
| 46 | 48 | queryMode: "restart", |
| 47 | 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 | 63 | tool_call_read_only: { |
| 50 | 64 | prompts: [TOOL_CALL_READ_ONLY_PROMPT], |
| 51 | 65 | defaultTranscriptFile: "fixtures/tool_call_read_only/claude_transcript.ndjson", |
| @@ -88,15 +102,24 @@ function readArgValue(name: string): string | undefined { · 36 unmodified lines | ||
| 88 | 102 | return index >= 0 ? args[index + 1] : undefined; |
| 89 | 103 | } |
| 90 | 104 | |
| 91 | −function selectedQueryMode(defaultMode: "streaming" | "restart"): "streaming" | "restart" { | |
| 105 | +type ClaudeRecordingQueryMode = "streaming" | "restart" | "active_steering" | "interrupt_restart"; | |
| 106 | + | |
| 107 | +function selectedQueryMode(defaultMode: ClaudeRecordingQueryMode): ClaudeRecordingQueryMode { | |
| 92 | 108 | const raw = readArgValue("--query-mode") ?? process.env.T3_CLAUDE_REPLAY_QUERY_MODE; |
| 93 | 109 | if (raw === undefined) { |
| 94 | 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 | 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 | |
| 102 | 125 | const scenario = readArgValue("--scenario") ?? process.env.T3_CLAUDE_REPLAY_SCENARIO ?? "simple"; |
| @@ -8,6 +8,7 @@ import * as CodexClient from "effect-codex-app-server/client"; · 7 unmodified lines | ||
| 8 | 8 | import type * as CodexSchema from "effect-codex-app-server/schema"; |
| 9 | 9 | |
| 10 | 10 | import { |
| 11 | + MESSAGE_STEERING_INITIAL_PROMPT, | |
| 11 | 12 | MESSAGE_STEERING_STEER_PROMPT, |
| 12 | 13 | MULTI_TURN_FIRST_PROMPT, |
| 13 | 14 | MULTI_TURN_SECOND_PROMPT, |
| @@ -491,20 +492,12 @@ function scenarios(): ReadonlyArray<ReplayScenario> { · 477 unmodified lines | ||
| 491 | 492 | { |
| 492 | 493 | name: "immediate-steer", |
| 493 | 494 | description: "Start a turn, then immediately steer the active root turn.", |
| 494 | − turnDefaults: { | |
| 495 | − approvalPolicy: "never", | |
| 496 | − sandboxPolicy: workspaceWriteSandbox(), | |
| 497 | − }, | |
| 498 | 495 | steps: [ |
| 499 | 496 | { |
| 500 | 497 | type: "steeredTurn", |
| 501 | 498 | label: "steered", |
| 502 | − prompt: TOOL_CALL_WRITE_PROMPT, | |
| 499 | + prompt: MESSAGE_STEERING_INITIAL_PROMPT, | |
| 503 | 500 | steer: MESSAGE_STEERING_STEER_PROMPT, |
| 504 | − turnOverrides: { | |
| 505 | − approvalPolicy: "on-request", | |
| 506 | − sandboxPolicy: readOnlyFullAccessSandbox(), | |
| 507 | − }, | |
| 508 | 501 | }, |
| 509 | 502 | ], |
| 510 | 503 | }, |
| @@ -1,3 +1,5 @@ | ||
| 1 | +import path from "node:path"; | |
| 2 | + | |
| 1 | 3 | import { |
| 2 | 4 | query, |
| 3 | 5 | type CanUseTool, |
| @@ -376,7 +378,7 @@ function makeReplayQueryRunner(transcript: ClaudeAgentSdkReplayTranscript): Clau · 372 unmodified lines | ||
| 376 | 378 | |
| 377 | 379 | if (entry.type === "runtime_exit") { |
| 378 | 380 | advance(); |
| 379 | − if (entry.status === "success") { | |
| 381 | + if (entry.status === "success" || entry.status === "cancelled") { | |
| 380 | 382 | return; |
| 381 | 383 | } |
| 382 | 384 | fail( |
| @@ -572,11 +574,14 @@ export async function replayClaudeAgentSdkTranscript(input: { · 189 unmodified lines | ||
| 572 | 574 | ); |
| 573 | 575 | } |
| 574 | 576 | |
| 575 | −function serializeReplayError(error: unknown): unknown { | |
| 577 | +function serializeReplayError(error: unknown, scenario?: string): unknown { | |
| 576 | 578 | return error instanceof Error |
| 577 | 579 | ? { |
| 578 | 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 | 586 | : error; |
| 582 | 587 | } |
| @@ -648,6 +653,13 @@ function sanitizedReplayCwd(scenario: string): string { · 65 unmodified lines | ||
| 648 | 653 | return `/tmp/claude-replay-${scenario}`; |
| 649 | 654 | } |
| 650 | 655 | |
| 656 | +function sanitizeReplayText(input: { readonly text: string; readonly scenario: string }): string { | |
| 657 | + const sanitizedCwd = sanitizedReplayCwd(input.scenario); | |
| 658 | + return [path.resolve(process.cwd(), "../.."), process.cwd()] | |
| 659 | + .toSorted((left, right) => right.length - left.length) | |
| 660 | + .reduce((text, localPath) => text.replaceAll(localPath, sanitizedCwd), input.text); | |
| 661 | +} | |
| 662 | + | |
| 651 | 663 | function sanitizeSdkMessageForReplay(input: { |
| 652 | 664 | readonly message: SDKMessage; |
| 653 | 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 | 708 | return message; |
| 689 | 709 | } |
| 690 | 710 | |
| @@ -763,6 +783,81 @@ async function recordMessagesUntilTurnResult(input: { · 72 unmodified lines | ||
| 763 | 783 | } |
| 764 | 784 | } |
| 765 | 785 | |
| 786 | +async function recordMessagesUntilTurnResults(input: { | |
| 787 | + readonly iterator: AsyncIterator<SDKMessage>; | |
| 788 | + readonly entries: Array<ProviderReplayEntry>; | |
| 789 | + readonly scenario: string; | |
| 790 | + readonly resultCount: number; | |
| 791 | +}): Promise<boolean> { | |
| 792 | + let seenResults = 0; | |
| 793 | + while (true) { | |
| 794 | + const next = await input.iterator.next(); | |
| 795 | + if (next.done === true) { | |
| 796 | + return false; | |
| 797 | + } | |
| 798 | + const replayMessage = sanitizeSdkMessageForReplay({ | |
| 799 | + message: next.value, | |
| 800 | + scenario: input.scenario, | |
| 801 | + }); | |
| 802 | + input.entries.push({ | |
| 803 | + type: "emit_inbound", | |
| 804 | + label: replayMessage.type, | |
| 805 | + frame: replayMessage, | |
| 806 | + }); | |
| 807 | + if (replayMessage.type === "result") { | |
| 808 | + seenResults += 1; | |
| 809 | + if (seenResults >= input.resultCount) { | |
| 810 | + return true; | |
| 811 | + } | |
| 812 | + } | |
| 813 | + } | |
| 814 | +} | |
| 815 | + | |
| 816 | +async function recordMessagesUntilIteratorDone(input: { | |
| 817 | + readonly iterator: AsyncIterator<SDKMessage>; | |
| 818 | + readonly entries: Array<ProviderReplayEntry>; | |
| 819 | + readonly scenario: string; | |
| 820 | +}): Promise<void> { | |
| 821 | + while (true) { | |
| 822 | + const next = await input.iterator.next(); | |
| 823 | + if (next.done === true) { | |
| 824 | + return; | |
| 825 | + } | |
| 826 | + const replayMessage = sanitizeSdkMessageForReplay({ | |
| 827 | + message: next.value, | |
| 828 | + scenario: input.scenario, | |
| 829 | + }); | |
| 830 | + input.entries.push({ | |
| 831 | + type: "emit_inbound", | |
| 832 | + label: replayMessage.type, | |
| 833 | + frame: replayMessage, | |
| 834 | + }); | |
| 835 | + } | |
| 836 | +} | |
| 837 | + | |
| 838 | +async function recordFirstInboundMessage(input: { | |
| 839 | + readonly iterator: AsyncIterator<SDKMessage>; | |
| 840 | + readonly entries: Array<ProviderReplayEntry>; | |
| 841 | + readonly scenario: string; | |
| 842 | +}): Promise<void> { | |
| 843 | + const next = await input.iterator.next(); | |
| 844 | + if (next.done === true) { | |
| 845 | + throw new Error(`Claude query ended before ${input.scenario} could be interrupted.`); | |
| 846 | + } | |
| 847 | + const replayMessage = sanitizeSdkMessageForReplay({ | |
| 848 | + message: next.value, | |
| 849 | + scenario: input.scenario, | |
| 850 | + }); | |
| 851 | + input.entries.push({ | |
| 852 | + type: "emit_inbound", | |
| 853 | + label: replayMessage.type, | |
| 854 | + frame: replayMessage, | |
| 855 | + }); | |
| 856 | + if (replayMessage.type === "result") { | |
| 857 | + throw new Error(`Claude query completed before ${input.scenario} could be interrupted.`); | |
| 858 | + } | |
| 859 | +} | |
| 860 | + | |
| 766 | 861 | async function recordClaudeStreamingQuery(input: { |
| 767 | 862 | readonly scenario: string; |
| 768 | 863 | readonly prompts: ReadonlyArray<string>; |
| @@ -869,7 +964,142 @@ async function recordClaudeStreamingQuery(input: { · 100 unmodified lines | ||
| 869 | 964 | input.entries.push({ |
| 870 | 965 | type: "runtime_exit", |
| 871 | 966 | status: "error", |
| 872 | − error: serializeReplayError(error), | |
| 967 | + error: serializeReplayError(error, input.scenario), | |
| 968 | + }); | |
| 969 | + throw error; | |
| 970 | + } | |
| 971 | +} | |
| 972 | + | |
| 973 | +async function recordClaudeActiveSteeringQuery(input: { | |
| 974 | + readonly scenario: string; | |
| 975 | + readonly prompts: ReadonlyArray<string>; | |
| 976 | + readonly modelSelection: ModelSelection; | |
| 977 | + readonly cwd: string; | |
| 978 | + readonly sessionId: string; | |
| 979 | + readonly entries: Array<ProviderReplayEntry>; | |
| 980 | + readonly enableTools?: boolean; | |
| 981 | + readonly tools?: ClaudeAgentSdkQueryTools; | |
| 982 | + readonly permissionMode?: ClaudeAgentSdkQueryOptions["permissionMode"]; | |
| 983 | + readonly allowedTools?: ReadonlyArray<string>; | |
| 984 | + readonly disallowedTools?: ReadonlyArray<string>; | |
| 985 | + readonly allowDangerouslySkipPermissions?: boolean; | |
| 986 | + readonly enablePermissionCallback?: boolean; | |
| 987 | + readonly permissionDecision?: ProviderApprovalDecision; | |
| 988 | +}): Promise<void> { | |
| 989 | + if (input.prompts.length < 2) { | |
| 990 | + throw new Error("Claude active steering replay recording requires at least two prompts."); | |
| 991 | + } | |
| 992 | + | |
| 993 | + const promptQueue = new RecordingPromptQueue(); | |
| 994 | + const offeredPrompts = new Set<number>(); | |
| 995 | + const offerPrompt = (index: number, priority?: SDKUserMessage["priority"]) => { | |
| 996 | + const message = makeClaudeUserMessage({ | |
| 997 | + text: input.prompts[index]!, | |
| 998 | + ...(priority === undefined ? {} : { priority }), | |
| 999 | + }); | |
| 1000 | + input.entries.push({ | |
| 1001 | + type: "expect_outbound", | |
| 1002 | + label: `prompt.offer:${index + 1}`, | |
| 1003 | + frame: makeClaudePromptOfferFrame(message), | |
| 1004 | + }); | |
| 1005 | + promptQueue.offer(message); | |
| 1006 | + offeredPrompts.add(index); | |
| 1007 | + }; | |
| 1008 | + const offerSteeringPrompts = () => { | |
| 1009 | + for (let index = 1; index < input.prompts.length; index += 1) { | |
| 1010 | + if (!offeredPrompts.has(index)) { | |
| 1011 | + offerPrompt(index, "now"); | |
| 1012 | + } | |
| 1013 | + } | |
| 1014 | + }; | |
| 1015 | + | |
| 1016 | + const canUseTool: CanUseTool | undefined = | |
| 1017 | + input.enablePermissionCallback === true | |
| 1018 | + ? async (toolName, toolInput, callbackOptions) => { | |
| 1019 | + const requestFrame = permissionRequestFrame({ | |
| 1020 | + toolName, | |
| 1021 | + toolInput, | |
| 1022 | + callbackOptions, | |
| 1023 | + }); | |
| 1024 | + input.entries.push({ | |
| 1025 | + type: "emit_inbound", | |
| 1026 | + label: `permission.request:${toolName}`, | |
| 1027 | + frame: requestFrame, | |
| 1028 | + }); | |
| 1029 | + const result = permissionResultForRecording({ | |
| 1030 | + decision: input.permissionDecision ?? "accept", | |
| 1031 | + toolInput, | |
| 1032 | + toolUseID: callbackOptions.toolUseID, | |
| 1033 | + ...(callbackOptions.suggestions === undefined | |
| 1034 | + ? {} | |
| 1035 | + : { suggestions: callbackOptions.suggestions }), | |
| 1036 | + }); | |
| 1037 | + input.entries.push({ | |
| 1038 | + type: "expect_outbound", | |
| 1039 | + label: `permission.response:${toolName}`, | |
| 1040 | + frame: makeClaudePermissionResponseFrame(result), | |
| 1041 | + }); | |
| 1042 | + return result; | |
| 1043 | + } | |
| 1044 | + : undefined; | |
| 1045 | + const options = makeClaudeQueryOptions({ | |
| 1046 | + modelSelection: input.modelSelection, | |
| 1047 | + nativeThreadId: input.sessionId, | |
| 1048 | + resume: false, | |
| 1049 | + cwd: input.cwd, | |
| 1050 | + ...(input.enableTools === true | |
| 1051 | + ? { | |
| 1052 | + tools: input.tools ?? { type: "preset", preset: "claude_code" }, | |
| 1053 | + permissionMode: input.permissionMode ?? "default", | |
| 1054 | + ...(input.allowedTools === undefined ? {} : { allowedTools: input.allowedTools }), | |
| 1055 | + ...(input.disallowedTools === undefined | |
| 1056 | + ? {} | |
| 1057 | + : { disallowedTools: input.disallowedTools }), | |
| 1058 | + ...(input.allowDangerouslySkipPermissions === true | |
| 1059 | + ? { allowDangerouslySkipPermissions: true } | |
| 1060 | + : {}), | |
| 1061 | + ...(canUseTool === undefined ? {} : { canUseTool }), | |
| 1062 | + } | |
| 1063 | + : {}), | |
| 1064 | + }); | |
| 1065 | + input.entries.push({ | |
| 1066 | + type: "expect_outbound", | |
| 1067 | + label: "query.open", | |
| 1068 | + frame: makeClaudeQueryOpenFrame({ options }), | |
| 1069 | + }); | |
| 1070 | + const queryRuntime = query({ | |
| 1071 | + prompt: promptQueue, | |
| 1072 | + options, | |
| 1073 | + }); | |
| 1074 | + const iterator = queryRuntime[Symbol.asyncIterator](); | |
| 1075 | + try { | |
| 1076 | + offerPrompt(0); | |
| 1077 | + offerSteeringPrompts(); | |
| 1078 | + const completed = await recordMessagesUntilTurnResults({ | |
| 1079 | + iterator, | |
| 1080 | + entries: input.entries, | |
| 1081 | + scenario: input.scenario, | |
| 1082 | + resultCount: input.prompts.length, | |
| 1083 | + }); | |
| 1084 | + if (!completed) { | |
| 1085 | + throw new Error("Claude active steering query ended before the turn completed."); | |
| 1086 | + } | |
| 1087 | + if (offeredPrompts.size < input.prompts.length) { | |
| 1088 | + throw new Error("Claude active steering prompts were not all offered before completion."); | |
| 1089 | + } | |
| 1090 | + promptQueue.close(); | |
| 1091 | + queryRuntime.close(); | |
| 1092 | + input.entries.push({ | |
| 1093 | + type: "runtime_exit", | |
| 1094 | + status: "success", | |
| 1095 | + }); | |
| 1096 | + } catch (error) { | |
| 1097 | + promptQueue.close(); | |
| 1098 | + queryRuntime.close(); | |
| 1099 | + input.entries.push({ | |
| 1100 | + type: "runtime_exit", | |
| 1101 | + status: "error", | |
| 1102 | + error: serializeReplayError(error, input.scenario), | |
| 873 | 1103 | }); |
| 874 | 1104 | throw error; |
| 875 | 1105 | } |
| @@ -955,20 +1185,183 @@ async function recordClaudeRestartingQueries(input: { · 79 unmodified lines | ||
| 955 | 1185 | input.entries.push({ |
| 956 | 1186 | type: "runtime_exit", |
| 957 | 1187 | status: "error", |
| 958 | − error: serializeReplayError(error), | |
| 1188 | + error: serializeReplayError(error, input.scenario), | |
| 959 | 1189 | }); |
| 960 | 1190 | throw error; |
| 961 | 1191 | } |
| 962 | 1192 | } |
| 963 | 1193 | } |
| 964 | 1194 | |
| 1195 | +async function recordClaudeInterruptRestartQuery(input: { | |
| 1196 | + readonly scenario: string; | |
| 1197 | + readonly prompts: ReadonlyArray<string>; | |
| 1198 | + readonly modelSelection: ModelSelection; | |
| 1199 | + readonly cwd: string; | |
| 1200 | + readonly sessionId: string; | |
| 1201 | + readonly entries: Array<ProviderReplayEntry>; | |
| 1202 | + readonly enableTools?: boolean; | |
| 1203 | + readonly tools?: ClaudeAgentSdkQueryTools; | |
| 1204 | + readonly permissionMode?: ClaudeAgentSdkQueryOptions["permissionMode"]; | |
| 1205 | + readonly allowedTools?: ReadonlyArray<string>; | |
| 1206 | + readonly disallowedTools?: ReadonlyArray<string>; | |
| 1207 | + readonly allowDangerouslySkipPermissions?: boolean; | |
| 1208 | +}): Promise<void> { | |
| 1209 | + if (input.prompts.length !== 2) { | |
| 1210 | + throw new Error( | |
| 1211 | + `Claude interrupt-restart replay scenario ${input.scenario} requires exactly two prompts.`, | |
| 1212 | + ); | |
| 1213 | + } | |
| 1214 | + | |
| 1215 | + const firstPromptQueue = new RecordingPromptQueue(); | |
| 1216 | + const firstOptions = makeClaudeQueryOptions({ | |
| 1217 | + modelSelection: input.modelSelection, | |
| 1218 | + nativeThreadId: input.sessionId, | |
| 1219 | + resume: false, | |
| 1220 | + cwd: input.cwd, | |
| 1221 | + ...(input.enableTools === true | |
| 1222 | + ? { | |
| 1223 | + tools: input.tools ?? { type: "preset", preset: "claude_code" }, | |
| 1224 | + permissionMode: input.permissionMode ?? "default", | |
| 1225 | + ...(input.allowedTools === undefined ? {} : { allowedTools: input.allowedTools }), | |
| 1226 | + ...(input.disallowedTools === undefined | |
| 1227 | + ? {} | |
| 1228 | + : { disallowedTools: input.disallowedTools }), | |
| 1229 | + ...(input.allowDangerouslySkipPermissions === true | |
| 1230 | + ? { allowDangerouslySkipPermissions: true } | |
| 1231 | + : {}), | |
| 1232 | + } | |
| 1233 | + : {}), | |
| 1234 | + }); | |
| 1235 | + input.entries.push({ | |
| 1236 | + type: "expect_outbound", | |
| 1237 | + label: "query.open:1", | |
| 1238 | + frame: makeClaudeQueryOpenFrame({ options: firstOptions }), | |
| 1239 | + }); | |
| 1240 | + const firstRuntime = query({ | |
| 1241 | + prompt: firstPromptQueue, | |
| 1242 | + options: firstOptions, | |
| 1243 | + }); | |
| 1244 | + const firstIterator = firstRuntime[Symbol.asyncIterator](); | |
| 1245 | + const firstMessage = makeClaudeUserMessage({ text: input.prompts[0]! }); | |
| 1246 | + input.entries.push({ | |
| 1247 | + type: "expect_outbound", | |
| 1248 | + label: "prompt.offer:1", | |
| 1249 | + frame: makeClaudePromptOfferFrame(firstMessage), | |
| 1250 | + }); | |
| 1251 | + firstPromptQueue.offer(firstMessage); | |
| 1252 | + await recordFirstInboundMessage({ | |
| 1253 | + iterator: firstIterator, | |
| 1254 | + entries: input.entries, | |
| 1255 | + scenario: input.scenario, | |
| 1256 | + }); | |
| 1257 | + input.entries.push({ | |
| 1258 | + type: "expect_outbound", | |
| 1259 | + label: "query.interrupt:1", | |
| 1260 | + frame: { type: "query.interrupt" }, | |
| 1261 | + }); | |
| 1262 | + try { | |
| 1263 | + let cancelledError: unknown; | |
| 1264 | + try { | |
| 1265 | + await firstRuntime.interrupt(); | |
| 1266 | + } catch (error) { | |
| 1267 | + cancelledError = error; | |
| 1268 | + } | |
| 1269 | + firstPromptQueue.close(); | |
| 1270 | + try { | |
| 1271 | + await recordMessagesUntilIteratorDone({ | |
| 1272 | + iterator: firstIterator, | |
| 1273 | + entries: input.entries, | |
| 1274 | + scenario: input.scenario, | |
| 1275 | + }); | |
| 1276 | + } catch (error) { | |
| 1277 | + cancelledError = error; | |
| 1278 | + } | |
| 1279 | + firstRuntime.close(); | |
| 1280 | + input.entries.push({ | |
| 1281 | + type: "runtime_exit", | |
| 1282 | + status: cancelledError === undefined ? "success" : "cancelled", | |
| 1283 | + ...(cancelledError === undefined | |
| 1284 | + ? {} | |
| 1285 | + : { error: serializeReplayError(cancelledError, input.scenario) }), | |
| 1286 | + }); | |
| 1287 | + } catch (error) { | |
| 1288 | + firstPromptQueue.close(); | |
| 1289 | + input.entries.push({ | |
| 1290 | + type: "runtime_exit", | |
| 1291 | + status: "error", | |
| 1292 | + error: serializeReplayError(error, input.scenario), | |
| 1293 | + }); | |
| 1294 | + throw error; | |
| 1295 | + } | |
| 1296 | + | |
| 1297 | + const secondPromptQueue = new RecordingPromptQueue(); | |
| 1298 | + const secondOptions = makeClaudeQueryOptions({ | |
| 1299 | + modelSelection: input.modelSelection, | |
| 1300 | + nativeThreadId: input.sessionId, | |
| 1301 | + resume: true, | |
| 1302 | + cwd: input.cwd, | |
| 1303 | + ...(input.enableTools === true | |
| 1304 | + ? { | |
| 1305 | + tools: input.tools ?? { type: "preset", preset: "claude_code" }, | |
| 1306 | + permissionMode: input.permissionMode ?? "default", | |
| 1307 | + ...(input.allowedTools === undefined ? {} : { allowedTools: input.allowedTools }), | |
| 1308 | + ...(input.disallowedTools === undefined | |
| 1309 | + ? {} | |
| 1310 | + : { disallowedTools: input.disallowedTools }), | |
| 1311 | + ...(input.allowDangerouslySkipPermissions === true | |
| 1312 | + ? { allowDangerouslySkipPermissions: true } | |
| 1313 | + : {}), | |
| 1314 | + } | |
| 1315 | + : {}), | |
| 1316 | + }); | |
| 1317 | + input.entries.push({ | |
| 1318 | + type: "expect_outbound", | |
| 1319 | + label: "query.open:2", | |
| 1320 | + frame: makeClaudeQueryOpenFrame({ options: secondOptions }), | |
| 1321 | + }); | |
| 1322 | + const secondMessage = makeClaudeUserMessage({ text: input.prompts[1]! }); | |
| 1323 | + input.entries.push({ | |
| 1324 | + type: "expect_outbound", | |
| 1325 | + label: "prompt.offer:2", | |
| 1326 | + frame: makeClaudePromptOfferFrame(secondMessage), | |
| 1327 | + }); | |
| 1328 | + | |
| 1329 | + try { | |
| 1330 | + const secondRuntime = query({ | |
| 1331 | + prompt: secondPromptQueue, | |
| 1332 | + options: secondOptions, | |
| 1333 | + }); | |
| 1334 | + secondPromptQueue.offer(secondMessage); | |
| 1335 | + secondPromptQueue.close(); | |
| 1336 | + const secondIterator = secondRuntime[Symbol.asyncIterator](); | |
| 1337 | + await recordMessagesUntilIteratorDone({ | |
| 1338 | + iterator: secondIterator, | |
| 1339 | + entries: input.entries, | |
| 1340 | + scenario: input.scenario, | |
| 1341 | + }); | |
| 1342 | + secondRuntime.close(); | |
| 1343 | + input.entries.push({ | |
| 1344 | + type: "runtime_exit", | |
| 1345 | + status: "success", | |
| 1346 | + }); | |
| 1347 | + } catch (error) { | |
| 1348 | + secondPromptQueue.close(); | |
| 1349 | + input.entries.push({ | |
| 1350 | + type: "runtime_exit", | |
| 1351 | + status: "error", | |
| 1352 | + error: serializeReplayError(error, input.scenario), | |
| 1353 | + }); | |
| 1354 | + throw error; | |
| 1355 | + } | |
| 1356 | +} | |
| 1357 | + | |
| 965 | 1358 | export async function recordClaudeAgentSdkReplayTranscript(input: { |
| 966 | 1359 | readonly scenario: string; |
| 967 | 1360 | readonly prompts: ReadonlyArray<string>; |
| 968 | 1361 | readonly modelSelection: ModelSelection; |
| 969 | 1362 | readonly cwd: string; |
| 970 | 1363 | readonly sessionId?: string; |
| 971 | − readonly queryMode?: "streaming" | "restart"; | |
| 1364 | + readonly queryMode?: "streaming" | "restart" | "active_steering" | "interrupt_restart"; | |
| 972 | 1365 | readonly enableTools?: boolean; |
| 973 | 1366 | readonly tools?: ClaudeAgentSdkQueryTools; |
| 974 | 1367 | readonly permissionMode?: ClaudeAgentSdkQueryOptions["permissionMode"]; |
| @@ -1010,7 +1403,30 @@ export async function recordClaudeAgentSdkReplayTranscript(input: { · 35 unmodified lines | ||
| 1010 | 1403 | ? {} |
| 1011 | 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 | 1430 | await recordClaudeRestartingQueries({ |
| 1015 | 1431 | scenario: input.scenario, |
| 1016 | 1432 | prompts: input.prompts, |
| @@ -1027,6 +1443,23 @@ export async function recordClaudeAgentSdkReplayTranscript(input: { · 10 unmodified lines | ||
| 1027 | 1443 | ? {} |
| 1028 | 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 | 1465 | return { |
| @@ -37,6 +37,7 @@ import * as Deferred from "effect/Deferred"; · 36 unmodified lines | ||
| 37 | 37 | import * as Effect from "effect/Effect"; |
| 38 | 38 | import * as Exit from "effect/Exit"; |
| 39 | 39 | import * as Layer from "effect/Layer"; |
| 40 | +import * as Option from "effect/Option"; | |
| 40 | 41 | import * as Path from "effect/Path"; |
| 41 | 42 | import * as Queue from "effect/Queue"; |
| 42 | 43 | import * as Random from "effect/Random"; |
| @@ -55,7 +56,7 @@ import { · 12 unmodified lines | ||
| 55 | 56 | ProviderAdapterResumeThreadError, |
| 56 | 57 | ProviderAdapterRollbackThreadError, |
| 57 | 58 | ProviderAdapterRuntimeRequestResponseError, |
| 58 | − ProviderAdapterSteerRunUnsupportedError, | |
| 59 | + ProviderAdapterSteerRunError, | |
| 59 | 60 | ProviderAdapterTurnStartError, |
| 60 | 61 | ProviderAdapterV2, |
| 61 | 62 | type ProviderAdapterV2EnsureThreadInput, |
| @@ -65,6 +66,7 @@ import { · 3 unmodified lines | ||
| 65 | 66 | type ProviderAdapterV2RuntimePolicy, |
| 66 | 67 | type ProviderAdapterV2Shape, |
| 67 | 68 | type ProviderAdapterV2SessionRuntime, |
| 69 | + type ProviderAdapterV2SteerInput, | |
| 68 | 70 | type ProviderAdapterV2TurnInput, |
| 69 | 71 | } from "../ProviderAdapter.ts"; |
| 70 | 72 | import { |
| @@ -97,8 +99,8 @@ export const ClaudeProviderCapabilitiesV2 = { · 26 unmodified lines | ||
| 97 | 99 | emitsTurnStarted: true, |
| 98 | 100 | emitsTurnCompleted: true, |
| 99 | 101 | supportsInterrupt: true, |
| 100 | − supportsActiveSteering: false, | |
| 101 | − supportsSteeringByInterruptRestart: true, | |
| 102 | + supportsActiveSteering: true, | |
| 103 | + supportsSteeringByInterruptRestart: false, | |
| 102 | 104 | supportsQueuedMessages: true, |
| 103 | 105 | terminalStatusQuality: "strong", |
| 104 | 106 | }, |
| @@ -444,11 +446,6 @@ const getNativeThreadId = Effect.fnUntraced(function* ( · 339 unmodified lines | ||
| 444 | 446 | return nativeThreadId; |
| 445 | 447 | }); |
| 446 | 448 | |
| 447 | −function shouldResumeClaudeThread(turnInput: ProviderAdapterV2TurnInput): boolean { | |
| 448 | − const firstRunOrdinal = turnInput.providerThread.firstRunOrdinal; | |
| 449 | − return firstRunOrdinal !== null && firstRunOrdinal < turnInput.runOrdinal; | |
| 450 | −} | |
| 451 | − | |
| 452 | 449 | export function makeClaudeUserMessage(input: { |
| 453 | 450 | readonly text: string; |
| 454 | 451 | readonly priority?: SDKUserMessage["priority"]; |
| @@ -1067,6 +1064,10 @@ function terminalStatusFromResult( · 612 unmodified lines | ||
| 1067 | 1064 | return "failed"; |
| 1068 | 1065 | } |
| 1069 | 1066 | |
| 1067 | +function isClaudeActiveSteeringAbortResult(message: SDKResultMessage): boolean { | |
| 1068 | + return message.terminal_reason === "aborted_streaming"; | |
| 1069 | +} | |
| 1070 | + | |
| 1070 | 1071 | function buildAssistantArtifacts(input: { |
| 1071 | 1072 | readonly idAllocator: IdAllocatorV2Shape; |
| 1072 | 1073 | readonly turnInput: ProviderAdapterV2TurnInput; |
| @@ -1156,6 +1157,7 @@ interface ActiveClaudeTurnContext { · 83 unmodified lines | ||
| 1156 | 1157 | readonly input: ProviderAdapterV2TurnInput; |
| 1157 | 1158 | readonly nativeTurnId: string; |
| 1158 | 1159 | readonly providerTurnId: OrchestrationV2ProviderTurn["id"]; |
| 1160 | + readonly providerTurnOrdinal: number; | |
| 1159 | 1161 | readonly startedAt: DateTime.Utc; |
| 1160 | 1162 | readonly assistant: { |
| 1161 | 1163 | text: string; |
| @@ -1168,6 +1170,7 @@ interface ClaudeLiveQueryContext { · 6 unmodified lines | ||
| 1168 | 1170 | readonly nativeThreadId: string; |
| 1169 | 1171 | readonly query: ClaudeAgentSdkQuerySession; |
| 1170 | 1172 | readonly queryPolicyKey: string; |
| 1173 | + readonly closed: Deferred.Deferred<void, never>; | |
| 1171 | 1174 | currentModel: string; |
| 1172 | 1175 | } |
| 1173 | 1176 | |
| @@ -1215,7 +1218,10 @@ export function makeClaudeAdapterV2( · 41 unmodified lines | ||
| 1215 | 1218 | const events = yield* Queue.unbounded<ProviderAdapterV2Event>(); |
| 1216 | 1219 | const activeTurn = yield* Ref.make<ActiveClaudeTurnContext | null>(null); |
| 1217 | 1220 | const interruptedTurns = yield* Ref.make(new Set<OrchestrationV2ProviderTurn["id"]>()); |
| 1221 | + const steeredTurns = yield* Ref.make(new Set<OrchestrationV2ProviderTurn["id"]>()); | |
| 1218 | 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 | 1225 | const itemOrdinals = yield* Ref.make(new Map<string, number>()); |
| 1220 | 1226 | const nextItemOrdinalsByTurn = yield* Ref.make(new Map<string, number>()); |
| 1221 | 1227 | const pendingRuntimeRequests = yield* Ref.make( |
| @@ -1266,7 +1272,7 @@ export function makeClaudeAdapterV2( · 44 unmodified lines | ||
| 1266 | 1272 | nativeId: input.context.nativeTurnId, |
| 1267 | 1273 | strength: "weak", |
| 1268 | 1274 | }, |
| 1269 | − ordinal: input.context.input.runOrdinal, | |
| 1275 | + ordinal: input.context.providerTurnOrdinal, | |
| 1270 | 1276 | status: input.status, |
| 1271 | 1277 | startedAt: input.context.startedAt, |
| 1272 | 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 | 1670 | const context = yield* Ref.get(activeTurn); |
| 1656 | 1671 | if (context === null) { |
| 1657 | 1672 | return; |
| @@ -1710,9 +1725,19 @@ export function makeClaudeAdapterV2( · 52 unmodified lines | ||
| 1710 | 1725 | |
| 1711 | 1726 | if (message.type === "result") { |
| 1712 | 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 | 1738 | yield* finalizeActiveTurn({ |
| 1714 | 1739 | context, |
| 1715 | − status: terminalStatusFromResult(message), | |
| 1740 | + status: interrupted ? "interrupted" : terminalStatusFromResult(message), | |
| 1716 | 1741 | completedAt, |
| 1717 | 1742 | }); |
| 1718 | 1743 | } |
| @@ -1836,7 +1861,15 @@ export function makeClaudeAdapterV2( · 117 unmodified lines | ||
| 1836 | 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 | 1873 | const querySession = yield* queryRunner.open({ |
| 1841 | 1874 | options: makeClaudeQueryOptions({ |
| 1842 | 1875 | modelSelection: turnInput.modelSelection, |
| @@ -1856,26 +1889,31 @@ export function makeClaudeAdapterV2( · 13 unmodified lines | ||
| 1856 | 1889 | ...(shouldInstallClaudePermissionCallback(queryPolicy) ? { canUseTool } : {}), |
| 1857 | 1890 | }), |
| 1858 | 1891 | }); |
| 1892 | + const closed = yield* Deferred.make<void, never>(); | |
| 1859 | 1893 | const context: ClaudeLiveQueryContext = { |
| 1860 | 1894 | nativeThreadId, |
| 1861 | 1895 | query: querySession, |
| 1862 | 1896 | queryPolicyKey, |
| 1897 | + closed, | |
| 1863 | 1898 | currentModel: turnInput.modelSelection.model, |
| 1864 | 1899 | }; |
| 1865 | 1900 | yield* Ref.set(queryContext, context); |
| 1866 | 1901 | yield* querySession.messages.pipe( |
| 1867 | − Stream.runForEach(handleSdkMessage), | |
| 1902 | + Stream.runForEach((message) => handleSdkMessage({ query: querySession, message })), | |
| 1868 | 1903 | Effect.exit, |
| 1869 | 1904 | Effect.flatMap( |
| 1870 | 1905 | Effect.fnUntraced(function* (exit: ClaudeQueryStreamExit) { |
| 1871 | − yield* Ref.update(queryContext, (current) => | |
| 1872 | − current?.query === querySession ? null : current, | |
| 1873 | − ); | |
| 1874 | − yield* finalizeActiveTurnAfterQueryExit( | |
| 1875 | − exit._tag === "Failure" ? exit.cause : undefined, | |
| 1906 | + const ownsLiveQuery = yield* Ref.modify(queryContext, (current) => | |
| 1907 | + current?.query === querySession ? [true, null] : [false, current], | |
| 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 | 1917 | Effect.forkIn(sessionScope), |
| 1880 | 1918 | ); |
| 1881 | 1919 | return context; |
| @@ -1885,11 +1923,21 @@ export function makeClaudeAdapterV2( · 3 unmodified lines | ||
| 1885 | 1923 | function* (turnInput: ProviderAdapterV2TurnInput) { |
| 1886 | 1924 | const startedAt = yield* DateTime.now; |
| 1887 | 1925 | const nativeThreadId = yield* getNativeThreadId(turnInput.providerThread); |
| 1888 | − const nativeTurnId = `turn:${turnInput.runId}`; | |
| 1926 | + const nativeTurnId = `turn:${turnInput.attemptId}`; | |
| 1889 | 1927 | const providerTurnId = idAllocator.derive.providerTurn({ |
| 1890 | 1928 | provider: CLAUDE_PROVIDER, |
| 1891 | 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 | 1941 | const currentTurn = yield* Ref.get(activeTurn); |
| 1894 | 1942 | if (currentTurn !== null) { |
| 1895 | 1943 | return yield* new ProviderAdapterProtocolError({ |
| @@ -1901,6 +1949,7 @@ export function makeClaudeAdapterV2( · 5 unmodified lines | ||
| 1901 | 1949 | input: turnInput, |
| 1902 | 1950 | nativeTurnId, |
| 1903 | 1951 | providerTurnId, |
| 1952 | + providerTurnOrdinal, | |
| 1904 | 1953 | startedAt, |
| 1905 | 1954 | assistant: { |
| 1906 | 1955 | text: "", |
| @@ -1947,12 +1996,42 @@ export function makeClaudeAdapterV2( · 40 unmodified lines | ||
| 1947 | 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 | 2006 | yield* Ref.update(interruptedTurns, (current) => { |
| 1951 | 2007 | const next = new Set(current); |
| 1952 | 2008 | next.add(turnInput.providerTurnId); |
| 1953 | 2009 | return next; |
| 1954 | 2010 | }); |
| 1955 | 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 | 2036 | (effect, turnInput) => |
| 1958 | 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 | 2089 | const closeSession = Effect.fnUntraced(function* () { |
| 1972 | 2090 | const existing = yield* Ref.get(queryContext); |
| 1973 | 2091 | if (existing !== null) { |
| @@ -2040,13 +2158,7 @@ export function makeClaudeAdapterV2( · 66 unmodified lines | ||
| 2040 | 2158 | ), |
| 2041 | 2159 | ), |
| 2042 | 2160 | startTurn, |
| 2043 | − steerTurn: (turnInput) => | |
| 2044 | − Effect.fail( | |
| 2045 | − new ProviderAdapterSteerRunUnsupportedError({ | |
| 2046 | − provider: CLAUDE_PROVIDER, | |
| 2047 | − providerThreadId: turnInput.providerThread.id, | |
| 2048 | − }), | |
| 2049 | − ), | |
| 2161 | + steerTurn, | |
| 2050 | 2162 | interruptTurn, |
| 2051 | 2163 | respondToRuntimeRequest: Effect.fn("ClaudeAdapterV2.respondToRuntimeRequest")( |
| 2052 | 2164 | function* (requestInput) { |
| @@ -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 | 1133 | (candidate) => candidate.id === targetRun.activeAttemptId, |
| 1104 | 1134 | ); |
| 1105 | − const currentRootNode = input.projection.nodes.find( | |
| 1135 | + const currentRootNode = postInterruptProjection.nodes.find( | |
| 1106 | 1136 | (candidate) => candidate.id === rootNodeId, |
| 1107 | 1137 | ); |
| 1108 | 1138 | const attemptOrdinal = |
| 1109 | 1139 | Math.max( |
| 1110 | 1140 | 0, |
| 1111 | − ...input.projection.attempts | |
| 1141 | + ...postInterruptProjection.attempts | |
| 1112 | 1142 | .filter((candidate) => candidate.runId === targetRun.id) |
| 1113 | 1143 | .map((candidate) => candidate.attemptOrdinal), |
| 1114 | 1144 | ) + 1; |
| @@ -123,16 +123,10 @@ const scenarioExpectations = { · 122 unmodified lines | ||
| 123 | 123 | }, |
| 124 | 124 | message_steering: { |
| 125 | 125 | outgoing: ["initialize", "initialized", "thread/start", "turn/start", "turn/steer"], |
| 126 | − incoming: [ | |
| 127 | − "turn/started", | |
| 128 | − "turn/completed", | |
| 129 | − "item/agentMessage/delta", | |
| 130 | − "item/commandExecution/requestApproval", | |
| 131 | − "serverRequest/resolved", | |
| 132 | − ], | |
| 126 | + incoming: ["turn/started", "turn/completed", "item/agentMessage/delta"], | |
| 133 | 127 | turnStartCount: 1, |
| 134 | 128 | turnCompletedCount: 1, |
| 135 | − approvalRequestCount: 1, | |
| 129 | + approvalRequestCount: 0, | |
| 136 | 130 | }, |
| 137 | 131 | turn_interrupt: { |
| 138 | 132 | outgoing: ["initialize", "initialized", "thread/start", "turn/start", "turn/interrupt"], |
| @@ -2,6 +2,7 @@ import type { · 1 unmodified line | ||
| 2 | 2 | OrchestrationV2Command, |
| 3 | 3 | OrchestrationV2DomainEvent, |
| 4 | 4 | OrchestrationV2RuntimeRequest, |
| 5 | + OrchestrationV2Run, | |
| 5 | 6 | OrchestrationV2ShellSnapshot, |
| 6 | 7 | OrchestrationV2StoredEvent, |
| 7 | 8 | OrchestrationV2ThreadProjection, |
| @@ -37,6 +38,11 @@ export type OrchestratorV2ScenarioStep = · 29 unmodified lines | ||
| 37 | 38 | readonly type: "await_thread_idle"; |
| 38 | 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 | 47 | readonly type: "respond_to_next_runtime_request"; |
| 42 | 48 | readonly threadId: ThreadId; |
| @@ -208,6 +214,33 @@ export function runOrchestratorV2Scenario( · 165 unmodified lines | ||
| 208 | 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 | 244 | for (const step of scenarioSteps(scenario)) { |
| 212 | 245 | switch (step.type) { |
| 213 | 246 | case "dispatch": { |
| @@ -242,6 +275,9 @@ export function runOrchestratorV2Scenario( · 28 unmodified lines | ||
| 242 | 275 | case "await_thread_idle": |
| 243 | 276 | yield* waitForThreadIdle(step.threadId); |
| 244 | 277 | break; |
| 278 | + case "await_run_steerable": | |
| 279 | + yield* waitForRunSteerable(step.threadId, step.runId); | |
| 280 | + break; | |
| 245 | 281 | case "respond_to_next_runtime_request": { |
| 246 | 282 | const request = yield* waitForPendingRuntimeRequest(step.threadId); |
| 247 | 283 | const result = yield* orchestrator.dispatch({ |
| @@ -1,3 +1,4 @@ | ||
| 1 | +import { assertClaudeMessageSteeringOutput } from "./message_steering/claude_output.ts"; | |
| 1 | 2 | import { assertMessageSteeringOutput } from "./message_steering/codex_output.ts"; |
| 2 | 3 | import { messageSteeringInput } from "./message_steering/input.ts"; |
| 3 | 4 | import { assertMultiTurnClaudeOutput } from "./multi_turn/claude_output.ts"; |
| @@ -207,6 +208,12 @@ export const ORCHESTRATOR_REPLAY_FIXTURES = [ · 203 unmodified lines | ||
| 207 | 208 | modelSelection: CODEX_MODEL_SELECTION, |
| 208 | 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 | 281 | provider: "codex", |
| 275 | 282 | transcriptFile: new URL("./message_steering/codex_transcript.ndjson", import.meta.url), |
| 276 | 283 | modelSelection: CODEX_MODEL_SELECTION, |
| 277 | − runtimePolicyOverride: READ_ONLY_ON_REQUEST_POLICY, | |
| 278 | 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 | 325 | // `tool_call_restricted_granular/claude_transcript.ndjson`, and |
| 314 | 326 | // docs/orchestration-v2/provider-capability-system.md. |
| 315 | 327 | |
| 316 | −// TODO(claude-v2/control): add Claude providers to queued-turn, interrupt, and steering fixtures | |
| 317 | −// once the real adapter exposes those behaviors through capability-checked V2 paths. | |
| 318 | −// Cross-reference `queued_turn/codex_transcript.ndjson`, `turn_interrupt/codex_transcript.ndjson`, | |
| 319 | −// `message_steering/codex_transcript.ndjson`, and docs/orchestration-v2/feature-lifecycles.md. | |
| 328 | +// TODO(claude-v2/control): add a Claude interrupt fixture once explicit run interrupt behavior | |
| 329 | +// is fully hardened. Cross-reference `turn_interrupt/codex_transcript.ndjson` and | |
| 330 | +// docs/orchestration-v2/feature-lifecycles.md. | |
| 320 | 331 | |
| 321 | 332 | // TODO(claude-v2/context-transfer): add provider-switch handoff and return fixtures when portable |
| 322 | 333 | // context handoff is implemented. Cross-reference docs/orchestration-v2/provider-switching-and-context.md |
| @@ -0,0 +1,43 @@ | ||
| 1 | +import { assert } from "@effect/vitest"; | |
| 2 | +import type { ProviderReplayTranscript } from "@t3tools/contracts"; | |
| 3 | + | |
| 4 | +import type { OrchestratorV2ScenarioResult } from "../../OrchestratorScenario.ts"; | |
| 5 | +import { | |
| 6 | + assertAssistantTextIncludes, | |
| 7 | + assertBaseProjection, | |
| 8 | + assertRuntimeRequestCounts, | |
| 9 | + assertSemanticProjectionIntegrity, | |
| 10 | + assertTurnItemTypes, | |
| 11 | + assertUserMessageInputIntents, | |
| 12 | + assertUserMessagesInclude, | |
| 13 | + assertVisibleTurnItemsMirrorLocalTurnItems, | |
| 14 | + MESSAGE_STEERING_INITIAL_PROMPT, | |
| 15 | + MESSAGE_STEERING_STEER_PROMPT, | |
| 16 | + projectionFor, | |
| 17 | +} from "../shared.ts"; | |
| 18 | + | |
| 19 | +export function assertClaudeMessageSteeringOutput( | |
| 20 | + result: OrchestratorV2ScenarioResult, | |
| 21 | + transcript: ProviderReplayTranscript, | |
| 22 | +) { | |
| 23 | + assert.equal(transcript.provider, "claudeAgent"); | |
| 24 | + assertBaseProjection({ result, transcript, runCount: 1, runStatuses: ["completed"] }); | |
| 25 | + | |
| 26 | + const projection = projectionFor(result, transcript.scenario); | |
| 27 | + assertSemanticProjectionIntegrity(projection); | |
| 28 | + assertVisibleTurnItemsMirrorLocalTurnItems(projection); | |
| 29 | + assertTurnItemTypes(projection, ["user_message", "assistant_message"]); | |
| 30 | + assertRuntimeRequestCounts(projection, { total: 0 }); | |
| 31 | + assertUserMessagesInclude(projection, [ | |
| 32 | + MESSAGE_STEERING_INITIAL_PROMPT, | |
| 33 | + MESSAGE_STEERING_STEER_PROMPT, | |
| 34 | + ]); | |
| 35 | + assertUserMessageInputIntents(projection, ["turn_start", "steer"]); | |
| 36 | + assertAssistantTextIncludes(projection, "steering fixture observed"); | |
| 37 | + assert.equal(projection.runs.length, 1, "steering must attach to the active run"); | |
| 38 | + assert.equal( | |
| 39 | + projection.providerTurns.length, | |
| 40 | + 1, | |
| 41 | + "active steering must not create a new provider turn", | |
| 42 | + ); | |
| 43 | +} | |
| @@ -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"} | |
| @@ -3,40 +3,37 @@ import type { ProviderReplayTranscript } from "@t3tools/contracts"; · 2 unmodified lines | ||
| 3 | 3 | |
| 4 | 4 | import type { OrchestratorV2ScenarioResult } from "../../OrchestratorScenario.ts"; |
| 5 | 5 | import { |
| 6 | − assertAllRuntimeRequestsResolved, | |
| 6 | + assertAssistantTextIncludes, | |
| 7 | 7 | assertBaseProjection, |
| 8 | 8 | assertRuntimeRequestCounts, |
| 9 | − assertRuntimeRequestKinds, | |
| 10 | 9 | assertSemanticProjectionIntegrity, |
| 11 | 10 | assertTurnItemTypes, |
| 12 | 11 | assertUserMessageInputIntents, |
| 13 | 12 | assertUserMessagesInclude, |
| 14 | 13 | assertVisibleTurnItemsMirrorLocalTurnItems, |
| 14 | + MESSAGE_STEERING_INITIAL_PROMPT, | |
| 15 | 15 | MESSAGE_STEERING_STEER_PROMPT, |
| 16 | 16 | projectionFor, |
| 17 | − TOOL_CALL_WRITE_PROMPT, | |
| 18 | 17 | } from "../shared.ts"; |
| 19 | 18 | |
| 20 | 19 | export function assertMessageSteeringOutput( |
| 21 | 20 | result: OrchestratorV2ScenarioResult, |
| 22 | 21 | transcript: ProviderReplayTranscript, |
| 23 | 22 | ) { |
| 23 | + assert.equal(transcript.provider, "codex"); | |
| 24 | 24 | assertBaseProjection({ result, transcript, runCount: 1, runStatuses: ["completed"] }); |
| 25 | 25 | |
| 26 | 26 | const projection = projectionFor(result, transcript.scenario); |
| 27 | 27 | assertSemanticProjectionIntegrity(projection); |
| 28 | 28 | assertVisibleTurnItemsMirrorLocalTurnItems(projection); |
| 29 | − assertTurnItemTypes(projection, [ | |
| 30 | − "user_message", | |
| 31 | − "command_execution", | |
| 32 | − "approval_request", | |
| 33 | − "assistant_message", | |
| 29 | + assertTurnItemTypes(projection, ["user_message", "assistant_message"]); | |
| 30 | + assertRuntimeRequestCounts(projection, { total: 0 }); | |
| 31 | + assertUserMessagesInclude(projection, [ | |
| 32 | + MESSAGE_STEERING_INITIAL_PROMPT, | |
| 33 | + MESSAGE_STEERING_STEER_PROMPT, | |
| 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 | 35 | assertUserMessageInputIntents(projection, ["turn_start", "steer"]); |
| 36 | + assertAssistantTextIncludes(projection, "steering fixture observed"); | |
| 40 | 37 | assert.equal(projection.runs.length, 1, "steering must attach to the active run"); |
| 41 | 38 | assert.equal( |
| 42 | 39 | projection.providerTurns.length, |
| @@ -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 | 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 | 4 | {"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}} |
| 5 | 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"}}} | |
| 7 | −{"type":"expect_outbound","label":"turn/start","frame":{"id":3,"method":"turn/start","params":{"approvalPolicy":"on-request","input":[{"text":"Create or overwrite .codex-probe-write-action.txt with exactly this text: codex app-server approval fixture. Use a local shell command or file edit only, then briefly report what happened. Do not read package metadata, use GitHub, use web, or use MCP.","type":"text"}],"sandboxPolicy":{"access":{"type":"fullAccess"},"networkAccess":false,"type":"readOnly"},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374"}}} | |
| 8 | −{"type":"emit_inbound","label":"thread/started","frame":{"method":"thread/started","params":{"thread":{"id":"019dadec-ebd7-75f0-812e-27a7e57bb374","forkedFromId":null,"preview":"","ephemeral":false,"modelProvider":"openai","createdAt":1776739478,"updatedAt":1776739478,"status":{"type":"idle"},"path":"/Users/julius/.codex/sessions/2026/04/20/rollout-2026-04-20T19-44-38-019dadec-ebd7-75f0-812e-27a7e57bb374.jsonl","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/packages/effect-codex-app-server","cliVersion":"0.120.0","source":"vscode","agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"turns":[]}}}} | |
| 6 | +{"type":"emit_inbound","label":"remoteControl/status/changed","frame":{"method":"remoteControl/status/changed","params":{"status":"disabled","environmentId":null}}} | |
| 7 | +{"type":"emit_inbound","label":"thread/start","frame":{"id":2,"result":{"thread":{"id":"019e014d-b214-71f0-9538-23527e8247ca","forkedFromId":null,"preview":"","ephemeral":false,"modelProvider":"openai","createdAt":1778138329,"updatedAt":1778138329,"status":{"type":"idle"},"path":"/Users/julius/.codex_p/sessions/2026/05/07/rollout-2026-05-07T00-18-49-019e014d-b214-71f0-9538-23527e8247ca.jsonl","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/apps/server","cliVersion":"0.128.0","source":"vscode","agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"turns":[]},"model":"gpt-5.5","modelProvider":"openai","serviceTier":"fast","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/apps/server","instructionSources":["/Users/julius/.codex_p/AGENTS.md","/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/AGENTS.md"],"approvalPolicy":"on-request","approvalsReviewer":"user","sandbox":{"type":"workspaceWrite","writableRoots":["/Users/julius/.codex_p/memories"],"networkAccess":false,"excludeTmpdirEnvVar":false,"excludeSlashTmp":false},"permissionProfile":{"type":"managed","network":{"enabled":false},"fileSystem":{"type":"restricted","entries":[{"path":{"type":"special","value":{"kind":"root"}},"access":"read"},{"path":{"type":"special","value":{"kind":"project_roots","subpath":null}},"access":"write"},{"path":{"type":"special","value":{"kind":"slash_tmp"}},"access":"write"},{"path":{"type":"special","value":{"kind":"tmpdir"}},"access":"write"},{"path":{"type":"special","value":{"kind":"project_roots","subpath":".git"}},"access":"read"},{"path":{"type":"special","value":{"kind":"project_roots","subpath":".agents"}},"access":"read"},{"path":{"type":"special","value":{"kind":"project_roots","subpath":".codex"}},"access":"read"},{"path":{"type":"path","path":"/Users/julius/.codex_p/memories"},"access":"write"}]}},"activePermissionProfile":{"id":":workspace","extends":null,"modifications":[]},"reasoningEffort":"medium"}}} | |
| 8 | +{"type":"expect_outbound","label":"turn/start","frame":{"id":3,"method":"turn/start","params":{"input":[{"text":"Respond with exactly: steering fixture initial response","type":"text"}],"threadId":"019e014d-b214-71f0-9538-23527e8247ca"}}} | |
| 9 | +{"type":"emit_inbound","label":"thread/started","frame":{"method":"thread/started","params":{"thread":{"id":"019e014d-b214-71f0-9538-23527e8247ca","forkedFromId":null,"preview":"","ephemeral":false,"modelProvider":"openai","createdAt":1778138329,"updatedAt":1778138329,"status":{"type":"idle"},"path":"/Users/julius/.codex_p/sessions/2026/05/07/rollout-2026-05-07T00-18-49-019e014d-b214-71f0-9538-23527e8247ca.jsonl","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/apps/server","cliVersion":"0.128.0","source":"vscode","agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"turns":[]}}}} | |
| 9 | 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 | 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":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","status":{"type":"active","activeFlags":[]}}}} | |
| 14 | −{"type":"emit_inbound","label":"turn/started","frame":{"method":"turn/started","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turn":{"id":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","items":[],"status":"inProgress","error":null,"startedAt":1776739478,"completedAt":null,"durationMs":null}}}} | |
| 15 | −{"type":"expect_outbound","label":"turn/steer","frame":{"id":4,"method":"turn/steer","params":{"expectedTurnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","input":[{"text":"Actually, respond with exactly: steering fixture observed","type":"text"}],"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374"}}} | |
| 16 | −{"type":"emit_inbound","label":"turn/steer","frame":{"id":4,"result":{"turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | |
| 17 | −{"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"codex_apps","status":"ready","error":null}}} | |
| 13 | +{"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"codex_apps","status":"starting","error":null}}} | |
| 14 | +{"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"computer-use","status":"starting","error":null}}} | |
| 15 | +{"type":"emit_inbound","label":"turn/start","frame":{"id":3,"result":{"turn":{"id":"019e014d-b227-7af1-a889-6c7d828eb1a2","items":[],"status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} | |
| 16 | +{"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","status":{"type":"active","activeFlags":[]}}}} | |
| 17 | +{"type":"emit_inbound","label":"turn/started","frame":{"method":"turn/started","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turn":{"id":"019e014d-b227-7af1-a889-6c7d828eb1a2","items":[],"status":"inProgress","error":null,"startedAt":1778138329,"completedAt":null,"durationMs":null}}}} | |
| 18 | +{"type":"expect_outbound","label":"turn/steer","frame":{"id":4,"method":"turn/steer","params":{"expectedTurnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","input":[{"text":"Actually, respond with exactly: steering fixture observed","type":"text"}],"threadId":"019e014d-b214-71f0-9538-23527e8247ca"}}} | |
| 19 | +{"type":"emit_inbound","label":"turn/steer","frame":{"id":4,"result":{"turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} | |
| 20 | +{"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"computer-use","status":"ready","error":null}}} | |
| 18 | 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"}}} | |
| 20 | −{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"550a4417-1843-4e5e-8334-6663461044d5","content":[{"type":"text","text":"Create or overwrite .codex-probe-write-action.txt with exactly this text: codex app-server approval fixture. Use a local shell command or file edit only, then briefly report what happened. Do not read package metadata, use GitHub, use web, or use MCP.","text_elements":[]}]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | |
| 21 | −{"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":0,"windowDurationMins":300,"resetsAt":1776748632},"secondary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1777335432},"credits":null,"planType":"pro"}}}} | |
| 22 | −{"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"reasoning","id":"rs_0a7ba873c300712d0169e6e49aa908819b82a8cc3df84c0036","summary":[],"content":[]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | |
| 23 | −{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"reasoning","id":"rs_0a7ba873c300712d0169e6e49aa908819b82a8cc3df84c0036","summary":[],"content":[]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | |
| 24 | −{"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","status":{"type":"active","activeFlags":["waitingOnApproval"]}}}} | |
| 25 | −{"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"commandExecution","id":"call_SkVd4jdkKEnDTMUaNZwexNLm","command":"/bin/zsh -lc \"printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt\"","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/packages/effect-codex-app-server","processId":null,"source":"agent","status":"inProgress","commandActions":[{"type":"unknown","command":"printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt"}],"aggregatedOutput":null,"exitCode":null,"durationMs":null},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | |
| 26 | −{"type":"emit_inbound","label":"item/commandExecution/requestApproval","frame":{"method":"item/commandExecution/requestApproval","id":0,"params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","itemId":"call_SkVd4jdkKEnDTMUaNZwexNLm","reason":"Do you want to allow writing the requested fixture file in the workspace?","command":"/bin/zsh -lc \"printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt\"","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/packages/effect-codex-app-server","commandActions":[{"type":"unknown","command":"printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt"}],"proposedExecpolicyAmendment":["/bin/zsh","-lc","printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt"],"availableDecisions":["accept",{"acceptWithExecpolicyAmendment":{"execpolicy_amendment":["/bin/zsh","-lc","printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt"]}},"cancel"]}}} | |
| 27 | −{"type":"expect_outbound","label":"item/commandExecution/requestApproval","frame":{"id":0,"result":{"decision":"accept"}}} | |
| 28 | −{"type":"emit_inbound","label":"serverRequest/resolved","frame":{"method":"serverRequest/resolved","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","requestId":0}}} | |
| 29 | −{"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","status":{"type":"active","activeFlags":[]}}}} | |
| 30 | −{"type":"emit_inbound","label":"thread/tokenUsage/updated","frame":{"method":"thread/tokenUsage/updated","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","tokenUsage":{"total":{"totalTokens":28779,"inputTokens":28169,"cachedInputTokens":2432,"outputTokens":610,"reasoningOutputTokens":492},"last":{"totalTokens":28779,"inputTokens":28169,"cachedInputTokens":2432,"outputTokens":610,"reasoningOutputTokens":492},"modelContextWindow":258400}}}} | |
| 31 | −{"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":0,"windowDurationMins":300,"resetsAt":1776748632},"secondary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1777335432},"credits":null,"planType":"pro"}}}} | |
| 32 | −{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"commandExecution","id":"call_SkVd4jdkKEnDTMUaNZwexNLm","command":"/bin/zsh -lc \"printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt\"","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/packages/effect-codex-app-server","processId":"78219","source":"unifiedExecStartup","status":"completed","commandActions":[{"type":"unknown","command":"printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt"}],"aggregatedOutput":null,"exitCode":0,"durationMs":0},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | |
| 33 | −{"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"userMessage","id":"c046f20c-4ec9-4e49-9c31-bcc25ca81b1f","content":[{"type":"text","text":"Actually, respond with exactly: steering fixture observed","text_elements":[]}]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | |
| 34 | −{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"c046f20c-4ec9-4e49-9c31-bcc25ca81b1f","content":[{"type":"text","text":"Actually, respond with exactly: steering fixture observed","text_elements":[]}]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | |
| 35 | −{"type":"emit_inbound","label":"thread/tokenUsage/updated","frame":{"method":"thread/tokenUsage/updated","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","tokenUsage":{"total":{"totalTokens":28779,"inputTokens":28169,"cachedInputTokens":2432,"outputTokens":610,"reasoningOutputTokens":492},"last":{"totalTokens":28779,"inputTokens":28169,"cachedInputTokens":2432,"outputTokens":610,"reasoningOutputTokens":492},"modelContextWindow":258400}}}} | |
| 36 | −{"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":0,"windowDurationMins":300,"resetsAt":1776748632},"secondary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1777335432},"credits":null,"planType":"pro"}}}} | |
| 37 | −{"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"reasoning","id":"rs_0a7ba873c300712d0169e6e4a28490819b9549007ee1031138","summary":[],"content":[]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | |
| 38 | −{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"reasoning","id":"rs_0a7ba873c300712d0169e6e4a28490819b9549007ee1031138","summary":[],"content":[]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | |
| 39 | −{"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_0a7ba873c300712d0169e6e4a2ba50819ba77e49ab176e7a34","text":"","phase":"final_answer","memoryCitation":null},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | |
| 40 | −{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","itemId":"msg_0a7ba873c300712d0169e6e4a2ba50819ba77e49ab176e7a34","delta":"ste"}}} | |
| 41 | −{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","itemId":"msg_0a7ba873c300712d0169e6e4a2ba50819ba77e49ab176e7a34","delta":"ering"}}} | |
| 42 | −{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","itemId":"msg_0a7ba873c300712d0169e6e4a2ba50819ba77e49ab176e7a34","delta":" fixture"}}} | |
| 43 | −{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","itemId":"msg_0a7ba873c300712d0169e6e4a2ba50819ba77e49ab176e7a34","delta":" observed"}}} | |
| 44 | −{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_0a7ba873c300712d0169e6e4a2ba50819ba77e49ab176e7a34","text":"steering fixture observed","phase":"final_answer","memoryCitation":null},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | |
| 45 | −{"type":"emit_inbound","label":"thread/tokenUsage/updated","frame":{"method":"thread/tokenUsage/updated","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","tokenUsage":{"total":{"totalTokens":57642,"inputTokens":57006,"cachedInputTokens":30464,"outputTokens":636,"reasoningOutputTokens":508},"last":{"totalTokens":28863,"inputTokens":28837,"cachedInputTokens":28032,"outputTokens":26,"reasoningOutputTokens":16},"modelContextWindow":258400}}}} | |
| 46 | −{"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":0,"windowDurationMins":300,"resetsAt":1776748632},"secondary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1777335432},"credits":null,"planType":"pro"}}}} | |
| 47 | −{"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","status":{"type":"idle"}}}} | |
| 48 | −{"type":"emit_inbound","label":"turn/completed","frame":{"method":"turn/completed","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turn":{"id":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","items":[],"status":"completed","error":null,"startedAt":1776739478,"completedAt":1776739490,"durationMs":12401}}}} | |
| 22 | +{"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"codex_apps","status":"ready","error":null}}} | |
| 23 | +{"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"userMessage","id":"4fac8685-35e8-475c-96b6-9adb33c68158","content":[{"type":"text","text":"Respond with exactly: steering fixture initial response","text_elements":[]}]},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} | |
| 24 | +{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"4fac8685-35e8-475c-96b6-9adb33c68158","content":[{"type":"text","text":"Respond with exactly: steering fixture initial response","text_elements":[]}]},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} | |
| 25 | +{"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":19,"windowDurationMins":300,"resetsAt":1778142198},"secondary":{"usedPercent":31,"windowDurationMins":10080,"resetsAt":1778539136},"credits":null,"planType":"pro","rateLimitReachedType":null}}}} | |
| 26 | +{"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"reasoning","id":"rs_010631d904f9e19a0169fc3cdcb670819486fcc6dc7b883f58","summary":[],"content":[]},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} | |
| 27 | +{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"reasoning","id":"rs_010631d904f9e19a0169fc3cdcb670819486fcc6dc7b883f58","summary":[],"content":[]},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} | |
| 28 | +{"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_010631d904f9e19a0169fc3cdce8ec8194a7d8a5bb50424c09","text":"","phase":"final_answer","memoryCitation":null},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} | |
| 29 | +{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cdce8ec8194a7d8a5bb50424c09","delta":"ste"}}} | |
| 30 | +{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cdce8ec8194a7d8a5bb50424c09","delta":"ering"}}} | |
| 31 | +{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cdce8ec8194a7d8a5bb50424c09","delta":" fixture"}}} | |
| 32 | +{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cdce8ec8194a7d8a5bb50424c09","delta":" initial"}}} | |
| 33 | +{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cdce8ec8194a7d8a5bb50424c09","delta":" response"}}} | |
| 34 | +{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_010631d904f9e19a0169fc3cdce8ec8194a7d8a5bb50424c09","text":"steering fixture initial response","phase":"final_answer","memoryCitation":null},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} | |
| 35 | +{"type":"emit_inbound","label":"thread/tokenUsage/updated","frame":{"method":"thread/tokenUsage/updated","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","tokenUsage":{"total":{"totalTokens":18900,"inputTokens":18880,"cachedInputTokens":7552,"outputTokens":20,"reasoningOutputTokens":9},"last":{"totalTokens":18900,"inputTokens":18880,"cachedInputTokens":7552,"outputTokens":20,"reasoningOutputTokens":9},"modelContextWindow":258400}}}} | |
| 36 | +{"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":19,"windowDurationMins":300,"resetsAt":1778142198},"secondary":{"usedPercent":31,"windowDurationMins":10080,"resetsAt":1778539136},"credits":null,"planType":"pro","rateLimitReachedType":null}}}} | |
| 37 | +{"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"userMessage","id":"3d8198bc-53c8-4885-8dfe-c5158eb2cfb3","content":[{"type":"text","text":"Actually, respond with exactly: steering fixture observed","text_elements":[]}]},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} | |
| 38 | +{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"3d8198bc-53c8-4885-8dfe-c5158eb2cfb3","content":[{"type":"text","text":"Actually, respond with exactly: steering fixture observed","text_elements":[]}]},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} | |
| 39 | +{"type":"emit_inbound","label":"thread/tokenUsage/updated","frame":{"method":"thread/tokenUsage/updated","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","tokenUsage":{"total":{"totalTokens":18900,"inputTokens":18880,"cachedInputTokens":7552,"outputTokens":20,"reasoningOutputTokens":9},"last":{"totalTokens":18900,"inputTokens":18880,"cachedInputTokens":7552,"outputTokens":20,"reasoningOutputTokens":9},"modelContextWindow":258400}}}} | |
| 40 | +{"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":19,"windowDurationMins":300,"resetsAt":1778142198},"secondary":{"usedPercent":31,"windowDurationMins":10080,"resetsAt":1778539136},"credits":null,"planType":"pro","rateLimitReachedType":null}}}} | |
| 41 | +{"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_010631d904f9e19a0169fc3cddd2b08194ba097f57a2fd6332","text":"","phase":"final_answer","memoryCitation":null},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} | |
| 42 | +{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cddd2b08194ba097f57a2fd6332","delta":"ste"}}} | |
| 43 | +{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cddd2b08194ba097f57a2fd6332","delta":"ering"}}} | |
| 44 | +{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cddd2b08194ba097f57a2fd6332","delta":" fixture"}}} | |
| 45 | +{"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cddd2b08194ba097f57a2fd6332","delta":" observed"}}} | |
| 46 | +{"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_010631d904f9e19a0169fc3cddd2b08194ba097f57a2fd6332","text":"steering fixture observed","phase":"final_answer","memoryCitation":null},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} | |
| 47 | +{"type":"emit_inbound","label":"thread/tokenUsage/updated","frame":{"method":"thread/tokenUsage/updated","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","tokenUsage":{"total":{"totalTokens":37812,"inputTokens":37784,"cachedInputTokens":26368,"outputTokens":28,"reasoningOutputTokens":9},"last":{"totalTokens":18912,"inputTokens":18904,"cachedInputTokens":18816,"outputTokens":8,"reasoningOutputTokens":0},"modelContextWindow":258400}}}} | |
| 48 | +{"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":19,"windowDurationMins":300,"resetsAt":1778142198},"secondary":{"usedPercent":31,"windowDurationMins":10080,"resetsAt":1778539136},"credits":null,"planType":"pro","rateLimitReachedType":null}}}} | |
| 49 | +{"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","status":{"type":"idle"}}}} | |
| 50 | +{"type":"emit_inbound","label":"turn/completed","frame":{"method":"turn/completed","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turn":{"id":"019e014d-b227-7af1-a889-6c7d828eb1a2","items":[],"status":"completed","error":null,"startedAt":1778138329,"completedAt":1778138333,"durationMs":4311}}}} | |
| 49 | 51 | {"type":"runtime_exit","status":"success"} |
| @@ -1,19 +1,18 @@ | ||
| 1 | 1 | import { |
| 2 | + MESSAGE_STEERING_INITIAL_PROMPT, | |
| 2 | 3 | MESSAGE_STEERING_STEER_PROMPT, |
| 3 | − TOOL_CALL_WRITE_PROMPT, | |
| 4 | 4 | type OrchestratorFixtureInput, |
| 5 | 5 | } from "../shared.ts"; |
| 6 | 6 | |
| 7 | 7 | export function messageSteeringInput(): OrchestratorFixtureInput { |
| 8 | 8 | return { |
| 9 | 9 | steps: [ |
| 10 | − { type: "message", text: TOOL_CALL_WRITE_PROMPT }, | |
| 10 | + { type: "message", text: MESSAGE_STEERING_INITIAL_PROMPT }, | |
| 11 | 11 | { |
| 12 | 12 | type: "steer", |
| 13 | 13 | text: MESSAGE_STEERING_STEER_PROMPT, |
| 14 | 14 | targetRunIndex: 1, |
| 15 | 15 | }, |
| 16 | − { type: "approve_next_runtime_request" }, | |
| 17 | 16 | ], |
| 18 | 17 | }; |
| 19 | 18 | } |
| @@ -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"} | |
| @@ -37,6 +37,8 @@ export const TOOL_CALL_READ_ONLY_WORKSPACE_ROOT = "/tmp/claude-replay-tool_call_ · 36 unmodified lines | ||
| 37 | 37 | export const TOOL_CALL_READ_ONLY_PROMPT = `Read ${TOOL_CALL_READ_ONLY_WORKSPACE_ROOT}/package.json and ${TOOL_CALL_READ_ONLY_WORKSPACE_ROOT}/tsconfig.json, then answer exactly: read only tool fixture complete`; |
| 38 | 38 | export const TOOL_CALL_WRITE_PROMPT = |
| 39 | 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."; |
| 40 | +export const MESSAGE_STEERING_INITIAL_PROMPT = | |
| 41 | + "Respond with exactly: steering fixture initial response"; | |
| 40 | 42 | export const SUBAGENT_PROMPT = |
| 41 | 43 | "Spawn 2 subagents, one to read package.json and one to read tsconfig.json"; |
| 42 | 44 | export const TURN_INTERRUPT_PROMPT = |
| @@ -296,8 +298,7 @@ export function materializeFixtureInput(input: { · 253 unmodified lines | ||
| 296 | 298 | const nextStep = input.fixtureInput.steps[stepIndex + 1]; |
| 297 | 299 | const shouldRunInBackground = |
| 298 | 300 | (nextStep !== undefined && |
| 299 | − (((nextStep.type === "steer" || nextStep.type === "interrupt") && | |
| 300 | − nextStep.targetRunIndex === messageIndex) || | |
| 301 | + ((nextStep.type === "interrupt" && nextStep.targetRunIndex === messageIndex) || | |
| 301 | 302 | nextStep.type === "queue_message")) || |
| 302 | 303 | nextStep?.type === "approve_next_runtime_request" || |
| 303 | 304 | nextStep?.type === "answer_next_user_input_request"; |
| @@ -321,7 +322,13 @@ export function materializeFixtureInput(input: { · 17 unmodified lines | ||
| 321 | 322 | ); |
| 322 | 323 | if (shouldRunInBackground) { |
| 323 | 324 | activeRunDispatchKeys.add(key); |
| 324 | − } else { | |
| 325 | + } else if ( | |
| 326 | + !( | |
| 327 | + nextStep !== undefined && | |
| 328 | + nextStep.type === "steer" && | |
| 329 | + nextStep.targetRunIndex === messageIndex | |
| 330 | + ) | |
| 331 | + ) { | |
| 325 | 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 | 408 | break; |
| 402 | 409 | case "steer": |
| 403 | 410 | messageIndex += 1; |
| 411 | + steps.push({ | |
| 412 | + type: "await_run_steerable", | |
| 413 | + threadId: ids.threadId, | |
| 414 | + runId: runIdFor(step.targetRunIndex), | |
| 415 | + }); | |
| 404 | 416 | pushDispatch( |
| 405 | 417 | dispatchMessageCommand({ |
| 406 | 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 ( | |
| 425 | − input.fixtureInput.steps[stepIndex + 1]?.type !== "approve_next_runtime_request" && | |
| 426 | − activeRunDispatchKeys.delete(`run:${step.targetRunIndex}`) | |
| 427 | − ) { | |
| 428 | − steps.push({ type: "await", key: `run:${step.targetRunIndex}` }); | |
| 436 | + if (input.fixtureInput.steps[stepIndex + 1]?.type !== "approve_next_runtime_request") { | |
| 437 | + if (activeRunDispatchKeys.delete(`run:${step.targetRunIndex}`)) { | |
| 438 | + steps.push({ type: "await", key: `run:${step.targetRunIndex}` }); | |
| 439 | + } | |
| 429 | 440 | steps.push({ type: "await_thread_idle", threadId: ids.threadId }); |
| 430 | 441 | } |
| 431 | 442 | break; |