- 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 | import { makeCheckpointWorkspace } from "../src/orchestration-v2/testkit/ReplayFixtureWorkspace.ts"; | 14 | import { makeCheckpointWorkspace } from "../src/orchestration-v2/testkit/ReplayFixtureWorkspace.ts"; |
| 15 | import { CLAUDE_MODEL_SELECTION } from "../src/orchestration-v2/testkit/fixtures/shared.ts"; | 15 | import { CLAUDE_MODEL_SELECTION } from "../src/orchestration-v2/testkit/fixtures/shared.ts"; |
| 16 | import { | 16 | import { |
| 17 | MESSAGE_STEERING_INITIAL_PROMPT, | ||
| 17 | MULTI_TURN_FIRST_PROMPT, | 18 | MULTI_TURN_FIRST_PROMPT, |
| 19 | MESSAGE_STEERING_STEER_PROMPT, | ||
| 18 | READ_ONLY_NEVER_POLICY, | 20 | READ_ONLY_NEVER_POLICY, |
| 19 | READ_ONLY_ON_REQUEST_POLICY, | 21 | READ_ONLY_ON_REQUEST_POLICY, |
| 20 | RESTRICTED_GRANULAR_POLICY, | 22 | RESTRICTED_GRANULAR_POLICY, |
| @@ -46,6 +48,18 @@ const CLAUDE_RECORDINGS = { · 25 unmodified lines | |||
| 46 | queryMode: "restart", | 48 | queryMode: "restart", |
| 47 | enableTools: true, | 49 | enableTools: true, |
| 48 | }, | 50 | }, |
| 51 | queued_turn: { | ||
| 52 | prompts: [MULTI_TURN_FIRST_PROMPT, MULTI_TURN_SECOND_PROMPT], | ||
| 53 | defaultTranscriptFile: "fixtures/queued_turn/claude_transcript.ndjson", | ||
| 54 | queryMode: "streaming", | ||
| 55 | enableTools: true, | ||
| 56 | }, | ||
| 57 | message_steering: { | ||
| 58 | prompts: [MESSAGE_STEERING_INITIAL_PROMPT, MESSAGE_STEERING_STEER_PROMPT], | ||
| 59 | defaultTranscriptFile: "fixtures/message_steering/claude_transcript.ndjson", | ||
| 60 | queryMode: "active_steering", | ||
| 61 | enableTools: true, | ||
| 62 | }, | ||
| 49 | tool_call_read_only: { | 63 | tool_call_read_only: { |
| 50 | prompts: [TOOL_CALL_READ_ONLY_PROMPT], | 64 | prompts: [TOOL_CALL_READ_ONLY_PROMPT], |
| 51 | defaultTranscriptFile: "fixtures/tool_call_read_only/claude_transcript.ndjson", | 65 | defaultTranscriptFile: "fixtures/tool_call_read_only/claude_transcript.ndjson", |
| @@ -88,15 +102,24 @@ function readArgValue(name: string): string | undefined { · 36 unmodified lines | |||
| 88 | return index >= 0 ? args[index + 1] : undefined; | 102 | return index >= 0 ? args[index + 1] : undefined; |
| 89 | } | 103 | } |
| 90 | 104 | ||
| 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 | const raw = readArgValue("--query-mode") ?? process.env.T3_CLAUDE_REPLAY_QUERY_MODE; | 108 | const raw = readArgValue("--query-mode") ?? process.env.T3_CLAUDE_REPLAY_QUERY_MODE; |
| 93 | if (raw === undefined) { | 109 | if (raw === undefined) { |
| 94 | return defaultMode; | 110 | return defaultMode; |
| 95 | } | 111 | } |
| 96 | if (raw === "streaming" || raw === "restart") { | 112 | if ( |
| 113 | raw === "streaming" || | ||
| 114 | raw === "restart" || | ||
| 115 | raw === "active_steering" || | ||
| 116 | raw === "interrupt_restart" | ||
| 117 | ) { | ||
| 97 | return raw; | 118 | return raw; |
| 98 | } | 119 | } |
| 99 | throw new Error(`Unsupported Claude replay query mode '${raw}'. Use 'streaming' or 'restart'.`); | 120 | throw new Error( |
| 121 | `Unsupported Claude replay query mode '${raw}'. Use 'streaming', 'restart', 'active_steering', or 'interrupt_restart'.`, | ||
| 122 | ); | ||
| 100 | } | 123 | } |
| 101 | 124 | ||
| 102 | const scenario = readArgValue("--scenario") ?? process.env.T3_CLAUDE_REPLAY_SCENARIO ?? "simple"; | 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 | import type * as CodexSchema from "effect-codex-app-server/schema"; | 8 | import type * as CodexSchema from "effect-codex-app-server/schema"; |
| 9 | 9 | ||
| 10 | import { | 10 | import { |
| 11 | MESSAGE_STEERING_INITIAL_PROMPT, | ||
| 11 | MESSAGE_STEERING_STEER_PROMPT, | 12 | MESSAGE_STEERING_STEER_PROMPT, |
| 12 | MULTI_TURN_FIRST_PROMPT, | 13 | MULTI_TURN_FIRST_PROMPT, |
| 13 | MULTI_TURN_SECOND_PROMPT, | 14 | MULTI_TURN_SECOND_PROMPT, |
| @@ -491,20 +492,12 @@ function scenarios(): ReadonlyArray<ReplayScenario> { · 477 unmodified lines | |||
| 491 | { | 492 | { |
| 492 | name: "immediate-steer", | 493 | name: "immediate-steer", |
| 493 | description: "Start a turn, then immediately steer the active root turn.", | 494 | description: "Start a turn, then immediately steer the active root turn.", |
| 494 | turnDefaults: { | ||
| 495 | approvalPolicy: "never", | ||
| 496 | sandboxPolicy: workspaceWriteSandbox(), | ||
| 497 | }, | ||
| 498 | steps: [ | 495 | steps: [ |
| 499 | { | 496 | { |
| 500 | type: "steeredTurn", | 497 | type: "steeredTurn", |
| 501 | label: "steered", | 498 | label: "steered", |
| 502 | prompt: TOOL_CALL_WRITE_PROMPT, | 499 | prompt: MESSAGE_STEERING_INITIAL_PROMPT, |
| 503 | steer: MESSAGE_STEERING_STEER_PROMPT, | 500 | steer: MESSAGE_STEERING_STEER_PROMPT, |
| 504 | turnOverrides: { | ||
| 505 | approvalPolicy: "on-request", | ||
| 506 | sandboxPolicy: readOnlyFullAccessSandbox(), | ||
| 507 | }, | ||
| 508 | }, | 501 | }, |
| 509 | ], | 502 | ], |
| 510 | }, | 503 | }, |
| @@ -1,3 +1,5 @@ | |||
| 1 | import path from "node:path"; | ||
| 2 | |||
| 1 | import { | 3 | import { |
| 2 | query, | 4 | query, |
| 3 | type CanUseTool, | 5 | type CanUseTool, |
| @@ -376,7 +378,7 @@ function makeReplayQueryRunner(transcript: ClaudeAgentSdkReplayTranscript): Clau · 372 unmodified lines | |||
| 376 | 378 | ||
| 377 | if (entry.type === "runtime_exit") { | 379 | if (entry.type === "runtime_exit") { |
| 378 | advance(); | 380 | advance(); |
| 379 | if (entry.status === "success") { | 381 | if (entry.status === "success" || entry.status === "cancelled") { |
| 380 | return; | 382 | return; |
| 381 | } | 383 | } |
| 382 | fail( | 384 | fail( |
| @@ -572,11 +574,14 @@ export async function replayClaudeAgentSdkTranscript(input: { · 189 unmodified lines | |||
| 572 | ); | 574 | ); |
| 573 | } | 575 | } |
| 574 | 576 | ||
| 575 | function serializeReplayError(error: unknown): unknown { | 577 | function serializeReplayError(error: unknown, scenario?: string): unknown { |
| 576 | return error instanceof Error | 578 | return error instanceof Error |
| 577 | ? { | 579 | ? { |
| 578 | name: error.name, | 580 | name: error.name, |
| 579 | message: error.message, | 581 | message: |
| 582 | scenario === undefined | ||
| 583 | ? error.message | ||
| 584 | : sanitizeReplayText({ text: error.message, scenario }), | ||
| 580 | } | 585 | } |
| 581 | : error; | 586 | : error; |
| 582 | } | 587 | } |
| @@ -648,6 +653,13 @@ function sanitizedReplayCwd(scenario: string): string { · 65 unmodified lines | |||
| 648 | return `/tmp/claude-replay-${scenario}`; | 653 | return `/tmp/claude-replay-${scenario}`; |
| 649 | } | 654 | } |
| 650 | 655 | ||
| 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 | function sanitizeSdkMessageForReplay(input: { | 663 | function sanitizeSdkMessageForReplay(input: { |
| 652 | readonly message: SDKMessage; | 664 | readonly message: SDKMessage; |
| 653 | readonly scenario: string; | 665 | readonly scenario: string; |
| @@ -685,6 +697,14 @@ function sanitizeSdkMessageForReplay(input: { · 31 unmodified lines | |||
| 685 | }, | 697 | }, |
| 686 | }; | 698 | }; |
| 687 | } | 699 | } |
| 700 | if (message.type === "result" && message.subtype !== "success" && message.errors.length > 0) { | ||
| 701 | return { | ||
| 702 | ...message, | ||
| 703 | errors: message.errors.map((error) => | ||
| 704 | sanitizeReplayText({ text: error, scenario: input.scenario }), | ||
| 705 | ), | ||
| 706 | }; | ||
| 707 | } | ||
| 688 | return message; | 708 | return message; |
| 689 | } | 709 | } |
| 690 | 710 | ||
| @@ -763,6 +783,81 @@ async function recordMessagesUntilTurnResult(input: { · 72 unmodified lines | |||
| 763 | } | 783 | } |
| 764 | } | 784 | } |
| 765 | 785 | ||
| 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 | async function recordClaudeStreamingQuery(input: { | 861 | async function recordClaudeStreamingQuery(input: { |
| 767 | readonly scenario: string; | 862 | readonly scenario: string; |
| 768 | readonly prompts: ReadonlyArray<string>; | 863 | readonly prompts: ReadonlyArray<string>; |
| @@ -869,7 +964,142 @@ async function recordClaudeStreamingQuery(input: { · 100 unmodified lines | |||
| 869 | input.entries.push({ | 964 | input.entries.push({ |
| 870 | type: "runtime_exit", | 965 | type: "runtime_exit", |
| 871 | status: "error", | 966 | status: "error", |
| 872 | error: serializeReplayError(error), | 967 | error: serializeReplayError(error, input.scenario), |
| 968 | }); | ||
| 969 | throw error; | ||
| 970 | } | ||
| 971 | } | ||
| 972 | |||
| 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 | throw error; | 1104 | throw error; |
| 875 | } | 1105 | } |
| @@ -955,20 +1185,183 @@ async function recordClaudeRestartingQueries(input: { · 79 unmodified lines | |||
| 955 | input.entries.push({ | 1185 | input.entries.push({ |
| 956 | type: "runtime_exit", | 1186 | type: "runtime_exit", |
| 957 | status: "error", | 1187 | status: "error", |
| 958 | error: serializeReplayError(error), | 1188 | error: serializeReplayError(error, input.scenario), |
| 959 | }); | 1189 | }); |
| 960 | throw error; | 1190 | throw error; |
| 961 | } | 1191 | } |
| 962 | } | 1192 | } |
| 963 | } | 1193 | } |
| 964 | 1194 | ||
| 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 | export async function recordClaudeAgentSdkReplayTranscript(input: { | 1358 | export async function recordClaudeAgentSdkReplayTranscript(input: { |
| 966 | readonly scenario: string; | 1359 | readonly scenario: string; |
| 967 | readonly prompts: ReadonlyArray<string>; | 1360 | readonly prompts: ReadonlyArray<string>; |
| 968 | readonly modelSelection: ModelSelection; | 1361 | readonly modelSelection: ModelSelection; |
| 969 | readonly cwd: string; | 1362 | readonly cwd: string; |
| 970 | readonly sessionId?: string; | 1363 | readonly sessionId?: string; |
| 971 | readonly queryMode?: "streaming" | "restart"; | 1364 | readonly queryMode?: "streaming" | "restart" | "active_steering" | "interrupt_restart"; |
| 972 | readonly enableTools?: boolean; | 1365 | readonly enableTools?: boolean; |
| 973 | readonly tools?: ClaudeAgentSdkQueryTools; | 1366 | readonly tools?: ClaudeAgentSdkQueryTools; |
| 974 | readonly permissionMode?: ClaudeAgentSdkQueryOptions["permissionMode"]; | 1367 | readonly permissionMode?: ClaudeAgentSdkQueryOptions["permissionMode"]; |
| @@ -1010,7 +1403,30 @@ export async function recordClaudeAgentSdkReplayTranscript(input: { · 35 unmodified lines | |||
| 1010 | ? {} | 1403 | ? {} |
| 1011 | : { permissionDecision: input.permissionDecision }), | 1404 | : { permissionDecision: input.permissionDecision }), |
| 1012 | }); | 1405 | }); |
| 1013 | } else { | 1406 | } else if (queryMode === "active_steering") { |
| 1407 | await recordClaudeActiveSteeringQuery({ | ||
| 1408 | scenario: input.scenario, | ||
| 1409 | prompts: input.prompts, | ||
| 1410 | modelSelection: input.modelSelection, | ||
| 1411 | cwd: input.cwd, | ||
| 1412 | sessionId, | ||
| 1413 | entries, | ||
| 1414 | ...(input.enableTools === undefined ? {} : { enableTools: input.enableTools }), | ||
| 1415 | ...(input.tools === undefined ? {} : { tools: input.tools }), | ||
| 1416 | ...(input.permissionMode === undefined ? {} : { permissionMode: input.permissionMode }), | ||
| 1417 | ...(input.allowedTools === undefined ? {} : { allowedTools: input.allowedTools }), | ||
| 1418 | ...(input.disallowedTools === undefined ? {} : { disallowedTools: input.disallowedTools }), | ||
| 1419 | ...(input.allowDangerouslySkipPermissions === undefined | ||
| 1420 | ? {} | ||
| 1421 | : { allowDangerouslySkipPermissions: input.allowDangerouslySkipPermissions }), | ||
| 1422 | ...(input.enablePermissionCallback === undefined | ||
| 1423 | ? {} | ||
| 1424 | : { enablePermissionCallback: input.enablePermissionCallback }), | ||
| 1425 | ...(input.permissionDecision === undefined | ||
| 1426 | ? {} | ||
| 1427 | : { permissionDecision: input.permissionDecision }), | ||
| 1428 | }); | ||
| 1429 | } else if (queryMode === "restart") { | ||
| 1014 | await recordClaudeRestartingQueries({ | 1430 | await recordClaudeRestartingQueries({ |
| 1015 | scenario: input.scenario, | 1431 | scenario: input.scenario, |
| 1016 | prompts: input.prompts, | 1432 | prompts: input.prompts, |
| @@ -1027,6 +1443,23 @@ export async function recordClaudeAgentSdkReplayTranscript(input: { · 10 unmodified lines | |||
| 1027 | ? {} | 1443 | ? {} |
| 1028 | : { allowDangerouslySkipPermissions: input.allowDangerouslySkipPermissions }), | 1444 | : { allowDangerouslySkipPermissions: input.allowDangerouslySkipPermissions }), |
| 1029 | }); | 1445 | }); |
| 1446 | } else { | ||
| 1447 | await recordClaudeInterruptRestartQuery({ | ||
| 1448 | scenario: input.scenario, | ||
| 1449 | prompts: input.prompts, | ||
| 1450 | modelSelection: input.modelSelection, | ||
| 1451 | cwd: input.cwd, | ||
| 1452 | sessionId, | ||
| 1453 | entries, | ||
| 1454 | ...(input.enableTools === undefined ? {} : { enableTools: input.enableTools }), | ||
| 1455 | ...(input.tools === undefined ? {} : { tools: input.tools }), | ||
| 1456 | ...(input.permissionMode === undefined ? {} : { permissionMode: input.permissionMode }), | ||
| 1457 | ...(input.allowedTools === undefined ? {} : { allowedTools: input.allowedTools }), | ||
| 1458 | ...(input.disallowedTools === undefined ? {} : { disallowedTools: input.disallowedTools }), | ||
| 1459 | ...(input.allowDangerouslySkipPermissions === undefined | ||
| 1460 | ? {} | ||
| 1461 | : { allowDangerouslySkipPermissions: input.allowDangerouslySkipPermissions }), | ||
| 1462 | }); | ||
| 1030 | } | 1463 | } |
| 1031 | 1464 | ||
| 1032 | return { | 1465 | return { |
| @@ -37,6 +37,7 @@ import * as Deferred from "effect/Deferred"; · 36 unmodified lines | |||
| 37 | import * as Effect from "effect/Effect"; | 37 | import * as Effect from "effect/Effect"; |
| 38 | import * as Exit from "effect/Exit"; | 38 | import * as Exit from "effect/Exit"; |
| 39 | import * as Layer from "effect/Layer"; | 39 | import * as Layer from "effect/Layer"; |
| 40 | import * as Option from "effect/Option"; | ||
| 40 | import * as Path from "effect/Path"; | 41 | import * as Path from "effect/Path"; |
| 41 | import * as Queue from "effect/Queue"; | 42 | import * as Queue from "effect/Queue"; |
| 42 | import * as Random from "effect/Random"; | 43 | import * as Random from "effect/Random"; |
| @@ -55,7 +56,7 @@ import { · 12 unmodified lines | |||
| 55 | ProviderAdapterResumeThreadError, | 56 | ProviderAdapterResumeThreadError, |
| 56 | ProviderAdapterRollbackThreadError, | 57 | ProviderAdapterRollbackThreadError, |
| 57 | ProviderAdapterRuntimeRequestResponseError, | 58 | ProviderAdapterRuntimeRequestResponseError, |
| 58 | ProviderAdapterSteerRunUnsupportedError, | 59 | ProviderAdapterSteerRunError, |
| 59 | ProviderAdapterTurnStartError, | 60 | ProviderAdapterTurnStartError, |
| 60 | ProviderAdapterV2, | 61 | ProviderAdapterV2, |
| 61 | type ProviderAdapterV2EnsureThreadInput, | 62 | type ProviderAdapterV2EnsureThreadInput, |
| @@ -65,6 +66,7 @@ import { · 3 unmodified lines | |||
| 65 | type ProviderAdapterV2RuntimePolicy, | 66 | type ProviderAdapterV2RuntimePolicy, |
| 66 | type ProviderAdapterV2Shape, | 67 | type ProviderAdapterV2Shape, |
| 67 | type ProviderAdapterV2SessionRuntime, | 68 | type ProviderAdapterV2SessionRuntime, |
| 69 | type ProviderAdapterV2SteerInput, | ||
| 68 | type ProviderAdapterV2TurnInput, | 70 | type ProviderAdapterV2TurnInput, |
| 69 | } from "../ProviderAdapter.ts"; | 71 | } from "../ProviderAdapter.ts"; |
| 70 | import { | 72 | import { |
| @@ -97,8 +99,8 @@ export const ClaudeProviderCapabilitiesV2 = { · 26 unmodified lines | |||
| 97 | emitsTurnStarted: true, | 99 | emitsTurnStarted: true, |
| 98 | emitsTurnCompleted: true, | 100 | emitsTurnCompleted: true, |
| 99 | supportsInterrupt: true, | 101 | supportsInterrupt: true, |
| 100 | supportsActiveSteering: false, | 102 | supportsActiveSteering: true, |
| 101 | supportsSteeringByInterruptRestart: true, | 103 | supportsSteeringByInterruptRestart: false, |
| 102 | supportsQueuedMessages: true, | 104 | supportsQueuedMessages: true, |
| 103 | terminalStatusQuality: "strong", | 105 | terminalStatusQuality: "strong", |
| 104 | }, | 106 | }, |
| @@ -444,11 +446,6 @@ const getNativeThreadId = Effect.fnUntraced(function* ( · 339 unmodified lines | |||
| 444 | return nativeThreadId; | 446 | return nativeThreadId; |
| 445 | }); | 447 | }); |
| 446 | 448 | ||
| 447 | function shouldResumeClaudeThread(turnInput: ProviderAdapterV2TurnInput): boolean { | ||
| 448 | const firstRunOrdinal = turnInput.providerThread.firstRunOrdinal; | ||
| 449 | return firstRunOrdinal !== null && firstRunOrdinal < turnInput.runOrdinal; | ||
| 450 | } | ||
| 451 | |||
| 452 | export function makeClaudeUserMessage(input: { | 449 | export function makeClaudeUserMessage(input: { |
| 453 | readonly text: string; | 450 | readonly text: string; |
| 454 | readonly priority?: SDKUserMessage["priority"]; | 451 | readonly priority?: SDKUserMessage["priority"]; |
| @@ -1067,6 +1064,10 @@ function terminalStatusFromResult( · 612 unmodified lines | |||
| 1067 | return "failed"; | 1064 | return "failed"; |
| 1068 | } | 1065 | } |
| 1069 | 1066 | ||
| 1067 | function isClaudeActiveSteeringAbortResult(message: SDKResultMessage): boolean { | ||
| 1068 | return message.terminal_reason === "aborted_streaming"; | ||
| 1069 | } | ||
| 1070 | |||
| 1070 | function buildAssistantArtifacts(input: { | 1071 | function buildAssistantArtifacts(input: { |
| 1071 | readonly idAllocator: IdAllocatorV2Shape; | 1072 | readonly idAllocator: IdAllocatorV2Shape; |
| 1072 | readonly turnInput: ProviderAdapterV2TurnInput; | 1073 | readonly turnInput: ProviderAdapterV2TurnInput; |
| @@ -1156,6 +1157,7 @@ interface ActiveClaudeTurnContext { · 83 unmodified lines | |||
| 1156 | readonly input: ProviderAdapterV2TurnInput; | 1157 | readonly input: ProviderAdapterV2TurnInput; |
| 1157 | readonly nativeTurnId: string; | 1158 | readonly nativeTurnId: string; |
| 1158 | readonly providerTurnId: OrchestrationV2ProviderTurn["id"]; | 1159 | readonly providerTurnId: OrchestrationV2ProviderTurn["id"]; |
| 1160 | readonly providerTurnOrdinal: number; | ||
| 1159 | readonly startedAt: DateTime.Utc; | 1161 | readonly startedAt: DateTime.Utc; |
| 1160 | readonly assistant: { | 1162 | readonly assistant: { |
| 1161 | text: string; | 1163 | text: string; |
| @@ -1168,6 +1170,7 @@ interface ClaudeLiveQueryContext { · 6 unmodified lines | |||
| 1168 | readonly nativeThreadId: string; | 1170 | readonly nativeThreadId: string; |
| 1169 | readonly query: ClaudeAgentSdkQuerySession; | 1171 | readonly query: ClaudeAgentSdkQuerySession; |
| 1170 | readonly queryPolicyKey: string; | 1172 | readonly queryPolicyKey: string; |
| 1173 | readonly closed: Deferred.Deferred<void, never>; | ||
| 1171 | currentModel: string; | 1174 | currentModel: string; |
| 1172 | } | 1175 | } |
| 1173 | 1176 | ||
| @@ -1215,7 +1218,10 @@ export function makeClaudeAdapterV2( · 41 unmodified lines | |||
| 1215 | const events = yield* Queue.unbounded<ProviderAdapterV2Event>(); | 1218 | const events = yield* Queue.unbounded<ProviderAdapterV2Event>(); |
| 1216 | const activeTurn = yield* Ref.make<ActiveClaudeTurnContext | null>(null); | 1219 | const activeTurn = yield* Ref.make<ActiveClaudeTurnContext | null>(null); |
| 1217 | const interruptedTurns = yield* Ref.make(new Set<OrchestrationV2ProviderTurn["id"]>()); | 1220 | const interruptedTurns = yield* Ref.make(new Set<OrchestrationV2ProviderTurn["id"]>()); |
| 1221 | const steeredTurns = yield* Ref.make(new Set<OrchestrationV2ProviderTurn["id"]>()); | ||
| 1218 | const queryContext = yield* Ref.make<ClaudeLiveQueryContext | null>(null); | 1222 | const queryContext = yield* Ref.make<ClaudeLiveQueryContext | null>(null); |
| 1223 | const openedNativeThreads = yield* Ref.make(new Set<string>()); | ||
| 1224 | const nextProviderTurnOrdinals = yield* Ref.make(new Map<string, number>()); | ||
| 1219 | const itemOrdinals = yield* Ref.make(new Map<string, number>()); | 1225 | const itemOrdinals = yield* Ref.make(new Map<string, number>()); |
| 1220 | const nextItemOrdinalsByTurn = yield* Ref.make(new Map<string, number>()); | 1226 | const nextItemOrdinalsByTurn = yield* Ref.make(new Map<string, number>()); |
| 1221 | const pendingRuntimeRequests = yield* Ref.make( | 1227 | const pendingRuntimeRequests = yield* Ref.make( |
| @@ -1266,7 +1272,7 @@ export function makeClaudeAdapterV2( · 44 unmodified lines | |||
| 1266 | nativeId: input.context.nativeTurnId, | 1272 | nativeId: input.context.nativeTurnId, |
| 1267 | strength: "weak", | 1273 | strength: "weak", |
| 1268 | }, | 1274 | }, |
| 1269 | ordinal: input.context.input.runOrdinal, | 1275 | ordinal: input.context.providerTurnOrdinal, |
| 1270 | status: input.status, | 1276 | status: input.status, |
| 1271 | startedAt: input.context.startedAt, | 1277 | startedAt: input.context.startedAt, |
| 1272 | completedAt: input.completedAt, | 1278 | completedAt: input.completedAt, |
| @@ -1651,7 +1657,16 @@ export function makeClaudeAdapterV2( · 378 unmodified lines | |||
| 1651 | } | 1657 | } |
| 1652 | }); | 1658 | }); |
| 1653 | 1659 | ||
| 1654 | const handleSdkMessage = Effect.fnUntraced(function* (message: SDKMessage) { | 1660 | const handleSdkMessage = Effect.fnUntraced(function* (input: { |
| 1661 | readonly query: ClaudeAgentSdkQuerySession; | ||
| 1662 | readonly message: SDKMessage; | ||
| 1663 | }) { | ||
| 1664 | const liveQuery = yield* Ref.get(queryContext); | ||
| 1665 | if (liveQuery?.query !== input.query) { | ||
| 1666 | return; | ||
| 1667 | } | ||
| 1668 | |||
| 1669 | const message = input.message; | ||
| 1655 | const context = yield* Ref.get(activeTurn); | 1670 | const context = yield* Ref.get(activeTurn); |
| 1656 | if (context === null) { | 1671 | if (context === null) { |
| 1657 | return; | 1672 | return; |
| @@ -1710,9 +1725,19 @@ export function makeClaudeAdapterV2( · 52 unmodified lines | |||
| 1710 | 1725 | ||
| 1711 | if (message.type === "result") { | 1726 | if (message.type === "result") { |
| 1712 | const completedAt = yield* DateTime.now; | 1727 | const completedAt = yield* DateTime.now; |
| 1728 | const interrupted = (yield* Ref.get(interruptedTurns)).has(context.providerTurnId); | ||
| 1729 | const wasSteered = (yield* Ref.get(steeredTurns)).has(context.providerTurnId); | ||
| 1730 | if (!interrupted && wasSteered && isClaudeActiveSteeringAbortResult(message)) { | ||
| 1731 | return; | ||
| 1732 | } | ||
| 1733 | yield* Ref.update(steeredTurns, (current) => { | ||
| 1734 | const next = new Set(current); | ||
| 1735 | next.delete(context.providerTurnId); | ||
| 1736 | return next; | ||
| 1737 | }); | ||
| 1713 | yield* finalizeActiveTurn({ | 1738 | yield* finalizeActiveTurn({ |
| 1714 | context, | 1739 | context, |
| 1715 | status: terminalStatusFromResult(message), | 1740 | status: interrupted ? "interrupted" : terminalStatusFromResult(message), |
| 1716 | completedAt, | 1741 | completedAt, |
| 1717 | }); | 1742 | }); |
| 1718 | } | 1743 | } |
| @@ -1836,7 +1861,15 @@ export function makeClaudeAdapterV2( · 117 unmodified lines | |||
| 1836 | yield* existing.query.close.pipe(Effect.ignore); | 1861 | yield* existing.query.close.pipe(Effect.ignore); |
| 1837 | } | 1862 | } |
| 1838 | 1863 | ||
| 1839 | const openedWithResume = shouldResumeClaudeThread(turnInput); | 1864 | const openedWithResume = yield* Ref.modify(openedNativeThreads, (current) => { |
| 1865 | const hasOpenedThread = current.has(nativeThreadId); | ||
| 1866 | if (hasOpenedThread) { | ||
| 1867 | return [true, current]; | ||
| 1868 | } | ||
| 1869 | const updated = new Set(current); | ||
| 1870 | updated.add(nativeThreadId); | ||
| 1871 | return [false, updated]; | ||
| 1872 | }); | ||
| 1840 | const querySession = yield* queryRunner.open({ | 1873 | const querySession = yield* queryRunner.open({ |
| 1841 | options: makeClaudeQueryOptions({ | 1874 | options: makeClaudeQueryOptions({ |
| 1842 | modelSelection: turnInput.modelSelection, | 1875 | modelSelection: turnInput.modelSelection, |
| @@ -1856,26 +1889,31 @@ export function makeClaudeAdapterV2( · 13 unmodified lines | |||
| 1856 | ...(shouldInstallClaudePermissionCallback(queryPolicy) ? { canUseTool } : {}), | 1889 | ...(shouldInstallClaudePermissionCallback(queryPolicy) ? { canUseTool } : {}), |
| 1857 | }), | 1890 | }), |
| 1858 | }); | 1891 | }); |
| 1892 | const closed = yield* Deferred.make<void, never>(); | ||
| 1859 | const context: ClaudeLiveQueryContext = { | 1893 | const context: ClaudeLiveQueryContext = { |
| 1860 | nativeThreadId, | 1894 | nativeThreadId, |
| 1861 | query: querySession, | 1895 | query: querySession, |
| 1862 | queryPolicyKey, | 1896 | queryPolicyKey, |
| 1897 | closed, | ||
| 1863 | currentModel: turnInput.modelSelection.model, | 1898 | currentModel: turnInput.modelSelection.model, |
| 1864 | }; | 1899 | }; |
| 1865 | yield* Ref.set(queryContext, context); | 1900 | yield* Ref.set(queryContext, context); |
| 1866 | yield* querySession.messages.pipe( | 1901 | yield* querySession.messages.pipe( |
| 1867 | Stream.runForEach(handleSdkMessage), | 1902 | Stream.runForEach((message) => handleSdkMessage({ query: querySession, message })), |
| 1868 | Effect.exit, | 1903 | Effect.exit, |
| 1869 | Effect.flatMap( | 1904 | Effect.flatMap( |
| 1870 | Effect.fnUntraced(function* (exit: ClaudeQueryStreamExit) { | 1905 | Effect.fnUntraced(function* (exit: ClaudeQueryStreamExit) { |
| 1871 | yield* Ref.update(queryContext, (current) => | 1906 | const ownsLiveQuery = yield* Ref.modify(queryContext, (current) => |
| 1872 | current?.query === querySession ? null : current, | 1907 | current?.query === querySession ? [true, null] : [false, current], |
| 1873 | ); | ||
| 1874 | yield* finalizeActiveTurnAfterQueryExit( | ||
| 1875 | exit._tag === "Failure" ? exit.cause : undefined, | ||
| 1876 | ); | 1908 | ); |
| 1909 | if (ownsLiveQuery) { | ||
| 1910 | yield* finalizeActiveTurnAfterQueryExit( | ||
| 1911 | exit._tag === "Failure" ? exit.cause : undefined, | ||
| 1912 | ); | ||
| 1913 | } | ||
| 1877 | }), | 1914 | }), |
| 1878 | ), | 1915 | ), |
| 1916 | Effect.ensuring(Deferred.succeed(closed, undefined)), | ||
| 1879 | Effect.forkIn(sessionScope), | 1917 | Effect.forkIn(sessionScope), |
| 1880 | ); | 1918 | ); |
| 1881 | return context; | 1919 | return context; |
| @@ -1885,11 +1923,21 @@ export function makeClaudeAdapterV2( · 3 unmodified lines | |||
| 1885 | function* (turnInput: ProviderAdapterV2TurnInput) { | 1923 | function* (turnInput: ProviderAdapterV2TurnInput) { |
| 1886 | const startedAt = yield* DateTime.now; | 1924 | const startedAt = yield* DateTime.now; |
| 1887 | const nativeThreadId = yield* getNativeThreadId(turnInput.providerThread); | 1925 | const nativeThreadId = yield* getNativeThreadId(turnInput.providerThread); |
| 1888 | const nativeTurnId = `turn:${turnInput.runId}`; | 1926 | const nativeTurnId = `turn:${turnInput.attemptId}`; |
| 1889 | const providerTurnId = idAllocator.derive.providerTurn({ | 1927 | const providerTurnId = idAllocator.derive.providerTurn({ |
| 1890 | provider: CLAUDE_PROVIDER, | 1928 | provider: CLAUDE_PROVIDER, |
| 1891 | nativeTurnId, | 1929 | nativeTurnId, |
| 1892 | }); | 1930 | }); |
| 1931 | const providerTurnOrdinal = yield* Ref.modify(nextProviderTurnOrdinals, (current) => { | ||
| 1932 | const previous = current.get(String(turnInput.providerThread.id)); | ||
| 1933 | const next = | ||
| 1934 | previous === undefined | ||
| 1935 | ? turnInput.runOrdinal | ||
| 1936 | : Math.max(previous + 1, turnInput.runOrdinal); | ||
| 1937 | const updated = new Map(current); | ||
| 1938 | updated.set(String(turnInput.providerThread.id), next); | ||
| 1939 | return [next, updated]; | ||
| 1940 | }); | ||
| 1893 | const currentTurn = yield* Ref.get(activeTurn); | 1941 | const currentTurn = yield* Ref.get(activeTurn); |
| 1894 | if (currentTurn !== null) { | 1942 | if (currentTurn !== null) { |
| 1895 | return yield* new ProviderAdapterProtocolError({ | 1943 | return yield* new ProviderAdapterProtocolError({ |
| @@ -1901,6 +1949,7 @@ export function makeClaudeAdapterV2( · 5 unmodified lines | |||
| 1901 | input: turnInput, | 1949 | input: turnInput, |
| 1902 | nativeTurnId, | 1950 | nativeTurnId, |
| 1903 | providerTurnId, | 1951 | providerTurnId, |
| 1952 | providerTurnOrdinal, | ||
| 1904 | startedAt, | 1953 | startedAt, |
| 1905 | assistant: { | 1954 | assistant: { |
| 1906 | text: "", | 1955 | text: "", |
| @@ -1947,12 +1996,42 @@ export function makeClaudeAdapterV2( · 40 unmodified lines | |||
| 1947 | detail: `Claude provider thread ${turnInput.providerThread.id} has no live query.`, | 1996 | detail: `Claude provider thread ${turnInput.providerThread.id} has no live query.`, |
| 1948 | }); | 1997 | }); |
| 1949 | } | 1998 | } |
| 1999 | const currentTurn = yield* Ref.get(activeTurn); | ||
| 2000 | if (currentTurn?.providerTurnId !== turnInput.providerTurnId) { | ||
| 2001 | return yield* new ProviderAdapterProtocolError({ | ||
| 2002 | provider: CLAUDE_PROVIDER, | ||
| 2003 | detail: `Claude provider turn ${turnInput.providerTurnId} is not the active turn.`, | ||
| 2004 | }); | ||
| 2005 | } | ||
| 1950 | yield* Ref.update(interruptedTurns, (current) => { | 2006 | yield* Ref.update(interruptedTurns, (current) => { |
| 1951 | const next = new Set(current); | 2007 | const next = new Set(current); |
| 1952 | next.add(turnInput.providerTurnId); | 2008 | next.add(turnInput.providerTurnId); |
| 1953 | return next; | 2009 | return next; |
| 1954 | }); | 2010 | }); |
| 1955 | yield* existing.query.interrupt; | 2011 | yield* existing.query.interrupt; |
| 2012 | yield* existing.query.close.pipe(Effect.ignore); | ||
| 2013 | const closed = yield* Deferred.await(existing.closed).pipe( | ||
| 2014 | Effect.timeoutOption("10 seconds"), | ||
| 2015 | ); | ||
| 2016 | if (Option.isSome(closed)) { | ||
| 2017 | return; | ||
| 2018 | } | ||
| 2019 | |||
| 2020 | const completedAt = yield* DateTime.now; | ||
| 2021 | yield* Effect.logWarning("orchestration-v2.claude-query-interrupt-timeout", { | ||
| 2022 | providerSessionId: input.providerSessionId, | ||
| 2023 | providerThreadId: turnInput.providerThread.id, | ||
| 2024 | providerTurnId: turnInput.providerTurnId, | ||
| 2025 | }); | ||
| 2026 | yield* Ref.update(queryContext, (current) => | ||
| 2027 | current?.query === existing.query ? null : current, | ||
| 2028 | ); | ||
| 2029 | yield* finalizeActiveTurn({ | ||
| 2030 | context: currentTurn, | ||
| 2031 | status: "interrupted", | ||
| 2032 | completedAt, | ||
| 2033 | }); | ||
| 2034 | yield* Deferred.succeed(existing.closed, undefined); | ||
| 1956 | }, | 2035 | }, |
| 1957 | (effect, turnInput) => | 2036 | (effect, turnInput) => |
| 1958 | effect.pipe( | 2037 | effect.pipe( |
| @@ -1968,6 +2047,45 @@ export function makeClaudeAdapterV2( · 9 unmodified lines | |||
| 1968 | ), | 2047 | ), |
| 1969 | ); | 2048 | ); |
| 1970 | 2049 | ||
| 2050 | const steerTurn = Effect.fn("ClaudeAdapterV2.steerTurn")( | ||
| 2051 | function* (turnInput: ProviderAdapterV2SteerInput) { | ||
| 2052 | const existing = yield* Ref.get(queryContext); | ||
| 2053 | if (existing === null) { | ||
| 2054 | return yield* new ProviderAdapterProtocolError({ | ||
| 2055 | provider: CLAUDE_PROVIDER, | ||
| 2056 | detail: `Claude provider thread ${turnInput.providerThread.id} has no live query.`, | ||
| 2057 | }); | ||
| 2058 | } | ||
| 2059 | const currentTurn = yield* Ref.get(activeTurn); | ||
| 2060 | if (currentTurn?.providerTurnId !== turnInput.providerTurnId) { | ||
| 2061 | return yield* new ProviderAdapterProtocolError({ | ||
| 2062 | provider: CLAUDE_PROVIDER, | ||
| 2063 | detail: `Claude provider turn ${turnInput.providerTurnId} is not the active turn.`, | ||
| 2064 | }); | ||
| 2065 | } | ||
| 2066 | yield* Ref.update(steeredTurns, (current) => { | ||
| 2067 | const next = new Set(current); | ||
| 2068 | next.add(turnInput.providerTurnId); | ||
| 2069 | return next; | ||
| 2070 | }); | ||
| 2071 | yield* existing.query.offer( | ||
| 2072 | makeClaudeUserMessage({ text: turnInput.message.text, priority: "now" }), | ||
| 2073 | ); | ||
| 2074 | }, | ||
| 2075 | (effect, turnInput) => | ||
| 2076 | effect.pipe( | ||
| 2077 | Effect.mapError( | ||
| 2078 | (cause) => | ||
| 2079 | new ProviderAdapterSteerRunError({ | ||
| 2080 | provider: CLAUDE_PROVIDER, | ||
| 2081 | providerThreadId: turnInput.providerThread.id, | ||
| 2082 | providerTurnId: turnInput.providerTurnId, | ||
| 2083 | cause, | ||
| 2084 | }), | ||
| 2085 | ), | ||
| 2086 | ), | ||
| 2087 | ); | ||
| 2088 | |||
| 1971 | const closeSession = Effect.fnUntraced(function* () { | 2089 | const closeSession = Effect.fnUntraced(function* () { |
| 1972 | const existing = yield* Ref.get(queryContext); | 2090 | const existing = yield* Ref.get(queryContext); |
| 1973 | if (existing !== null) { | 2091 | if (existing !== null) { |
| @@ -2040,13 +2158,7 @@ export function makeClaudeAdapterV2( · 66 unmodified lines | |||
| 2040 | ), | 2158 | ), |
| 2041 | ), | 2159 | ), |
| 2042 | startTurn, | 2160 | startTurn, |
| 2043 | steerTurn: (turnInput) => | 2161 | steerTurn, |
| 2044 | Effect.fail( | ||
| 2045 | new ProviderAdapterSteerRunUnsupportedError({ | ||
| 2046 | provider: CLAUDE_PROVIDER, | ||
| 2047 | providerThreadId: turnInput.providerThread.id, | ||
| 2048 | }), | ||
| 2049 | ), | ||
| 2050 | interruptTurn, | 2162 | interruptTurn, |
| 2051 | respondToRuntimeRequest: Effect.fn("ClaudeAdapterV2.respondToRuntimeRequest")( | 2163 | respondToRuntimeRequest: Effect.fn("ClaudeAdapterV2.respondToRuntimeRequest")( |
| 2052 | function* (requestInput) { | 2164 | function* (requestInput) { |
| @@ -1099,16 +1099,46 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio · 1098 unmodified lines | |||
| 1099 | ), | 1099 | ), |
| 1100 | ); | 1100 | ); |
| 1101 | 1101 | ||
| 1102 | const currentAttempt = input.projection.attempts.find( | 1102 | const waitForInterruptedProviderTurn = ( |
| 1103 | attemptsRemaining = 1_000, | ||
| 1104 | ): Effect.Effect<OrchestrationV2ThreadProjection, OrchestratorV2Error> => | ||
| 1105 | Effect.gen(function* () { | ||
| 1106 | const current = yield* projectionStore | ||
| 1107 | .getThreadProjection(input.command.threadId) | ||
| 1108 | .pipe( | ||
| 1109 | Effect.mapError( | ||
| 1110 | () => new OrchestratorProjectionError({ threadId: input.command.threadId }), | ||
| 1111 | ), | ||
| 1112 | ); | ||
| 1113 | const interruptedTurn = current.providerTurns.find( | ||
| 1114 | (candidate) => candidate.id === providerTurn.id, | ||
| 1115 | ); | ||
| 1116 | if (interruptedTurn !== undefined && interruptedTurn.status !== "running") { | ||
| 1117 | return current; | ||
| 1118 | } | ||
| 1119 | if (attemptsRemaining <= 0) { | ||
| 1120 | return yield* new OrchestratorDispatchError({ | ||
| 1121 | commandId: input.command.commandId, | ||
| 1122 | commandType: input.command.type, | ||
| 1123 | cause: `Provider turn ${providerTurn.id} did not terminalize before steering restart.`, | ||
| 1124 | }); | ||
| 1125 | } | ||
| 1126 | yield* Effect.yieldNow; | ||
| 1127 | return yield* waitForInterruptedProviderTurn(attemptsRemaining - 1); | ||
| 1128 | }); | ||
| 1129 | |||
| 1130 | const postInterruptProjection = yield* waitForInterruptedProviderTurn(); | ||
| 1131 | |||
| 1132 | const currentAttempt = postInterruptProjection.attempts.find( | ||
| 1103 | (candidate) => candidate.id === targetRun.activeAttemptId, | 1133 | (candidate) => candidate.id === targetRun.activeAttemptId, |
| 1104 | ); | 1134 | ); |
| 1105 | const currentRootNode = input.projection.nodes.find( | 1135 | const currentRootNode = postInterruptProjection.nodes.find( |
| 1106 | (candidate) => candidate.id === rootNodeId, | 1136 | (candidate) => candidate.id === rootNodeId, |
| 1107 | ); | 1137 | ); |
| 1108 | const attemptOrdinal = | 1138 | const attemptOrdinal = |
| 1109 | Math.max( | 1139 | Math.max( |
| 1110 | 0, | 1140 | 0, |
| 1111 | ...input.projection.attempts | 1141 | ...postInterruptProjection.attempts |
| 1112 | .filter((candidate) => candidate.runId === targetRun.id) | 1142 | .filter((candidate) => candidate.runId === targetRun.id) |
| 1113 | .map((candidate) => candidate.attemptOrdinal), | 1143 | .map((candidate) => candidate.attemptOrdinal), |
| 1114 | ) + 1; | 1144 | ) + 1; |
| @@ -123,16 +123,10 @@ const scenarioExpectations = { · 122 unmodified lines | |||
| 123 | }, | 123 | }, |
| 124 | message_steering: { | 124 | message_steering: { |
| 125 | outgoing: ["initialize", "initialized", "thread/start", "turn/start", "turn/steer"], | 125 | outgoing: ["initialize", "initialized", "thread/start", "turn/start", "turn/steer"], |
| 126 | incoming: [ | 126 | incoming: ["turn/started", "turn/completed", "item/agentMessage/delta"], |
| 127 | "turn/started", | ||
| 128 | "turn/completed", | ||
| 129 | "item/agentMessage/delta", | ||
| 130 | "item/commandExecution/requestApproval", | ||
| 131 | "serverRequest/resolved", | ||
| 132 | ], | ||
| 133 | turnStartCount: 1, | 127 | turnStartCount: 1, |
| 134 | turnCompletedCount: 1, | 128 | turnCompletedCount: 1, |
| 135 | approvalRequestCount: 1, | 129 | approvalRequestCount: 0, |
| 136 | }, | 130 | }, |
| 137 | turn_interrupt: { | 131 | turn_interrupt: { |
| 138 | outgoing: ["initialize", "initialized", "thread/start", "turn/start", "turn/interrupt"], | 132 | outgoing: ["initialize", "initialized", "thread/start", "turn/start", "turn/interrupt"], |
| @@ -2,6 +2,7 @@ import type { · 1 unmodified line | |||
| 2 | OrchestrationV2Command, | 2 | OrchestrationV2Command, |
| 3 | OrchestrationV2DomainEvent, | 3 | OrchestrationV2DomainEvent, |
| 4 | OrchestrationV2RuntimeRequest, | 4 | OrchestrationV2RuntimeRequest, |
| 5 | OrchestrationV2Run, | ||
| 5 | OrchestrationV2ShellSnapshot, | 6 | OrchestrationV2ShellSnapshot, |
| 6 | OrchestrationV2StoredEvent, | 7 | OrchestrationV2StoredEvent, |
| 7 | OrchestrationV2ThreadProjection, | 8 | OrchestrationV2ThreadProjection, |
| @@ -37,6 +38,11 @@ export type OrchestratorV2ScenarioStep = · 29 unmodified lines | |||
| 37 | readonly type: "await_thread_idle"; | 38 | readonly type: "await_thread_idle"; |
| 38 | readonly threadId: ThreadId; | 39 | readonly threadId: ThreadId; |
| 39 | } | 40 | } |
| 41 | | { | ||
| 42 | readonly type: "await_run_steerable"; | ||
| 43 | readonly threadId: ThreadId; | ||
| 44 | readonly runId: OrchestrationV2Run["id"]; | ||
| 45 | } | ||
| 40 | | { | 46 | | { |
| 41 | readonly type: "respond_to_next_runtime_request"; | 47 | readonly type: "respond_to_next_runtime_request"; |
| 42 | readonly threadId: ThreadId; | 48 | readonly threadId: ThreadId; |
| @@ -208,6 +214,33 @@ export function runOrchestratorV2Scenario( · 165 unmodified lines | |||
| 208 | return yield* waitForThreadIdle(threadId, attemptsRemaining - 1); | 214 | return yield* waitForThreadIdle(threadId, attemptsRemaining - 1); |
| 209 | }); | 215 | }); |
| 210 | 216 | ||
| 217 | const waitForRunSteerable = ( | ||
| 218 | threadId: ThreadId, | ||
| 219 | runId: OrchestrationV2Run["id"], | ||
| 220 | attemptsRemaining = 1_000, | ||
| 221 | ): Effect.Effect<void, OrchestratorV2Error | OrchestratorV2ScenarioStepError, never> => | ||
| 222 | Effect.gen(function* () { | ||
| 223 | const projection = yield* orchestrator.getThreadProjection(threadId); | ||
| 224 | const run = projection.runs.find((candidate) => candidate.id === runId); | ||
| 225 | const providerTurn = projection.providerTurns.find( | ||
| 226 | (candidate) => | ||
| 227 | run?.activeAttemptId !== null && | ||
| 228 | candidate.runAttemptId === run?.activeAttemptId && | ||
| 229 | candidate.status === "running", | ||
| 230 | ); | ||
| 231 | if (run?.status === "running" && providerTurn !== undefined) { | ||
| 232 | return; | ||
| 233 | } | ||
| 234 | if (attemptsRemaining <= 0) { | ||
| 235 | return yield* new OrchestratorV2ScenarioStepError({ | ||
| 236 | scenario: scenario.name, | ||
| 237 | step: `await_run_steerable:${runId}`, | ||
| 238 | }); | ||
| 239 | } | ||
| 240 | yield* Effect.yieldNow; | ||
| 241 | return yield* waitForRunSteerable(threadId, runId, attemptsRemaining - 1); | ||
| 242 | }); | ||
| 243 | |||
| 211 | for (const step of scenarioSteps(scenario)) { | 244 | for (const step of scenarioSteps(scenario)) { |
| 212 | switch (step.type) { | 245 | switch (step.type) { |
| 213 | case "dispatch": { | 246 | case "dispatch": { |
| @@ -242,6 +275,9 @@ export function runOrchestratorV2Scenario( · 28 unmodified lines | |||
| 242 | case "await_thread_idle": | 275 | case "await_thread_idle": |
| 243 | yield* waitForThreadIdle(step.threadId); | 276 | yield* waitForThreadIdle(step.threadId); |
| 244 | break; | 277 | break; |
| 278 | case "await_run_steerable": | ||
| 279 | yield* waitForRunSteerable(step.threadId, step.runId); | ||
| 280 | break; | ||
| 245 | case "respond_to_next_runtime_request": { | 281 | case "respond_to_next_runtime_request": { |
| 246 | const request = yield* waitForPendingRuntimeRequest(step.threadId); | 282 | const request = yield* waitForPendingRuntimeRequest(step.threadId); |
| 247 | const result = yield* orchestrator.dispatch({ | 283 | const result = yield* orchestrator.dispatch({ |
| @@ -1,3 +1,4 @@ | |||
| 1 | import { assertClaudeMessageSteeringOutput } from "./message_steering/claude_output.ts"; | ||
| 1 | import { assertMessageSteeringOutput } from "./message_steering/codex_output.ts"; | 2 | import { assertMessageSteeringOutput } from "./message_steering/codex_output.ts"; |
| 2 | import { messageSteeringInput } from "./message_steering/input.ts"; | 3 | import { messageSteeringInput } from "./message_steering/input.ts"; |
| 3 | import { assertMultiTurnClaudeOutput } from "./multi_turn/claude_output.ts"; | 4 | import { assertMultiTurnClaudeOutput } from "./multi_turn/claude_output.ts"; |
| @@ -207,6 +208,12 @@ export const ORCHESTRATOR_REPLAY_FIXTURES = [ · 203 unmodified lines | |||
| 207 | modelSelection: CODEX_MODEL_SELECTION, | 208 | modelSelection: CODEX_MODEL_SELECTION, |
| 208 | assertOutput: assertQueuedTurnOutput, | 209 | assertOutput: assertQueuedTurnOutput, |
| 209 | }, | 210 | }, |
| 211 | { | ||
| 212 | provider: "claudeAgent", | ||
| 213 | transcriptFile: new URL("./queued_turn/claude_transcript.ndjson", import.meta.url), | ||
| 214 | modelSelection: CLAUDE_MODEL_SELECTION, | ||
| 215 | assertOutput: assertQueuedTurnOutput, | ||
| 216 | }, | ||
| 210 | ], | 217 | ], |
| 211 | }, | 218 | }, |
| 212 | { | 219 | { |
| @@ -274,9 +281,14 @@ export const ORCHESTRATOR_REPLAY_FIXTURES = [ · 61 unmodified lines | |||
| 274 | provider: "codex", | 281 | provider: "codex", |
| 275 | transcriptFile: new URL("./message_steering/codex_transcript.ndjson", import.meta.url), | 282 | transcriptFile: new URL("./message_steering/codex_transcript.ndjson", import.meta.url), |
| 276 | modelSelection: CODEX_MODEL_SELECTION, | 283 | modelSelection: CODEX_MODEL_SELECTION, |
| 277 | runtimePolicyOverride: READ_ONLY_ON_REQUEST_POLICY, | ||
| 278 | assertOutput: assertMessageSteeringOutput, | 284 | assertOutput: assertMessageSteeringOutput, |
| 279 | }, | 285 | }, |
| 286 | { | ||
| 287 | provider: "claudeAgent", | ||
| 288 | transcriptFile: new URL("./message_steering/claude_transcript.ndjson", import.meta.url), | ||
| 289 | modelSelection: CLAUDE_MODEL_SELECTION, | ||
| 290 | assertOutput: assertClaudeMessageSteeringOutput, | ||
| 291 | }, | ||
| 280 | ], | 292 | ], |
| 281 | }, | 293 | }, |
| 282 | { | 294 | { |
| @@ -313,10 +325,9 @@ export const ORCHESTRATOR_REPLAY_FIXTURES = [ · 30 unmodified lines | |||
| 313 | // `tool_call_restricted_granular/claude_transcript.ndjson`, and | 325 | // `tool_call_restricted_granular/claude_transcript.ndjson`, and |
| 314 | // docs/orchestration-v2/provider-capability-system.md. | 326 | // docs/orchestration-v2/provider-capability-system.md. |
| 315 | 327 | ||
| 316 | // TODO(claude-v2/control): add Claude providers to queued-turn, interrupt, and steering fixtures | 328 | // TODO(claude-v2/control): add a Claude interrupt fixture once explicit run interrupt behavior |
| 317 | // once the real adapter exposes those behaviors through capability-checked V2 paths. | 329 | // is fully hardened. Cross-reference `turn_interrupt/codex_transcript.ndjson` and |
| 318 | // Cross-reference `queued_turn/codex_transcript.ndjson`, `turn_interrupt/codex_transcript.ndjson`, | 330 | // docs/orchestration-v2/feature-lifecycles.md. |
| 319 | // `message_steering/codex_transcript.ndjson`, and docs/orchestration-v2/feature-lifecycles.md. | ||
| 320 | 331 | ||
| 321 | // TODO(claude-v2/context-transfer): add provider-switch handoff and return fixtures when portable | 332 | // TODO(claude-v2/context-transfer): add provider-switch handoff and return fixtures when portable |
| 322 | // context handoff is implemented. Cross-reference docs/orchestration-v2/provider-switching-and-context.md | 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 | import type { OrchestratorV2ScenarioResult } from "../../OrchestratorScenario.ts"; | 4 | import type { OrchestratorV2ScenarioResult } from "../../OrchestratorScenario.ts"; |
| 5 | import { | 5 | import { |
| 6 | assertAllRuntimeRequestsResolved, | 6 | assertAssistantTextIncludes, |
| 7 | assertBaseProjection, | 7 | assertBaseProjection, |
| 8 | assertRuntimeRequestCounts, | 8 | assertRuntimeRequestCounts, |
| 9 | assertRuntimeRequestKinds, | ||
| 10 | assertSemanticProjectionIntegrity, | 9 | assertSemanticProjectionIntegrity, |
| 11 | assertTurnItemTypes, | 10 | assertTurnItemTypes, |
| 12 | assertUserMessageInputIntents, | 11 | assertUserMessageInputIntents, |
| 13 | assertUserMessagesInclude, | 12 | assertUserMessagesInclude, |
| 14 | assertVisibleTurnItemsMirrorLocalTurnItems, | 13 | assertVisibleTurnItemsMirrorLocalTurnItems, |
| 14 | MESSAGE_STEERING_INITIAL_PROMPT, | ||
| 15 | MESSAGE_STEERING_STEER_PROMPT, | 15 | MESSAGE_STEERING_STEER_PROMPT, |
| 16 | projectionFor, | 16 | projectionFor, |
| 17 | TOOL_CALL_WRITE_PROMPT, | ||
| 18 | } from "../shared.ts"; | 17 | } from "../shared.ts"; |
| 19 | 18 | ||
| 20 | export function assertMessageSteeringOutput( | 19 | export function assertMessageSteeringOutput( |
| 21 | result: OrchestratorV2ScenarioResult, | 20 | result: OrchestratorV2ScenarioResult, |
| 22 | transcript: ProviderReplayTranscript, | 21 | transcript: ProviderReplayTranscript, |
| 23 | ) { | 22 | ) { |
| 23 | assert.equal(transcript.provider, "codex"); | ||
| 24 | assertBaseProjection({ result, transcript, runCount: 1, runStatuses: ["completed"] }); | 24 | assertBaseProjection({ result, transcript, runCount: 1, runStatuses: ["completed"] }); |
| 25 | 25 | ||
| 26 | const projection = projectionFor(result, transcript.scenario); | 26 | const projection = projectionFor(result, transcript.scenario); |
| 27 | assertSemanticProjectionIntegrity(projection); | 27 | assertSemanticProjectionIntegrity(projection); |
| 28 | assertVisibleTurnItemsMirrorLocalTurnItems(projection); | 28 | assertVisibleTurnItemsMirrorLocalTurnItems(projection); |
| 29 | assertTurnItemTypes(projection, [ | 29 | assertTurnItemTypes(projection, ["user_message", "assistant_message"]); |
| 30 | "user_message", | 30 | assertRuntimeRequestCounts(projection, { total: 0 }); |
| 31 | "command_execution", | 31 | assertUserMessagesInclude(projection, [ |
| 32 | "approval_request", | 32 | MESSAGE_STEERING_INITIAL_PROMPT, |
| 33 | "assistant_message", | 33 | MESSAGE_STEERING_STEER_PROMPT, |
| 34 | ]); | 34 | ]); |
| 35 | assertRuntimeRequestCounts(projection, { total: 1, resolved: 1 }); | ||
| 36 | assertRuntimeRequestKinds(projection, ["command"]); | ||
| 37 | assertAllRuntimeRequestsResolved(projection); | ||
| 38 | assertUserMessagesInclude(projection, [TOOL_CALL_WRITE_PROMPT, MESSAGE_STEERING_STEER_PROMPT]); | ||
| 39 | assertUserMessageInputIntents(projection, ["turn_start", "steer"]); | 35 | assertUserMessageInputIntents(projection, ["turn_start", "steer"]); |
| 36 | assertAssistantTextIncludes(projection, "steering fixture observed"); | ||
| 40 | assert.equal(projection.runs.length, 1, "steering must attach to the active run"); | 37 | assert.equal(projection.runs.length, 1, "steering must attach to the active run"); |
| 41 | assert.equal( | 38 | assert.equal( |
| 42 | projection.providerTurns.length, | 39 | projection.providerTurns.length, |
| @@ -1,49 +1,51 @@ | |||
| 1 | {"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.120.0","scenario":"message_steering","metadata":{"source":"codex-app-server-probe","fileName":"message_steering.ndjson","description":"One active turn receives an immediate turn/steer request."}} | 1 | {"type":"transcript_start","provider":"codex","protocol":"codex.app-server","version":"0.128.0","scenario":"message_steering","metadata":{"source":"record-codex-app-server-replay-fixture","fileName":"message_steering.ndjson","description":"One active turn receives an immediate turn/steer request."}} |
| 2 | {"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}} | 2 | {"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}} |
| 3 | {"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex","platformFamily":"unix","platformOs":"macos"}}} | 3 | {"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.128.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/Users/julius/.codex_p","platformFamily":"unix","platformOs":"macos"}}} |
| 4 | {"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}} | 4 | {"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}} |
| 5 | {"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}} | 5 | {"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{}}} |
| 6 | {"type":"emit_inbound","label":"thread/start","frame":{"id":2,"result":{"thread":{"id":"019dadec-ebd7-75f0-812e-27a7e57bb374","forkedFromId":null,"preview":"","ephemeral":false,"modelProvider":"openai","createdAt":1776739478,"updatedAt":1776739478,"status":{"type":"idle"},"path":"/Users/julius/.codex/sessions/2026/04/20/rollout-2026-04-20T19-44-38-019dadec-ebd7-75f0-812e-27a7e57bb374.jsonl","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/packages/effect-codex-app-server","cliVersion":"0.120.0","source":"vscode","agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"turns":[]},"model":"gpt-5.4","modelProvider":"openai","serviceTier":"fast","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/packages/effect-codex-app-server","approvalPolicy":"on-request","approvalsReviewer":"user","sandbox":{"type":"workspaceWrite","writableRoots":["/Users/julius/.codex/memories"],"readOnlyAccess":{"type":"fullAccess"},"networkAccess":false,"excludeTmpdirEnvVar":false,"excludeSlashTmp":false},"reasoningEffort":"xhigh"}}} | 6 | {"type":"emit_inbound","label":"remoteControl/status/changed","frame":{"method":"remoteControl/status/changed","params":{"status":"disabled","environmentId":null}}} |
| 7 | {"type":"expect_outbound","label":"turn/start","frame":{"id":3,"method":"turn/start","params":{"approvalPolicy":"on-request","input":[{"text":"Create or overwrite .codex-probe-write-action.txt with exactly this text: codex app-server approval fixture. Use a local shell command or file edit only, then briefly report what happened. Do not read package metadata, use GitHub, use web, or use MCP.","type":"text"}],"sandboxPolicy":{"access":{"type":"fullAccess"},"networkAccess":false,"type":"readOnly"},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374"}}} | 7 | {"type":"emit_inbound","label":"thread/start","frame":{"id":2,"result":{"thread":{"id":"019e014d-b214-71f0-9538-23527e8247ca","forkedFromId":null,"preview":"","ephemeral":false,"modelProvider":"openai","createdAt":1778138329,"updatedAt":1778138329,"status":{"type":"idle"},"path":"/Users/julius/.codex_p/sessions/2026/05/07/rollout-2026-05-07T00-18-49-019e014d-b214-71f0-9538-23527e8247ca.jsonl","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/apps/server","cliVersion":"0.128.0","source":"vscode","agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"turns":[]},"model":"gpt-5.5","modelProvider":"openai","serviceTier":"fast","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/apps/server","instructionSources":["/Users/julius/.codex_p/AGENTS.md","/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/AGENTS.md"],"approvalPolicy":"on-request","approvalsReviewer":"user","sandbox":{"type":"workspaceWrite","writableRoots":["/Users/julius/.codex_p/memories"],"networkAccess":false,"excludeTmpdirEnvVar":false,"excludeSlashTmp":false},"permissionProfile":{"type":"managed","network":{"enabled":false},"fileSystem":{"type":"restricted","entries":[{"path":{"type":"special","value":{"kind":"root"}},"access":"read"},{"path":{"type":"special","value":{"kind":"project_roots","subpath":null}},"access":"write"},{"path":{"type":"special","value":{"kind":"slash_tmp"}},"access":"write"},{"path":{"type":"special","value":{"kind":"tmpdir"}},"access":"write"},{"path":{"type":"special","value":{"kind":"project_roots","subpath":".git"}},"access":"read"},{"path":{"type":"special","value":{"kind":"project_roots","subpath":".agents"}},"access":"read"},{"path":{"type":"special","value":{"kind":"project_roots","subpath":".codex"}},"access":"read"},{"path":{"type":"path","path":"/Users/julius/.codex_p/memories"},"access":"write"}]}},"activePermissionProfile":{"id":":workspace","extends":null,"modifications":[]},"reasoningEffort":"medium"}}} |
| 8 | {"type":"emit_inbound","label":"thread/started","frame":{"method":"thread/started","params":{"thread":{"id":"019dadec-ebd7-75f0-812e-27a7e57bb374","forkedFromId":null,"preview":"","ephemeral":false,"modelProvider":"openai","createdAt":1776739478,"updatedAt":1776739478,"status":{"type":"idle"},"path":"/Users/julius/.codex/sessions/2026/04/20/rollout-2026-04-20T19-44-38-019dadec-ebd7-75f0-812e-27a7e57bb374.jsonl","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/packages/effect-codex-app-server","cliVersion":"0.120.0","source":"vscode","agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"turns":[]}}}} | 8 | {"type":"expect_outbound","label":"turn/start","frame":{"id":3,"method":"turn/start","params":{"input":[{"text":"Respond with exactly: steering fixture initial response","type":"text"}],"threadId":"019e014d-b214-71f0-9538-23527e8247ca"}}} |
| 9 | {"type":"emit_inbound","label":"thread/started","frame":{"method":"thread/started","params":{"thread":{"id":"019e014d-b214-71f0-9538-23527e8247ca","forkedFromId":null,"preview":"","ephemeral":false,"modelProvider":"openai","createdAt":1778138329,"updatedAt":1778138329,"status":{"type":"idle"},"path":"/Users/julius/.codex_p/sessions/2026/05/07/rollout-2026-05-07T00-18-49-019e014d-b214-71f0-9538-23527e8247ca.jsonl","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/apps/server","cliVersion":"0.128.0","source":"vscode","agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"turns":[]}}}} | ||
| 9 | {"type":"emit_inbound","label":"deprecationNotice","frame":{"method":"deprecationNotice","params":{"summary":"`[features].collab` is deprecated. Use `[features].multi_agent` instead.","details":"Enable it with `--enable multi_agent` or `[features].multi_agent` in config.toml. See https://developers.openai.com/codex/config-basic#feature-flags for details."}}} | 10 | {"type":"emit_inbound","label":"deprecationNotice","frame":{"method":"deprecationNotice","params":{"summary":"`[features].collab` is deprecated. Use `[features].multi_agent` instead.","details":"Enable it with `--enable multi_agent` or `[features].multi_agent` in config.toml. See https://developers.openai.com/codex/config-basic#feature-flags for details."}}} |
| 10 | {"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"codex_apps","status":"starting","error":null}}} | 11 | {"type":"emit_inbound","label":"warning","frame":{"method":"warning","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","message":"Under-development features enabled: apply_patch_freeform. Under-development features are incomplete and may behave unpredictably. To suppress this warning, set `suppress_unstable_features_warning = true` in /Users/julius/.codex_p/config.toml."}}} |
| 11 | {"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"uidotsh","status":"starting","error":null}}} | 12 | {"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"uidotsh","status":"starting","error":null}}} |
| 12 | {"type":"emit_inbound","label":"turn/start","frame":{"id":3,"result":{"turn":{"id":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","items":[],"status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} | 13 | {"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"codex_apps","status":"starting","error":null}}} |
| 13 | {"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","status":{"type":"active","activeFlags":[]}}}} | 14 | {"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"computer-use","status":"starting","error":null}}} |
| 14 | {"type":"emit_inbound","label":"turn/started","frame":{"method":"turn/started","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turn":{"id":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","items":[],"status":"inProgress","error":null,"startedAt":1776739478,"completedAt":null,"durationMs":null}}}} | 15 | {"type":"emit_inbound","label":"turn/start","frame":{"id":3,"result":{"turn":{"id":"019e014d-b227-7af1-a889-6c7d828eb1a2","items":[],"status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} |
| 15 | {"type":"expect_outbound","label":"turn/steer","frame":{"id":4,"method":"turn/steer","params":{"expectedTurnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","input":[{"text":"Actually, respond with exactly: steering fixture observed","type":"text"}],"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374"}}} | 16 | {"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","status":{"type":"active","activeFlags":[]}}}} |
| 16 | {"type":"emit_inbound","label":"turn/steer","frame":{"id":4,"result":{"turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | 17 | {"type":"emit_inbound","label":"turn/started","frame":{"method":"turn/started","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turn":{"id":"019e014d-b227-7af1-a889-6c7d828eb1a2","items":[],"status":"inProgress","error":null,"startedAt":1778138329,"completedAt":null,"durationMs":null}}}} |
| 17 | {"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"codex_apps","status":"ready","error":null}}} | 18 | {"type":"expect_outbound","label":"turn/steer","frame":{"id":4,"method":"turn/steer","params":{"expectedTurnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","input":[{"text":"Actually, respond with exactly: steering fixture observed","type":"text"}],"threadId":"019e014d-b214-71f0-9538-23527e8247ca"}}} |
| 19 | {"type":"emit_inbound","label":"turn/steer","frame":{"id":4,"result":{"turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} | ||
| 20 | {"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"computer-use","status":"ready","error":null}}} | ||
| 18 | {"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"uidotsh","status":"ready","error":null}}} | 21 | {"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"uidotsh","status":"ready","error":null}}} |
| 19 | {"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"userMessage","id":"550a4417-1843-4e5e-8334-6663461044d5","content":[{"type":"text","text":"Create or overwrite .codex-probe-write-action.txt with exactly this text: codex app-server approval fixture. Use a local shell command or file edit only, then briefly report what happened. Do not read package metadata, use GitHub, use web, or use MCP.","text_elements":[]}]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | 22 | {"type":"emit_inbound","label":"mcpServer/startupStatus/updated","frame":{"method":"mcpServer/startupStatus/updated","params":{"name":"codex_apps","status":"ready","error":null}}} |
| 20 | {"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"550a4417-1843-4e5e-8334-6663461044d5","content":[{"type":"text","text":"Create or overwrite .codex-probe-write-action.txt with exactly this text: codex app-server approval fixture. Use a local shell command or file edit only, then briefly report what happened. Do not read package metadata, use GitHub, use web, or use MCP.","text_elements":[]}]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | 23 | {"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"userMessage","id":"4fac8685-35e8-475c-96b6-9adb33c68158","content":[{"type":"text","text":"Respond with exactly: steering fixture initial response","text_elements":[]}]},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} |
| 21 | {"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":0,"windowDurationMins":300,"resetsAt":1776748632},"secondary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1777335432},"credits":null,"planType":"pro"}}}} | 24 | {"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"4fac8685-35e8-475c-96b6-9adb33c68158","content":[{"type":"text","text":"Respond with exactly: steering fixture initial response","text_elements":[]}]},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} |
| 22 | {"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"reasoning","id":"rs_0a7ba873c300712d0169e6e49aa908819b82a8cc3df84c0036","summary":[],"content":[]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | 25 | {"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":19,"windowDurationMins":300,"resetsAt":1778142198},"secondary":{"usedPercent":31,"windowDurationMins":10080,"resetsAt":1778539136},"credits":null,"planType":"pro","rateLimitReachedType":null}}}} |
| 23 | {"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"reasoning","id":"rs_0a7ba873c300712d0169e6e49aa908819b82a8cc3df84c0036","summary":[],"content":[]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | 26 | {"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"reasoning","id":"rs_010631d904f9e19a0169fc3cdcb670819486fcc6dc7b883f58","summary":[],"content":[]},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} |
| 24 | {"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","status":{"type":"active","activeFlags":["waitingOnApproval"]}}}} | 27 | {"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"reasoning","id":"rs_010631d904f9e19a0169fc3cdcb670819486fcc6dc7b883f58","summary":[],"content":[]},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} |
| 25 | {"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"commandExecution","id":"call_SkVd4jdkKEnDTMUaNZwexNLm","command":"/bin/zsh -lc \"printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt\"","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/packages/effect-codex-app-server","processId":null,"source":"agent","status":"inProgress","commandActions":[{"type":"unknown","command":"printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt"}],"aggregatedOutput":null,"exitCode":null,"durationMs":null},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | 28 | {"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_010631d904f9e19a0169fc3cdce8ec8194a7d8a5bb50424c09","text":"","phase":"final_answer","memoryCitation":null},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} |
| 26 | {"type":"emit_inbound","label":"item/commandExecution/requestApproval","frame":{"method":"item/commandExecution/requestApproval","id":0,"params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","itemId":"call_SkVd4jdkKEnDTMUaNZwexNLm","reason":"Do you want to allow writing the requested fixture file in the workspace?","command":"/bin/zsh -lc \"printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt\"","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/packages/effect-codex-app-server","commandActions":[{"type":"unknown","command":"printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt"}],"proposedExecpolicyAmendment":["/bin/zsh","-lc","printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt"],"availableDecisions":["accept",{"acceptWithExecpolicyAmendment":{"execpolicy_amendment":["/bin/zsh","-lc","printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt"]}},"cancel"]}}} | 29 | {"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cdce8ec8194a7d8a5bb50424c09","delta":"ste"}}} |
| 27 | {"type":"expect_outbound","label":"item/commandExecution/requestApproval","frame":{"id":0,"result":{"decision":"accept"}}} | 30 | {"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cdce8ec8194a7d8a5bb50424c09","delta":"ering"}}} |
| 28 | {"type":"emit_inbound","label":"serverRequest/resolved","frame":{"method":"serverRequest/resolved","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","requestId":0}}} | 31 | {"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cdce8ec8194a7d8a5bb50424c09","delta":" fixture"}}} |
| 29 | {"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","status":{"type":"active","activeFlags":[]}}}} | 32 | {"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cdce8ec8194a7d8a5bb50424c09","delta":" initial"}}} |
| 30 | {"type":"emit_inbound","label":"thread/tokenUsage/updated","frame":{"method":"thread/tokenUsage/updated","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","tokenUsage":{"total":{"totalTokens":28779,"inputTokens":28169,"cachedInputTokens":2432,"outputTokens":610,"reasoningOutputTokens":492},"last":{"totalTokens":28779,"inputTokens":28169,"cachedInputTokens":2432,"outputTokens":610,"reasoningOutputTokens":492},"modelContextWindow":258400}}}} | 33 | {"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cdce8ec8194a7d8a5bb50424c09","delta":" response"}}} |
| 31 | {"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":0,"windowDurationMins":300,"resetsAt":1776748632},"secondary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1777335432},"credits":null,"planType":"pro"}}}} | 34 | {"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_010631d904f9e19a0169fc3cdce8ec8194a7d8a5bb50424c09","text":"steering fixture initial response","phase":"final_answer","memoryCitation":null},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} |
| 32 | {"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"commandExecution","id":"call_SkVd4jdkKEnDTMUaNZwexNLm","command":"/bin/zsh -lc \"printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt\"","cwd":"/Users/julius/.t3/worktrees/codething-mvp/t3code-c1e5e1d1/packages/effect-codex-app-server","processId":"78219","source":"unifiedExecStartup","status":"completed","commandActions":[{"type":"unknown","command":"printf '%s' 'codex app-server approval fixture' > .codex-probe-write-action.txt"}],"aggregatedOutput":null,"exitCode":0,"durationMs":0},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | 35 | {"type":"emit_inbound","label":"thread/tokenUsage/updated","frame":{"method":"thread/tokenUsage/updated","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","tokenUsage":{"total":{"totalTokens":18900,"inputTokens":18880,"cachedInputTokens":7552,"outputTokens":20,"reasoningOutputTokens":9},"last":{"totalTokens":18900,"inputTokens":18880,"cachedInputTokens":7552,"outputTokens":20,"reasoningOutputTokens":9},"modelContextWindow":258400}}}} |
| 33 | {"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"userMessage","id":"c046f20c-4ec9-4e49-9c31-bcc25ca81b1f","content":[{"type":"text","text":"Actually, respond with exactly: steering fixture observed","text_elements":[]}]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | 36 | {"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":19,"windowDurationMins":300,"resetsAt":1778142198},"secondary":{"usedPercent":31,"windowDurationMins":10080,"resetsAt":1778539136},"credits":null,"planType":"pro","rateLimitReachedType":null}}}} |
| 34 | {"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"c046f20c-4ec9-4e49-9c31-bcc25ca81b1f","content":[{"type":"text","text":"Actually, respond with exactly: steering fixture observed","text_elements":[]}]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | 37 | {"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"userMessage","id":"3d8198bc-53c8-4885-8dfe-c5158eb2cfb3","content":[{"type":"text","text":"Actually, respond with exactly: steering fixture observed","text_elements":[]}]},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} |
| 35 | {"type":"emit_inbound","label":"thread/tokenUsage/updated","frame":{"method":"thread/tokenUsage/updated","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","tokenUsage":{"total":{"totalTokens":28779,"inputTokens":28169,"cachedInputTokens":2432,"outputTokens":610,"reasoningOutputTokens":492},"last":{"totalTokens":28779,"inputTokens":28169,"cachedInputTokens":2432,"outputTokens":610,"reasoningOutputTokens":492},"modelContextWindow":258400}}}} | 38 | {"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"3d8198bc-53c8-4885-8dfe-c5158eb2cfb3","content":[{"type":"text","text":"Actually, respond with exactly: steering fixture observed","text_elements":[]}]},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} |
| 36 | {"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":0,"windowDurationMins":300,"resetsAt":1776748632},"secondary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1777335432},"credits":null,"planType":"pro"}}}} | 39 | {"type":"emit_inbound","label":"thread/tokenUsage/updated","frame":{"method":"thread/tokenUsage/updated","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","tokenUsage":{"total":{"totalTokens":18900,"inputTokens":18880,"cachedInputTokens":7552,"outputTokens":20,"reasoningOutputTokens":9},"last":{"totalTokens":18900,"inputTokens":18880,"cachedInputTokens":7552,"outputTokens":20,"reasoningOutputTokens":9},"modelContextWindow":258400}}}} |
| 37 | {"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"reasoning","id":"rs_0a7ba873c300712d0169e6e4a28490819b9549007ee1031138","summary":[],"content":[]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | 40 | {"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":19,"windowDurationMins":300,"resetsAt":1778142198},"secondary":{"usedPercent":31,"windowDurationMins":10080,"resetsAt":1778539136},"credits":null,"planType":"pro","rateLimitReachedType":null}}}} |
| 38 | {"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"reasoning","id":"rs_0a7ba873c300712d0169e6e4a28490819b9549007ee1031138","summary":[],"content":[]},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | 41 | {"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_010631d904f9e19a0169fc3cddd2b08194ba097f57a2fd6332","text":"","phase":"final_answer","memoryCitation":null},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} |
| 39 | {"type":"emit_inbound","label":"item/started","frame":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_0a7ba873c300712d0169e6e4a2ba50819ba77e49ab176e7a34","text":"","phase":"final_answer","memoryCitation":null},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | 42 | {"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cddd2b08194ba097f57a2fd6332","delta":"ste"}}} |
| 40 | {"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","itemId":"msg_0a7ba873c300712d0169e6e4a2ba50819ba77e49ab176e7a34","delta":"ste"}}} | 43 | {"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cddd2b08194ba097f57a2fd6332","delta":"ering"}}} |
| 41 | {"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","itemId":"msg_0a7ba873c300712d0169e6e4a2ba50819ba77e49ab176e7a34","delta":"ering"}}} | 44 | {"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cddd2b08194ba097f57a2fd6332","delta":" fixture"}}} |
| 42 | {"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","itemId":"msg_0a7ba873c300712d0169e6e4a2ba50819ba77e49ab176e7a34","delta":" fixture"}}} | 45 | {"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","itemId":"msg_010631d904f9e19a0169fc3cddd2b08194ba097f57a2fd6332","delta":" observed"}}} |
| 43 | {"type":"emit_inbound","label":"item/agentMessage/delta","frame":{"method":"item/agentMessage/delta","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","itemId":"msg_0a7ba873c300712d0169e6e4a2ba50819ba77e49ab176e7a34","delta":" observed"}}} | 46 | {"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_010631d904f9e19a0169fc3cddd2b08194ba097f57a2fd6332","text":"steering fixture observed","phase":"final_answer","memoryCitation":null},"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2"}}} |
| 44 | {"type":"emit_inbound","label":"item/completed","frame":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_0a7ba873c300712d0169e6e4a2ba50819ba77e49ab176e7a34","text":"steering fixture observed","phase":"final_answer","memoryCitation":null},"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2"}}} | 47 | {"type":"emit_inbound","label":"thread/tokenUsage/updated","frame":{"method":"thread/tokenUsage/updated","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turnId":"019e014d-b227-7af1-a889-6c7d828eb1a2","tokenUsage":{"total":{"totalTokens":37812,"inputTokens":37784,"cachedInputTokens":26368,"outputTokens":28,"reasoningOutputTokens":9},"last":{"totalTokens":18912,"inputTokens":18904,"cachedInputTokens":18816,"outputTokens":8,"reasoningOutputTokens":0},"modelContextWindow":258400}}}} |
| 45 | {"type":"emit_inbound","label":"thread/tokenUsage/updated","frame":{"method":"thread/tokenUsage/updated","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turnId":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","tokenUsage":{"total":{"totalTokens":57642,"inputTokens":57006,"cachedInputTokens":30464,"outputTokens":636,"reasoningOutputTokens":508},"last":{"totalTokens":28863,"inputTokens":28837,"cachedInputTokens":28032,"outputTokens":26,"reasoningOutputTokens":16},"modelContextWindow":258400}}}} | 48 | {"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":19,"windowDurationMins":300,"resetsAt":1778142198},"secondary":{"usedPercent":31,"windowDurationMins":10080,"resetsAt":1778539136},"credits":null,"planType":"pro","rateLimitReachedType":null}}}} |
| 46 | {"type":"emit_inbound","label":"account/rateLimits/updated","frame":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"primary":{"usedPercent":0,"windowDurationMins":300,"resetsAt":1776748632},"secondary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1777335432},"credits":null,"planType":"pro"}}}} | 49 | {"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","status":{"type":"idle"}}}} |
| 47 | {"type":"emit_inbound","label":"thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","status":{"type":"idle"}}}} | 50 | {"type":"emit_inbound","label":"turn/completed","frame":{"method":"turn/completed","params":{"threadId":"019e014d-b214-71f0-9538-23527e8247ca","turn":{"id":"019e014d-b227-7af1-a889-6c7d828eb1a2","items":[],"status":"completed","error":null,"startedAt":1778138329,"completedAt":1778138333,"durationMs":4311}}}} |
| 48 | {"type":"emit_inbound","label":"turn/completed","frame":{"method":"turn/completed","params":{"threadId":"019dadec-ebd7-75f0-812e-27a7e57bb374","turn":{"id":"019dadec-ebdf-7c22-9dc9-f4d9e4ed2af2","items":[],"status":"completed","error":null,"startedAt":1776739478,"completedAt":1776739490,"durationMs":12401}}}} | ||
| 49 | {"type":"runtime_exit","status":"success"} | 51 | {"type":"runtime_exit","status":"success"} |
| @@ -1,19 +1,18 @@ | |||
| 1 | import { | 1 | import { |
| 2 | MESSAGE_STEERING_INITIAL_PROMPT, | ||
| 2 | MESSAGE_STEERING_STEER_PROMPT, | 3 | MESSAGE_STEERING_STEER_PROMPT, |
| 3 | TOOL_CALL_WRITE_PROMPT, | ||
| 4 | type OrchestratorFixtureInput, | 4 | type OrchestratorFixtureInput, |
| 5 | } from "../shared.ts"; | 5 | } from "../shared.ts"; |
| 6 | 6 | ||
| 7 | export function messageSteeringInput(): OrchestratorFixtureInput { | 7 | export function messageSteeringInput(): OrchestratorFixtureInput { |
| 8 | return { | 8 | return { |
| 9 | steps: [ | 9 | steps: [ |
| 10 | { type: "message", text: TOOL_CALL_WRITE_PROMPT }, | 10 | { type: "message", text: MESSAGE_STEERING_INITIAL_PROMPT }, |
| 11 | { | 11 | { |
| 12 | type: "steer", | 12 | type: "steer", |
| 13 | text: MESSAGE_STEERING_STEER_PROMPT, | 13 | text: MESSAGE_STEERING_STEER_PROMPT, |
| 14 | targetRunIndex: 1, | 14 | targetRunIndex: 1, |
| 15 | }, | 15 | }, |
| 16 | { type: "approve_next_runtime_request" }, | ||
| 17 | ], | 16 | ], |
| 18 | }; | 17 | }; |
| 19 | } | 18 | } |
| @@ -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 | 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`; | 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 | export const TOOL_CALL_WRITE_PROMPT = | 38 | export const TOOL_CALL_WRITE_PROMPT = |
| 39 | "Create or overwrite .codex-probe-write-action.txt with exactly this text: codex app-server approval fixture. Use a local shell command or file edit only, then briefly report what happened. Do not read package metadata, use GitHub, use web, or use MCP."; | 39 | "Create or overwrite .codex-probe-write-action.txt with exactly this text: codex app-server approval fixture. Use a local shell command or file edit only, then briefly report what happened. Do not read package metadata, use GitHub, use web, or use MCP."; |
| 40 | export const MESSAGE_STEERING_INITIAL_PROMPT = | ||
| 41 | "Respond with exactly: steering fixture initial response"; | ||
| 40 | export const SUBAGENT_PROMPT = | 42 | export const SUBAGENT_PROMPT = |
| 41 | "Spawn 2 subagents, one to read package.json and one to read tsconfig.json"; | 43 | "Spawn 2 subagents, one to read package.json and one to read tsconfig.json"; |
| 42 | export const TURN_INTERRUPT_PROMPT = | 44 | export const TURN_INTERRUPT_PROMPT = |
| @@ -296,8 +298,7 @@ export function materializeFixtureInput(input: { · 253 unmodified lines | |||
| 296 | const nextStep = input.fixtureInput.steps[stepIndex + 1]; | 298 | const nextStep = input.fixtureInput.steps[stepIndex + 1]; |
| 297 | const shouldRunInBackground = | 299 | const shouldRunInBackground = |
| 298 | (nextStep !== undefined && | 300 | (nextStep !== undefined && |
| 299 | (((nextStep.type === "steer" || nextStep.type === "interrupt") && | 301 | ((nextStep.type === "interrupt" && nextStep.targetRunIndex === messageIndex) || |
| 300 | nextStep.targetRunIndex === messageIndex) || | ||
| 301 | nextStep.type === "queue_message")) || | 302 | nextStep.type === "queue_message")) || |
| 302 | nextStep?.type === "approve_next_runtime_request" || | 303 | nextStep?.type === "approve_next_runtime_request" || |
| 303 | nextStep?.type === "answer_next_user_input_request"; | 304 | nextStep?.type === "answer_next_user_input_request"; |
| @@ -321,7 +322,13 @@ export function materializeFixtureInput(input: { · 17 unmodified lines | |||
| 321 | ); | 322 | ); |
| 322 | if (shouldRunInBackground) { | 323 | if (shouldRunInBackground) { |
| 323 | activeRunDispatchKeys.add(key); | 324 | activeRunDispatchKeys.add(key); |
| 324 | } else { | 325 | } else if ( |
| 326 | !( | ||
| 327 | nextStep !== undefined && | ||
| 328 | nextStep.type === "steer" && | ||
| 329 | nextStep.targetRunIndex === messageIndex | ||
| 330 | ) | ||
| 331 | ) { | ||
| 325 | steps.push({ type: "await_thread_idle", threadId: ids.threadId }); | 332 | steps.push({ type: "await_thread_idle", threadId: ids.threadId }); |
| 326 | } | 333 | } |
| 327 | } | 334 | } |
| @@ -401,6 +408,11 @@ export function materializeFixtureInput(input: { · 73 unmodified lines | |||
| 401 | break; | 408 | break; |
| 402 | case "steer": | 409 | case "steer": |
| 403 | messageIndex += 1; | 410 | messageIndex += 1; |
| 411 | steps.push({ | ||
| 412 | type: "await_run_steerable", | ||
| 413 | threadId: ids.threadId, | ||
| 414 | runId: runIdFor(step.targetRunIndex), | ||
| 415 | }); | ||
| 404 | pushDispatch( | 416 | pushDispatch( |
| 405 | dispatchMessageCommand({ | 417 | dispatchMessageCommand({ |
| 406 | commandId: yield* idAllocator.allocate.command({ | 418 | commandId: yield* idAllocator.allocate.command({ |
| @@ -421,11 +433,10 @@ export function materializeFixtureInput(input: { · 14 unmodified lines | |||
| 421 | }, | 433 | }, |
| 422 | }), | 434 | }), |
| 423 | ); | 435 | ); |
| 424 | if ( | 436 | if (input.fixtureInput.steps[stepIndex + 1]?.type !== "approve_next_runtime_request") { |
| 425 | input.fixtureInput.steps[stepIndex + 1]?.type !== "approve_next_runtime_request" && | 437 | if (activeRunDispatchKeys.delete(`run:${step.targetRunIndex}`)) { |
| 426 | activeRunDispatchKeys.delete(`run:${step.targetRunIndex}`) | 438 | steps.push({ type: "await", key: `run:${step.targetRunIndex}` }); |
| 427 | ) { | 439 | } |
| 428 | steps.push({ type: "await", key: `run:${step.targetRunIndex}` }); | ||
| 429 | steps.push({ type: "await_thread_idle", threadId: ids.threadId }); | 440 | steps.push({ type: "await_thread_idle", threadId: ids.threadId }); |
| 430 | } | 441 | } |
| 431 | break; | 442 | break; |