@gkorepanov/ccodex 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/README.md +139 -63
  2. package/assets/ccodex/goals/continuation.md +1 -0
  3. package/{compatibility.json → config/compatibility.json} +5 -6
  4. package/dist/claude/asyncQueue.d.ts +3 -0
  5. package/dist/claude/asyncQueue.js +13 -2
  6. package/dist/claude/asyncQueue.js.map +1 -1
  7. package/dist/claude/commandActions.d.ts +6 -0
  8. package/dist/claude/commandActions.js +57 -0
  9. package/dist/claude/commandActions.js.map +1 -0
  10. package/dist/claude/compactCommand.d.ts +2 -0
  11. package/dist/claude/compactCommand.js +7 -0
  12. package/dist/claude/compactCommand.js.map +1 -0
  13. package/dist/claude/goalTools.d.ts +15 -0
  14. package/dist/claude/goalTools.js +128 -0
  15. package/dist/claude/goalTools.js.map +1 -0
  16. package/dist/claude/hookMapper.d.ts +17 -4
  17. package/dist/claude/hookMapper.js +7 -5
  18. package/dist/claude/hookMapper.js.map +1 -1
  19. package/dist/claude/inputMapper.d.ts +1 -0
  20. package/dist/claude/inputMapper.js +1 -0
  21. package/dist/claude/inputMapper.js.map +1 -1
  22. package/dist/claude/rateLimits.d.ts +72 -0
  23. package/dist/claude/rateLimits.js +330 -0
  24. package/dist/claude/rateLimits.js.map +1 -0
  25. package/dist/claude/responseItemValidation.d.ts +2 -0
  26. package/dist/claude/responseItemValidation.js +40 -0
  27. package/dist/claude/responseItemValidation.js.map +1 -0
  28. package/dist/claude/resultClassifier.d.ts +8 -2
  29. package/dist/claude/resultClassifier.js +9 -1
  30. package/dist/claude/resultClassifier.js.map +1 -1
  31. package/dist/claude/service.d.ts +116 -29
  32. package/dist/claude/service.js +1337 -680
  33. package/dist/claude/service.js.map +1 -1
  34. package/dist/claude/session/backgroundOutput.d.ts +2 -0
  35. package/dist/claude/session/backgroundOutput.js +29 -0
  36. package/dist/claude/session/backgroundOutput.js.map +1 -0
  37. package/dist/claude/session/commands.d.ts +862 -0
  38. package/dist/claude/session/commands.js +2 -0
  39. package/dist/claude/session/commands.js.map +1 -0
  40. package/dist/claude/session/goalState.d.ts +49 -0
  41. package/dist/claude/session/goalState.js +407 -0
  42. package/dist/claude/session/goalState.js.map +1 -0
  43. package/dist/claude/session/mailbox.d.ts +52 -0
  44. package/dist/claude/session/mailbox.js +190 -0
  45. package/dist/claude/session/mailbox.js.map +1 -0
  46. package/dist/claude/session/outputAdapter.d.ts +11 -0
  47. package/dist/claude/session/outputAdapter.js +31 -0
  48. package/dist/claude/session/outputAdapter.js.map +1 -0
  49. package/dist/claude/session/permissionUpdates.d.ts +4 -0
  50. package/dist/claude/session/permissionUpdates.js +17 -0
  51. package/dist/claude/session/permissionUpdates.js.map +1 -0
  52. package/dist/claude/session/providerFacts.d.ts +47 -0
  53. package/dist/claude/session/providerFacts.js +84 -0
  54. package/dist/claude/session/providerFacts.js.map +1 -0
  55. package/dist/claude/session/providerRuntime.d.ts +1 -0
  56. package/dist/claude/session/providerRuntime.js +2 -0
  57. package/dist/claude/session/providerRuntime.js.map +1 -0
  58. package/dist/claude/session/providerRuntimeFactory.d.ts +49 -0
  59. package/dist/claude/session/providerRuntimeFactory.js +122 -0
  60. package/dist/claude/session/providerRuntimeFactory.js.map +1 -0
  61. package/dist/claude/session/repository.d.ts +57 -0
  62. package/dist/claude/session/repository.js +161 -0
  63. package/dist/claude/session/repository.js.map +1 -0
  64. package/dist/claude/session/runtime.d.ts +46 -0
  65. package/dist/claude/session/runtime.js +140 -0
  66. package/dist/claude/session/runtime.js.map +1 -0
  67. package/dist/claude/session/scopeState.d.ts +61 -0
  68. package/dist/claude/session/scopeState.js +74 -0
  69. package/dist/claude/session/scopeState.js.map +1 -0
  70. package/dist/claude/session/session.d.ts +285 -0
  71. package/dist/claude/session/session.js +6778 -0
  72. package/dist/claude/session/session.js.map +1 -0
  73. package/dist/claude/session/shellRunner.d.ts +13 -0
  74. package/dist/claude/session/shellRunner.js +103 -0
  75. package/dist/claude/session/shellRunner.js.map +1 -0
  76. package/dist/claude/session/usage.d.ts +2 -0
  77. package/dist/claude/session/usage.js +10 -0
  78. package/dist/claude/session/usage.js.map +1 -0
  79. package/dist/claude/sessionRegistry.d.ts +26 -0
  80. package/dist/claude/sessionRegistry.js +118 -0
  81. package/dist/claude/sessionRegistry.js.map +1 -0
  82. package/dist/claude/shellSupervisor.d.ts +1 -0
  83. package/dist/claude/shellSupervisor.js +69 -0
  84. package/dist/claude/shellSupervisor.js.map +1 -0
  85. package/dist/claude/statusCommand.d.ts +21 -0
  86. package/dist/claude/statusCommand.js +97 -0
  87. package/dist/claude/statusCommand.js.map +1 -0
  88. package/dist/claude/toolMapper.d.ts +9 -1
  89. package/dist/claude/toolMapper.js +98 -18
  90. package/dist/claude/toolMapper.js.map +1 -1
  91. package/dist/claude/transcriptBrancher.d.ts +37 -0
  92. package/dist/claude/transcriptBrancher.js +103 -0
  93. package/dist/claude/transcriptBrancher.js.map +1 -0
  94. package/dist/cli/delegate.js +23 -1
  95. package/dist/cli/delegate.js.map +1 -1
  96. package/dist/cli/main.js +28 -12
  97. package/dist/cli/main.js.map +1 -1
  98. package/dist/codex/generated/ResponseItemRuntimeSchema.d.ts +8 -0
  99. package/dist/codex/generated/ResponseItemRuntimeSchema.js +7 -0
  100. package/dist/codex/generated/ResponseItemRuntimeSchema.js.map +1 -0
  101. package/dist/compatibility/probe.js +12 -7
  102. package/dist/compatibility/probe.js.map +1 -1
  103. package/dist/config/config.d.ts +12 -0
  104. package/dist/config/config.js +103 -11
  105. package/dist/config/config.js.map +1 -1
  106. package/dist/daemon/daemon.js +91 -43
  107. package/dist/daemon/daemon.js.map +1 -1
  108. package/dist/daemon/ownership.d.ts +15 -0
  109. package/dist/daemon/ownership.js +185 -0
  110. package/dist/daemon/ownership.js.map +1 -0
  111. package/dist/daemon/probe.js +1 -1
  112. package/dist/daemon/supervisor.d.ts +3 -0
  113. package/dist/daemon/supervisor.js +37 -1
  114. package/dist/daemon/supervisor.js.map +1 -1
  115. package/dist/gateway/clientConnection.d.ts +7 -1
  116. package/dist/gateway/clientConnection.js +656 -60
  117. package/dist/gateway/clientConnection.js.map +1 -1
  118. package/dist/gateway/logicalThreadProjection.d.ts +8 -0
  119. package/dist/gateway/logicalThreadProjection.js +59 -0
  120. package/dist/gateway/logicalThreadProjection.js.map +1 -0
  121. package/dist/gateway/remoteRelay.js +0 -1
  122. package/dist/gateway/remoteRelay.js.map +1 -1
  123. package/dist/gateway/server.d.ts +1 -1
  124. package/dist/gateway/server.js +69 -11
  125. package/dist/gateway/server.js.map +1 -1
  126. package/dist/gateway/stockRpc.d.ts +5 -2
  127. package/dist/gateway/stockRpc.js +52 -6
  128. package/dist/gateway/stockRpc.js.map +1 -1
  129. package/dist/gateway/subscriptions.d.ts +19 -3
  130. package/dist/gateway/subscriptions.js +152 -13
  131. package/dist/gateway/subscriptions.js.map +1 -1
  132. package/dist/gateway/threadList.d.ts +7 -2
  133. package/dist/gateway/threadList.js +8 -4
  134. package/dist/gateway/threadList.js.map +1 -1
  135. package/dist/gateway/transientNotice.d.ts +15 -0
  136. package/dist/gateway/transientNotice.js +96 -1
  137. package/dist/gateway/transientNotice.js.map +1 -1
  138. package/dist/handoff/providerEpochs.d.ts +18 -0
  139. package/dist/handoff/providerEpochs.js +77 -0
  140. package/dist/handoff/providerEpochs.js.map +1 -0
  141. package/dist/handoff/service.d.ts +86 -6
  142. package/dist/handoff/service.js +1276 -168
  143. package/dist/handoff/service.js.map +1 -1
  144. package/dist/handoff/store.d.ts +189 -2
  145. package/dist/handoff/store.js +618 -7
  146. package/dist/handoff/store.js.map +1 -1
  147. package/dist/handoff/titleGeneration.d.ts +28 -0
  148. package/dist/handoff/titleGeneration.js +138 -0
  149. package/dist/handoff/titleGeneration.js.map +1 -0
  150. package/dist/management/doctor.d.ts +1 -1
  151. package/dist/management/doctor.js +32 -23
  152. package/dist/management/doctor.js.map +1 -1
  153. package/dist/management/lifecycle.js +14 -2
  154. package/dist/management/lifecycle.js.map +1 -1
  155. package/dist/management/remoteShim.d.ts +10 -0
  156. package/dist/management/remoteShim.js +65 -0
  157. package/dist/management/remoteShim.js.map +1 -0
  158. package/dist/management/setup.d.ts +5 -0
  159. package/dist/management/setup.js +122 -41
  160. package/dist/management/setup.js.map +1 -1
  161. package/dist/management/shimSelect.d.ts +3 -0
  162. package/dist/management/shimSelect.js +49 -0
  163. package/dist/management/shimSelect.js.map +1 -0
  164. package/dist/management/shims.d.ts +5 -0
  165. package/dist/management/shims.js +59 -0
  166. package/dist/management/shims.js.map +1 -0
  167. package/dist/observability/metrics.d.ts +2 -0
  168. package/dist/observability/metrics.js +6 -0
  169. package/dist/observability/metrics.js.map +1 -1
  170. package/dist/protocol/errors.d.ts +1 -0
  171. package/dist/protocol/errors.js +4 -1
  172. package/dist/protocol/errors.js.map +1 -1
  173. package/dist/runtime/dependencies.js +3 -3
  174. package/dist/runtime/dependencies.js.map +1 -1
  175. package/dist/runtime/providerAvailability.d.ts +24 -0
  176. package/dist/runtime/providerAvailability.js +107 -0
  177. package/dist/runtime/providerAvailability.js.map +1 -0
  178. package/dist/state/stateCommand.d.ts +21 -0
  179. package/dist/state/stateCommand.js +198 -0
  180. package/dist/state/stateCommand.js.map +1 -0
  181. package/dist/state/stockStateTracker.d.ts +16 -0
  182. package/dist/state/stockStateTracker.js +137 -0
  183. package/dist/state/stockStateTracker.js.map +1 -0
  184. package/dist/store/HybridStore.d.ts +118 -2
  185. package/dist/store/HybridStore.js +23 -1
  186. package/dist/store/HybridStore.js.map +1 -1
  187. package/dist/store/memoryStore.d.ts +57 -7
  188. package/dist/store/memoryStore.js +337 -5
  189. package/dist/store/memoryStore.js.map +1 -1
  190. package/dist/store/sqliteStore.d.ts +30 -4
  191. package/dist/store/sqliteStore.js +395 -42
  192. package/dist/store/sqliteStore.js.map +1 -1
  193. package/examples/config.toml +35 -0
  194. package/{LICENSES.md → legal/LICENSES.md} +2 -2
  195. package/{THIRD_PARTY_NOTICES.md → legal/THIRD_PARTY_NOTICES.md} +177 -148
  196. package/package.json +36 -24
  197. package/{install.sh → scripts/install.sh} +2 -2
  198. package/scripts/postinstall.mjs +10 -0
  199. package/vendor/codex/continuation.md +51 -0
  200. package/BUILD_BENCHMARKS.md +0 -27
  201. package/RELEASING.md +0 -98
  202. package/SSH_VERIFY.md +0 -69
  203. package/config.example.toml +0 -20
  204. package/dist/claude/interactions.d.ts +0 -23
  205. package/dist/claude/interactions.js +0 -77
  206. package/dist/claude/interactions.js.map +0 -1
  207. package/dist/claude/sessionRuntime.d.ts +0 -102
  208. package/dist/claude/sessionRuntime.js +0 -1361
  209. package/dist/claude/sessionRuntime.js.map +0 -1
  210. /package/{uninstall.sh → scripts/uninstall.sh} +0 -0
@@ -1,1361 +0,0 @@
1
- import { v7 as uuidv7 } from "uuid";
2
- import { AsyncQueue } from "./asyncQueue.js";
3
- import { mapUserInput } from "./inputMapper.js";
4
- import { completeTool, planSteps, startTool, updateToolInput } from "./toolMapper.js";
5
- import { diffFile, snapshotFile } from "./fileSnapshots.js";
6
- import { appendHookProgress, completeHookRun, startHookRun } from "./hookMapper.js";
7
- import { isFileMutationTool, toolPolicy } from "./permissionPolicy.js";
8
- import { interruptAndCancelOwned } from "./interruptCompat.js";
9
- import { classifyClaudeResult, classifyClaudeRuntimeError } from "./resultClassifier.js";
10
- import { invalidParams } from "../protocol/errors.js";
11
- import { MetricsRegistry } from "../observability/metrics.js";
12
- import { normalizeClaudeModelIdentifier } from "./modelSelection.js";
13
- function nowSeconds() {
14
- return Math.floor(Date.now() / 1_000);
15
- }
16
- function eventDedupKey(method, params) {
17
- if (!params || typeof params !== "object")
18
- return null;
19
- const value = params;
20
- const item = value.item && typeof value.item === "object" ? value.item : undefined;
21
- const run = value.run && typeof value.run === "object" ? value.run : undefined;
22
- const turn = value.turn && typeof value.turn === "object" ? value.turn : undefined;
23
- const turnId = typeof value.turnId === "string" ? value.turnId : typeof turn?.id === "string" ? turn.id : null;
24
- if (["turn/started", "turn/completed", "thread/compacted"].includes(method) && turnId) {
25
- return `${method}:${turnId}`;
26
- }
27
- if (["item/started", "item/completed"].includes(method) && typeof item?.id === "string") {
28
- return `${method}:${item.id}`;
29
- }
30
- if (["hook/started", "hook/completed"].includes(method) && typeof run?.id === "string") {
31
- return `${method}:${run.id}`;
32
- }
33
- return null;
34
- }
35
- function appendInstructions(record) {
36
- const parts = [record.baseInstructions, record.developerInstructions];
37
- if (record.personality === "friendly")
38
- parts.push("Use a friendly, collaborative communication style.");
39
- if (record.personality === "pragmatic")
40
- parts.push("Be direct, pragmatic, and focused on concrete outcomes.");
41
- const value = parts.filter((part) => Boolean(part)).join("\n\n");
42
- return value || undefined;
43
- }
44
- function effort(value) {
45
- return value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max"
46
- ? value
47
- : undefined;
48
- }
49
- function permissionMode(record) {
50
- if (record.approvalsReviewer !== "user")
51
- return "auto";
52
- const sandbox = record.sandboxPolicy && typeof record.sandboxPolicy === "object" && "type" in record.sandboxPolicy
53
- ? record.sandboxPolicy
54
- : undefined;
55
- if (record.approvalPolicy === "never" && sandbox?.type === "dangerFullAccess")
56
- return "bypassPermissions";
57
- if (record.approvalPolicy === "never")
58
- return "dontAsk";
59
- return "default";
60
- }
61
- function claudeOutputSchema(value) {
62
- if (!value || typeof value !== "object" || Array.isArray(value))
63
- return undefined;
64
- const { $schema: _dialect, ...schema } = value;
65
- return schema;
66
- }
67
- function textFromAssistant(message) {
68
- if (message.type !== "assistant" || !Array.isArray(message.message.content))
69
- return [];
70
- return message.message.content.flatMap((block) => block.type === "text" && typeof block.text === "string" ? [block.text] : []);
71
- }
72
- function outputText(value) {
73
- if (typeof value === "string")
74
- return value;
75
- if (!Array.isArray(value))
76
- return "";
77
- return value.flatMap((block) => {
78
- if (!block || typeof block !== "object")
79
- return [];
80
- const candidate = block;
81
- return typeof candidate.text === "string" ? [candidate.text] : [];
82
- }).join("\n");
83
- }
84
- function toolResults(message) {
85
- if (message.type !== "user" || !Array.isArray(message.message.content))
86
- return [];
87
- return message.message.content.flatMap((block) => {
88
- if (!block || typeof block !== "object" || block.type !== "tool_result")
89
- return [];
90
- return [{ toolUseId: block.tool_use_id, output: outputText(block.content), isError: block.is_error === true }];
91
- });
92
- }
93
- function serverToolResult(value) {
94
- if (!value || typeof value !== "object")
95
- return undefined;
96
- const block = value;
97
- if (typeof block.type !== "string" || !block.type.endsWith("_tool_result") || typeof block.tool_use_id !== "string")
98
- return undefined;
99
- const content = block.content;
100
- const errorType = content && typeof content === "object" && !Array.isArray(content)
101
- ? content.type
102
- : undefined;
103
- return {
104
- toolUseId: block.tool_use_id,
105
- output: typeof content === "string" ? content : content === undefined ? "" : JSON.stringify(content),
106
- isError: typeof errorType === "string" && errorType.endsWith("_error"),
107
- };
108
- }
109
- export class ClaudeSessionRuntime {
110
- record;
111
- store;
112
- hub;
113
- logger;
114
- interactions;
115
- invalidateModelCatalog;
116
- metrics;
117
- promptQueue = new AsyncQueue();
118
- abort = new AbortController();
119
- sdkQuery;
120
- active;
121
- consumePromise;
122
- stopped = false;
123
- lastActivityMs = Date.now();
124
- pendingCompaction;
125
- fileSnapshots = new Map();
126
- hookRuns = new Map();
127
- hookDisplayOrder = 0;
128
- tasks = new Map();
129
- hiddenTaskIds = new Set();
130
- backgroundTaskIds = new Set();
131
- backgroundTurn;
132
- capabilities = new Set();
133
- ownedMessageIds = new Set();
134
- sessionApprovedTools = new Set();
135
- hasSubmittedInput = false;
136
- providerEventId = null;
137
- providerEventType = null;
138
- constructor(record, store, hub, logger, config, resume, queryFactory, interactions, invalidateModelCatalog, metrics) {
139
- this.record = record;
140
- this.store = store;
141
- this.hub = hub;
142
- this.logger = logger;
143
- this.interactions = interactions;
144
- this.invalidateModelCatalog = invalidateModelCatalog;
145
- this.metrics = metrics;
146
- const append = appendInstructions(record);
147
- const selectedPermissionMode = permissionMode(record);
148
- const selectedEffort = effort(record.reasoningEffort);
149
- if (record.reasoningEffort && !selectedEffort)
150
- throw new Error(`Unsupported Claude effort '${record.reasoningEffort}'.`);
151
- const outputSchema = claudeOutputSchema(record.outputSchema);
152
- this.sdkQuery = queryFactory({
153
- prompt: this.promptQueue,
154
- options: {
155
- cwd: record.thread.cwd,
156
- model: normalizeClaudeModelIdentifier(record.claudeModelValue),
157
- ...(resume ? { resume: record.claudeSessionId } : { sessionId: record.claudeSessionId }),
158
- pathToClaudeCodeExecutable: config.claudeBinary,
159
- persistSession: !record.thread.ephemeral,
160
- abortController: this.abort,
161
- includePartialMessages: true,
162
- includeHookEvents: true,
163
- forwardSubagentText: true,
164
- settingSources: ["user", "project", "local"],
165
- systemPrompt: { type: "preset", preset: "claude_code", ...(append ? { append } : {}) },
166
- permissionMode: selectedPermissionMode,
167
- ...(selectedPermissionMode === "bypassPermissions" ? { allowDangerouslySkipPermissions: true } : {}),
168
- ...(selectedPermissionMode === "auto"
169
- ? {}
170
- : { canUseTool: (name, input, options) => this.canUseTool(name, input, options) }),
171
- onElicitation: (request, options) => this.onElicitation(request, options.signal),
172
- hooks: {
173
- PreToolUse: [{ hooks: [(input, toolUseId) => this.beforeToolUse(input, toolUseId)] }],
174
- PostToolUse: [{ matcher: "Edit|Write|NotebookEdit", hooks: [(input, toolUseId) => this.captureFileAfter(input, toolUseId)] }],
175
- PostToolUseFailure: [{ matcher: "Edit|Write|NotebookEdit", hooks: [(input, toolUseId) => this.captureFileAfter(input, toolUseId)] }],
176
- },
177
- ...(record.serviceTier === "fast" ? { settings: { fastMode: true } } : {}),
178
- ...(selectedEffort ? { effort: selectedEffort } : {}),
179
- ...(record.reasoningSummary === "none"
180
- ? { thinking: { type: "adaptive", display: "omitted" } }
181
- : record.reasoningSummary
182
- ? { thinking: { type: "adaptive", display: "summarized" } }
183
- : {}),
184
- ...(outputSchema
185
- ? { outputFormat: { type: "json_schema", schema: outputSchema } }
186
- : {}),
187
- env: process.env,
188
- stderr: (line) => logger.debug("claude.stderr", { threadId: record.thread.id, output: line }),
189
- },
190
- });
191
- this.consumePromise = this.consume();
192
- }
193
- static async create(record, store, hub, logger, config, resume, queryFactory, interactions, invalidateModelCatalog = () => undefined, metrics = new MetricsRegistry()) {
194
- const runtime = new ClaudeSessionRuntime(record, store, hub, logger, config, resume, queryFactory, interactions, invalidateModelCatalog, metrics);
195
- let timer;
196
- try {
197
- await Promise.race([
198
- runtime.sdkQuery.initializationResult(),
199
- new Promise((_, reject) => { timer = setTimeout(() => reject(new Error("Claude session initialization timed out.")), 15_000); }),
200
- ]);
201
- return runtime;
202
- }
203
- catch (error) {
204
- await runtime.stop("Claude session initialization failed.");
205
- throw error;
206
- }
207
- finally {
208
- if (timer)
209
- clearTimeout(timer);
210
- }
211
- }
212
- get threadId() {
213
- return this.record.thread.id;
214
- }
215
- get isActive() {
216
- return this.active !== undefined;
217
- }
218
- get activeTurnId() {
219
- return this.active?.turn.id;
220
- }
221
- get isEphemeral() {
222
- return this.record.thread.ephemeral;
223
- }
224
- get canRestartEphemeral() {
225
- return this.record.thread.ephemeral && !this.hasSubmittedInput && !this.active;
226
- }
227
- synchronizeRecord(record) {
228
- if (record.thread.id !== this.threadId)
229
- throw new Error("Cannot synchronize a different Claude thread.");
230
- this.record = record;
231
- }
232
- isIdleFor(milliseconds) {
233
- return !this.active && Date.now() - this.lastActivityMs >= milliseconds;
234
- }
235
- async prepareTurn(params, review) {
236
- if (this.active)
237
- throw invalidParams(`Thread '${this.threadId}' already has an active turn.`);
238
- this.completeBackgroundTurn();
239
- this.lastActivityMs = Date.now();
240
- if (params.model && params.model !== this.record.modelPickerId) {
241
- if (!params.model.startsWith("claude:"))
242
- throw invalidParams("Cannot switch a Claude thread to a Codex model.");
243
- await this.sdkQuery.setModel(params.model.slice("claude:".length));
244
- this.record = {
245
- ...this.record,
246
- modelPickerId: params.model,
247
- claudeModelValue: params.model.slice("claude:".length),
248
- };
249
- }
250
- const userItem = {
251
- type: "userMessage",
252
- id: uuidv7(),
253
- clientId: params.clientUserMessageId ?? null,
254
- content: params.input,
255
- };
256
- const reviewItem = review ? { type: "enteredReviewMode", id: uuidv7(), review } : undefined;
257
- const turn = {
258
- id: uuidv7(),
259
- items: [...(reviewItem ? [reviewItem] : []), userItem],
260
- itemsView: "full",
261
- status: "inProgress",
262
- error: null,
263
- startedAt: nowSeconds(),
264
- completedAt: null,
265
- durationMs: null,
266
- };
267
- const messageUuid = uuidv7();
268
- const message = await mapUserInput(params.input, messageUuid, { cwd: this.record.thread.cwd, sandboxPolicy: this.record.sandboxPolicy });
269
- this.ownedMessageIds.add(messageUuid);
270
- this.active = {
271
- turn,
272
- blockItems: new Map(),
273
- tools: new Map(),
274
- completedItems: new Set([userItem.id, ...(reviewItem ? [reviewItem.id] : [])]),
275
- ...(review ? { review } : {}),
276
- };
277
- this.store.createTurn(this.threadId, turn);
278
- const preview = this.record.thread.preview || params.input.flatMap((item) => item.type === "text" ? [item.text] : []).join(" ").slice(0, 200);
279
- this.updateThread({
280
- ...this.record.thread,
281
- preview,
282
- status: { type: "active", activeFlags: [] },
283
- updatedAt: nowSeconds(),
284
- recencyAt: nowSeconds(),
285
- });
286
- return {
287
- turn,
288
- announce: () => {
289
- this.emit("turn/started", { threadId: this.threadId, turn });
290
- if (reviewItem) {
291
- this.emit("item/started", { item: reviewItem, threadId: this.threadId, turnId: turn.id, startedAtMs: Date.now() });
292
- this.emit("item/completed", { item: reviewItem, threadId: this.threadId, turnId: turn.id, completedAtMs: Date.now() });
293
- }
294
- this.emit("item/started", { item: userItem, threadId: this.threadId, turnId: turn.id, startedAtMs: Date.now() });
295
- this.emit("item/completed", { item: userItem, threadId: this.threadId, turnId: turn.id, completedAtMs: Date.now() });
296
- },
297
- start: () => {
298
- this.hasSubmittedInput = true;
299
- this.promptQueue.push(message);
300
- },
301
- };
302
- }
303
- async interrupt() {
304
- if (!this.active)
305
- return;
306
- this.interactions.cancelThread(this.threadId);
307
- const cancellation = await interruptAndCancelOwned(this.sdkQuery, this.ownedMessageIds, this.capabilities);
308
- for (const id of [...cancellation.cancelled, ...cancellation.raced])
309
- this.ownedMessageIds.delete(id);
310
- if (cancellation.raced.length > 0) {
311
- this.logger.warn("claude.interrupt.queued-message-race", { threadId: this.threadId, count: cancellation.raced.length });
312
- }
313
- }
314
- async steer(params) {
315
- if (!this.active || this.active.turn.id !== params.expectedTurnId) {
316
- throw invalidParams(`Expected active turn '${params.expectedTurnId}' does not match the Claude thread.`);
317
- }
318
- const item = {
319
- type: "userMessage",
320
- id: uuidv7(),
321
- clientId: params.clientUserMessageId ?? null,
322
- content: params.input,
323
- };
324
- const messageUuid = uuidv7();
325
- const message = await mapUserInput(params.input, messageUuid, { cwd: this.record.thread.cwd, sandboxPolicy: this.record.sandboxPolicy });
326
- this.ownedMessageIds.add(messageUuid);
327
- this.active.turn.items.push(item);
328
- this.active.completedItems.add(item.id);
329
- this.persistActive();
330
- this.emit("item/started", { item, threadId: this.threadId, turnId: this.active.turn.id, startedAtMs: Date.now() });
331
- this.emit("item/completed", { item, threadId: this.threadId, turnId: this.active.turn.id, completedAtMs: Date.now() });
332
- this.promptQueue.push(message);
333
- return this.active.turn.id;
334
- }
335
- async reinitialize() {
336
- await this.sdkQuery.reinitialize();
337
- }
338
- async compact() {
339
- if (this.active)
340
- throw invalidParams("Cannot compact a Claude thread during an active turn.");
341
- if (this.pendingCompaction)
342
- throw invalidParams("Claude compaction is already in progress.");
343
- const completion = new Promise((resolve, reject) => {
344
- const timer = setTimeout(() => {
345
- this.pendingCompaction = undefined;
346
- reject(new Error("Installed Claude CLI did not confirm compaction; this version may not support SDK-driven /compact."));
347
- }, 60_000);
348
- this.pendingCompaction = { resolve, reject, timer };
349
- });
350
- this.hasSubmittedInput = true;
351
- this.promptQueue.push(await mapUserInput([{ type: "text", text: "/compact", text_elements: [] }], uuidv7()));
352
- await completion;
353
- }
354
- async injectItems(items) {
355
- if (this.active)
356
- throw invalidParams("Cannot inject history during an active Claude turn.");
357
- const visible = items.map((item) => JSON.stringify(item)).join("\n");
358
- const input = [{ type: "text", text: `[Injected model-visible history]\n${visible}`, text_elements: [] }];
359
- const messageUuid = uuidv7();
360
- const message = await mapUserInput(input, messageUuid);
361
- this.hasSubmittedInput = true;
362
- this.promptQueue.push({ ...message, shouldQuery: false });
363
- const now = nowSeconds();
364
- const userItem = { type: "userMessage", id: uuidv7(), clientId: null, content: input };
365
- const turn = {
366
- id: uuidv7(), items: [userItem], itemsView: "full", status: "completed", error: null,
367
- startedAt: now, completedAt: now, durationMs: 0,
368
- };
369
- this.store.createTurn(this.threadId, turn);
370
- this.store.setTurnClaudeMessageUuid(this.threadId, turn.id, messageUuid);
371
- this.record = { ...this.record, lastClaudeMessageUuid: messageUuid };
372
- this.record = { ...this.record, lastCompletedTurnId: turn.id };
373
- this.updateThread({ ...this.record.thread, updatedAt: now, recencyAt: now });
374
- this.emit("item/completed", { item: userItem, threadId: this.threadId, turnId: turn.id, completedAtMs: Date.now() });
375
- }
376
- async stop(reason = "Claude runtime unloaded.") {
377
- if (this.stopped)
378
- return;
379
- if (this.pendingCompaction) {
380
- const pending = this.pendingCompaction;
381
- this.pendingCompaction = undefined;
382
- clearTimeout(pending.timer);
383
- pending.reject(new Error(reason));
384
- }
385
- if (this.active)
386
- await this.failActive(reason);
387
- this.completeBackgroundTurn("failed", reason);
388
- this.stopped = true;
389
- this.promptQueue.close();
390
- this.abort.abort();
391
- await this.sdkQuery.return(undefined).catch(() => undefined);
392
- await this.consumePromise.catch(() => undefined);
393
- if (this.record.thread.status.type !== "notLoaded") {
394
- this.updateThread({ ...this.record.thread, status: { type: "notLoaded" }, updatedAt: nowSeconds() });
395
- }
396
- }
397
- async consume() {
398
- try {
399
- for await (const message of this.sdkQuery)
400
- await this.handleMessage(message);
401
- if (!this.stopped) {
402
- if (this.active)
403
- await this.failActive("Claude runtime exited before the turn completed.");
404
- this.updateThread({ ...this.record.thread, status: { type: "systemError" }, updatedAt: nowSeconds() });
405
- }
406
- }
407
- catch (error) {
408
- if (!this.stopped) {
409
- await this.failActive(error instanceof Error ? error.message : String(error));
410
- this.updateThread({ ...this.record.thread, status: { type: "systemError" }, updatedAt: nowSeconds() });
411
- }
412
- }
413
- }
414
- async canUseTool(name, input, options) {
415
- if (!this.active)
416
- return { behavior: "deny", message: "No active Claude turn." };
417
- if (name === "AskUserQuestion")
418
- return this.askUser(input, options);
419
- if (name === "ExitPlanMode") {
420
- return { behavior: "deny", message: "The plan is visible in the client. Wait for the user's next instruction before executing it." };
421
- }
422
- if (permissionMode(this.record) === "bypassPermissions")
423
- return { behavior: "allow", updatedInput: input };
424
- const policy = toolPolicy(name, input, this.record.thread.cwd, this.record.approvalPolicy, this.record.sandboxPolicy);
425
- if (policy.decision === "deny")
426
- return { behavior: "deny", message: policy.reason ?? `Tool '${name}' is denied by the Codex thread policy.` };
427
- if (policy.decision === "defer" && isFileMutationTool(name))
428
- return { behavior: "allow", updatedInput: input };
429
- if (this.sessionApprovedTools.has(name))
430
- return { behavior: "allow", updatedInput: input };
431
- if (this.record.approvalPolicy === "never") {
432
- return { behavior: "deny", message: `Tool '${name}' requires permission, but approvalPolicy is never.` };
433
- }
434
- const itemId = options.toolUseID;
435
- const tool = this.active.tools.get(itemId);
436
- const item = tool && this.active.turn.items.find((candidate) => candidate.id === tool.itemId);
437
- if (tool && item) {
438
- updateToolInput(item, tool, input);
439
- this.persistActive();
440
- }
441
- const common = {
442
- threadId: this.threadId,
443
- turnId: this.active.turn.id,
444
- itemId,
445
- startedAtMs: Date.now(),
446
- };
447
- let method;
448
- let params;
449
- if (name === "Bash") {
450
- method = "item/commandExecution/requestApproval";
451
- params = {
452
- ...common, environmentId: null, reason: options.decisionReason ?? options.description ?? null,
453
- command: typeof input.command === "string" ? input.command : null, cwd: this.record.thread.cwd,
454
- commandActions: typeof input.command === "string" ? [{ type: "unknown", command: input.command }] : null,
455
- additionalPermissions: null, proposedExecpolicyAmendment: null,
456
- proposedNetworkPolicyAmendments: null, availableDecisions: ["accept", "acceptForSession", "decline", "cancel"],
457
- };
458
- }
459
- else if (name === "Edit" || name === "Write" || name === "NotebookEdit") {
460
- method = "item/fileChange/requestApproval";
461
- params = { ...common, reason: options.decisionReason ?? options.description ?? null, grantRoot: options.blockedPath ?? null };
462
- }
463
- else {
464
- method = "item/permissions/requestApproval";
465
- const path = options.blockedPath ? [options.blockedPath] : null;
466
- params = {
467
- ...common, environmentId: null, cwd: this.record.thread.cwd,
468
- reason: options.title ?? options.decisionReason ?? `Claude requests permission for ${name}.`,
469
- permissions: { network: null, fileSystem: path ? { read: path, write: path } : null },
470
- };
471
- }
472
- this.updateThread({ ...this.record.thread, status: { type: "active", activeFlags: ["waitingOnApproval"] } });
473
- if (tool && item)
474
- this.startToolItem(tool, item, true);
475
- let response;
476
- try {
477
- response = await this.interactions.open({
478
- threadId: this.threadId,
479
- turnId: this.active.turn.id,
480
- claudeRequestId: options.requestId,
481
- method,
482
- params,
483
- signal: options.signal,
484
- });
485
- }
486
- finally {
487
- if (this.active)
488
- this.updateThread({ ...this.record.thread, status: { type: "active", activeFlags: [] } });
489
- }
490
- if (response && typeof response === "object" && "cancelled" in response) {
491
- return { behavior: "deny", message: "User cancelled tool execution." };
492
- }
493
- const decision = response && typeof response === "object" && "decision" in response
494
- ? response.decision
495
- : response && typeof response === "object" && "scope" in response
496
- ? "accept"
497
- : "decline";
498
- if (decision === "accept" || decision === "acceptForSession") {
499
- if (decision === "acceptForSession")
500
- this.sessionApprovedTools.add(name);
501
- return {
502
- behavior: "allow",
503
- updatedInput: input,
504
- ...(decision === "acceptForSession" && options.suggestions ? { updatedPermissions: options.suggestions } : {}),
505
- };
506
- }
507
- return { behavior: "deny", message: decision === "cancel" ? "User cancelled tool execution." : "User declined tool execution." };
508
- }
509
- async askUser(input, options) {
510
- if (!this.active)
511
- return { behavior: "deny", message: "No active Claude turn." };
512
- const rawQuestions = Array.isArray(input.questions) ? input.questions : [];
513
- const questions = rawQuestions.map((value, index) => {
514
- const question = value && typeof value === "object" ? value : {};
515
- const prompt = typeof question.question === "string" ? question.question : `Question ${index + 1}`;
516
- const choices = Array.isArray(question.options) ? question.options : [];
517
- return {
518
- id: prompt,
519
- header: typeof question.header === "string" ? question.header : `Question ${index + 1}`,
520
- question: prompt,
521
- isOther: true,
522
- isSecret: false,
523
- options: choices.map((choice) => {
524
- const option = choice && typeof choice === "object" ? choice : {};
525
- return { label: String(option.label ?? ""), description: String(option.description ?? "") };
526
- }),
527
- };
528
- });
529
- const response = await this.interactions.open({
530
- threadId: this.threadId,
531
- turnId: this.active.turn.id,
532
- claudeRequestId: options.requestId,
533
- method: "item/tool/requestUserInput",
534
- params: { threadId: this.threadId, turnId: this.active.turn.id, itemId: options.toolUseID, questions, autoResolutionMs: null },
535
- signal: options.signal,
536
- });
537
- if (!response || typeof response !== "object" || "cancelled" in response) {
538
- return { behavior: "deny", message: "User cancelled the question." };
539
- }
540
- const rawAnswers = "answers" in response ? response.answers : undefined;
541
- const answers = {};
542
- if (rawAnswers && typeof rawAnswers === "object") {
543
- for (const [id, value] of Object.entries(rawAnswers)) {
544
- const values = value && typeof value === "object" && "answers" in value
545
- ? value.answers
546
- : undefined;
547
- if (Array.isArray(values))
548
- answers[id] = values.map(String).join(", ");
549
- }
550
- }
551
- return { behavior: "allow", updatedInput: { ...input, answers } };
552
- }
553
- async onElicitation(request, signal) {
554
- const response = await this.interactions.open({
555
- threadId: this.threadId,
556
- turnId: this.active?.turn.id ?? null,
557
- claudeRequestId: request.elicitationId ?? null,
558
- method: "mcpServer/elicitation/request",
559
- params: request.mode === "url"
560
- ? { threadId: this.threadId, turnId: this.active?.turn.id ?? null, serverName: request.serverName, mode: "url", _meta: null, message: request.message, url: request.url ?? "", elicitationId: request.elicitationId ?? "" }
561
- : { threadId: this.threadId, turnId: this.active?.turn.id ?? null, serverName: request.serverName, mode: "form", _meta: null, message: request.message, requestedSchema: request.requestedSchema ?? {} },
562
- signal,
563
- });
564
- if (!response || typeof response !== "object" || "cancelled" in response)
565
- return { action: "cancel" };
566
- const result = response;
567
- return {
568
- action: result.action ?? "cancel",
569
- ...(result.content ? { content: result.content } : {}),
570
- };
571
- }
572
- async beforeToolUse(input, toolUseId) {
573
- if (input.hook_event_name !== "PreToolUse" || !toolUseId || !this.active)
574
- return { continue: true };
575
- const toolInput = input.tool_input && typeof input.tool_input === "object"
576
- ? input.tool_input
577
- : {};
578
- const selected = toolPolicy(input.tool_name, toolInput, this.record.thread.cwd, this.record.approvalPolicy, this.record.sandboxPolicy);
579
- const policy = permissionMode(this.record) === "auto" && selected.decision === "ask"
580
- ? { decision: "defer" }
581
- : selected;
582
- if (policy.decision !== "deny") {
583
- const snapshot = await snapshotFile(input.tool_name, toolInput, this.record.thread.cwd);
584
- if (snapshot)
585
- this.fileSnapshots.set(toolUseId, snapshot);
586
- }
587
- if (policy.decision !== "deny" && this.sessionApprovedTools.has(input.tool_name))
588
- return { continue: true };
589
- if (policy.decision === "defer")
590
- return { continue: true };
591
- return {
592
- continue: true,
593
- hookSpecificOutput: {
594
- hookEventName: "PreToolUse",
595
- permissionDecision: policy.decision,
596
- ...(policy.reason ? { permissionDecisionReason: policy.reason } : {}),
597
- },
598
- };
599
- }
600
- async captureFileAfter(input, toolUseId) {
601
- if ((input.hook_event_name !== "PostToolUse" && input.hook_event_name !== "PostToolUseFailure") || !toolUseId) {
602
- return { continue: true };
603
- }
604
- const snapshot = this.fileSnapshots.get(toolUseId);
605
- this.fileSnapshots.delete(toolUseId);
606
- if (!snapshot || !this.active)
607
- return { continue: true };
608
- const change = await diffFile(snapshot);
609
- const tool = this.active.tools.get(toolUseId);
610
- const index = tool && this.active.turn.items.findIndex((item) => item.id === tool.itemId);
611
- const item = index !== undefined && index >= 0 ? this.active.turn.items[index] : undefined;
612
- if (!change || !item || item.type !== "fileChange")
613
- return { continue: true };
614
- item.changes = [change];
615
- this.persistActive();
616
- this.emitFileDiff(item);
617
- return { continue: true };
618
- }
619
- async handleMessage(message) {
620
- const previousId = this.providerEventId;
621
- const previousType = this.providerEventType;
622
- this.providerEventId = "uuid" in message && typeof message.uuid === "string" ? message.uuid : null;
623
- this.providerEventType = message.type === "system" && "subtype" in message ? `system/${message.subtype}` : message.type;
624
- if (this.providerEventId && this.store.hasProcessedProviderEvent(this.threadId, this.providerEventId)) {
625
- this.metrics.eventDeduplicated();
626
- this.providerEventId = previousId;
627
- this.providerEventType = previousType;
628
- return;
629
- }
630
- try {
631
- await this.handleProviderMessage(message);
632
- if (this.providerEventId && this.providerEventType) {
633
- this.store.markProviderEventProcessed(this.threadId, this.providerEventType, this.providerEventId);
634
- }
635
- }
636
- finally {
637
- this.providerEventId = previousId;
638
- this.providerEventType = previousType;
639
- }
640
- }
641
- async handleProviderMessage(message) {
642
- if (message.type === "system" && message.subtype === "hook_started") {
643
- const run = startHookRun(message, this.record.thread.cwd, Boolean(this.active), this.hookDisplayOrder++);
644
- if (run) {
645
- this.hookRuns.set(message.hook_id, run);
646
- this.emit("hook/started", { threadId: this.threadId, turnId: this.active?.turn.id ?? null, run });
647
- }
648
- return;
649
- }
650
- if (message.type === "system" && message.subtype === "hook_progress") {
651
- const run = this.hookRuns.get(message.hook_id);
652
- if (run)
653
- appendHookProgress(run, message);
654
- return;
655
- }
656
- if (message.type === "system" && message.subtype === "hook_response") {
657
- const run = this.hookRuns.get(message.hook_id);
658
- if (run) {
659
- completeHookRun(run, message);
660
- this.hookRuns.delete(message.hook_id);
661
- this.emit("hook/completed", { threadId: this.threadId, turnId: this.active?.turn.id ?? null, run });
662
- }
663
- return;
664
- }
665
- if (message.type === "system" && message.subtype === "task_started") {
666
- this.startTask(message);
667
- return;
668
- }
669
- if (message.type === "system" && message.subtype === "task_progress") {
670
- this.progressTask(message);
671
- return;
672
- }
673
- if (message.type === "system" && message.subtype === "task_updated") {
674
- this.updateTask(message);
675
- return;
676
- }
677
- if (message.type === "system" && message.subtype === "task_notification") {
678
- this.completeTask(message);
679
- return;
680
- }
681
- if (message.type === "system" && message.subtype === "background_tasks_changed") {
682
- this.backgroundTaskIds = new Set(message.tasks.map((task) => task.task_id));
683
- if (!this.active) {
684
- if (this.backgroundTaskIds.size === 0)
685
- this.completeBackgroundTurn();
686
- else
687
- this.updateThread({ ...this.record.thread, status: { type: "active", activeFlags: [] }, updatedAt: nowSeconds() });
688
- }
689
- return;
690
- }
691
- if (message.type === "system" && message.subtype === "status" && this.pendingCompaction && message.compact_result === "failed") {
692
- const pending = this.pendingCompaction;
693
- this.pendingCompaction = undefined;
694
- clearTimeout(pending.timer);
695
- pending.reject(new Error(message.compact_error ?? "Claude compaction failed."));
696
- return;
697
- }
698
- if (message.type === "system" && message.subtype === "compact_boundary") {
699
- await this.handleCompaction(message.compact_metadata);
700
- return;
701
- }
702
- if (message.type === "system" && message.subtype === "init") {
703
- if (message.session_id !== this.record.claudeSessionId) {
704
- throw new Error(`Claude initialized session '${message.session_id}', expected '${this.record.claudeSessionId}'.`);
705
- }
706
- this.record = {
707
- ...this.record,
708
- resolvedModel: message.model,
709
- claudeCodeVersion: message.claude_code_version,
710
- thread: { ...this.record.thread, cliVersion: message.claude_code_version },
711
- };
712
- this.capabilities.clear();
713
- for (const capability of message.capabilities ?? [])
714
- this.capabilities.add(capability);
715
- this.store.updateThread(this.record);
716
- return;
717
- }
718
- if (message.type === "stream_event") {
719
- await this.handleStreamEvent(message);
720
- return;
721
- }
722
- if (message.type === "assistant") {
723
- if (this.active) {
724
- if (message.error) {
725
- this.active.providerError = message.error;
726
- if (message.error === "authentication_failed" || message.error === "oauth_org_not_allowed" || message.error === "model_not_found") {
727
- this.invalidateModelCatalog();
728
- }
729
- }
730
- this.store.setTurnClaudeMessageUuid(this.threadId, this.active.turn.id, message.uuid);
731
- this.record = { ...this.record, lastClaudeMessageUuid: message.uuid };
732
- this.store.updateThread(this.record);
733
- if (Array.isArray(message.message.content)) {
734
- for (const block of message.message.content)
735
- this.completeServerToolResult(serverToolResult(block));
736
- }
737
- }
738
- await this.backfillAssistant(message);
739
- return;
740
- }
741
- if (message.type === "user") {
742
- if ("uuid" in message && typeof message.uuid === "string")
743
- this.ownedMessageIds.delete(message.uuid);
744
- this.handleToolResults(message);
745
- return;
746
- }
747
- if (message.type === "result") {
748
- if (!this.active && this.pendingCompaction && message.subtype !== "success") {
749
- const pending = this.pendingCompaction;
750
- this.pendingCompaction = undefined;
751
- clearTimeout(pending.timer);
752
- pending.reject(new Error(message.errors[0] ?? "Claude compaction failed."));
753
- return;
754
- }
755
- const result = classifyClaudeResult(message, this.active?.providerError);
756
- if (result.codexErrorInfo === "unauthorized" || result.codexErrorInfo === "badRequest")
757
- this.invalidateModelCatalog();
758
- if (result.status === "completed" && message.subtype === "success") {
759
- if (message.structured_output !== undefined)
760
- this.appendStructuredOutput(message.structured_output);
761
- this.emitUsage(message);
762
- await this.completeActive("completed");
763
- }
764
- else {
765
- await this.completeActive(result.status === "completed" ? "failed" : result.status, result.error, result.codexErrorInfo);
766
- }
767
- }
768
- }
769
- appendStructuredOutput(value) {
770
- if (!this.active)
771
- return;
772
- const text = JSON.stringify(value, null, 2);
773
- const item = { type: "agentMessage", id: uuidv7(), text, phase: null, memoryCitation: null };
774
- this.active.turn.items.push(item);
775
- this.active.completedItems.add(item.id);
776
- this.persistActive();
777
- this.emit("item/started", { item, threadId: this.threadId, turnId: this.active.turn.id, startedAtMs: Date.now() });
778
- this.emit("item/agentMessage/delta", { threadId: this.threadId, turnId: this.active.turn.id, itemId: item.id, delta: text });
779
- this.emit("item/completed", { item, threadId: this.threadId, turnId: this.active.turn.id, completedAtMs: Date.now() });
780
- }
781
- startTask(message) {
782
- if (message.skip_transcript) {
783
- this.hiddenTaskIds.add(message.task_id);
784
- return;
785
- }
786
- const turn = this.taskTurn();
787
- const existing = message.tool_use_id ? this.active?.tools.get(message.tool_use_id) : undefined;
788
- const existingItem = existing && turn.items.find((item) => item.id === existing.itemId);
789
- const subagent = Boolean(message.subagent_type) || message.task_type === "agent" || message.task_type === "subagent";
790
- const item = existingItem ?? (subagent
791
- ? {
792
- type: "collabAgentToolCall", id: message.tool_use_id ?? `claude-task:${uuidv7()}`, tool: "spawnAgent",
793
- status: "inProgress", senderThreadId: this.threadId, receiverThreadIds: [message.task_id],
794
- prompt: message.prompt ?? message.description, model: null, reasoningEffort: null,
795
- agentsStates: { [message.task_id]: { status: "running", message: message.description } },
796
- }
797
- : {
798
- type: "dynamicToolCall", id: message.tool_use_id ?? `claude-task:${uuidv7()}`, namespace: "claude",
799
- tool: message.task_type ?? "backgroundTask", arguments: { taskId: message.task_id, description: message.description },
800
- status: "inProgress", contentItems: null, success: null, durationMs: null,
801
- });
802
- if (item.type === "collabAgentToolCall") {
803
- item.receiverThreadIds = [message.task_id];
804
- item.agentsStates[message.task_id] = { status: "running", message: message.description };
805
- }
806
- if (!existingItem) {
807
- turn.items.push(item);
808
- this.persistTaskTurn(turn);
809
- this.emit("item/started", { item, threadId: this.threadId, turnId: turn.id, startedAtMs: Date.now() }, turn.id);
810
- }
811
- this.tasks.set(message.task_id, {
812
- taskId: message.task_id, itemId: item.id, turnId: turn.id, subagent, description: message.description, terminal: false,
813
- });
814
- }
815
- progressTask(message) {
816
- if (this.hiddenTaskIds.has(message.task_id))
817
- return;
818
- const task = this.taskForEvent(message.task_id, message.description, Boolean(message.subagent_type));
819
- if (!task)
820
- return;
821
- task.description = message.summary ?? message.description;
822
- const { turn, item } = this.taskItem(task);
823
- if (!turn || !item)
824
- return;
825
- if (item.type === "collabAgentToolCall") {
826
- item.agentsStates[message.task_id] = { status: "running", message: task.description };
827
- }
828
- else if (item.type === "dynamicToolCall") {
829
- item.contentItems = [{ type: "inputText", text: task.description }];
830
- }
831
- this.persistTaskTurn(turn);
832
- }
833
- updateTask(message) {
834
- if (this.hiddenTaskIds.has(message.task_id))
835
- return;
836
- const task = this.tasks.get(message.task_id);
837
- if (!task)
838
- return;
839
- if (message.patch.description)
840
- task.description = message.patch.description;
841
- const { turn, item } = this.taskItem(task);
842
- if (!turn || !item || item.type !== "collabAgentToolCall")
843
- return;
844
- const status = message.patch.status;
845
- item.agentsStates[message.task_id] = {
846
- status: status === "failed" ? "errored" : status === "killed" ? "interrupted" : status === "completed" ? "completed" : "running",
847
- message: message.patch.error ?? task.description,
848
- };
849
- this.persistTaskTurn(turn);
850
- }
851
- completeTask(message) {
852
- if (this.hiddenTaskIds.delete(message.task_id))
853
- return;
854
- const task = this.taskForEvent(message.task_id, message.summary, false);
855
- if (!task)
856
- return;
857
- const { turn, item } = this.taskItem(task);
858
- if (!turn || !item)
859
- return;
860
- task.terminal = true;
861
- if (item.type === "collabAgentToolCall") {
862
- item.status = message.status === "completed" ? "completed" : "failed";
863
- item.agentsStates[message.task_id] = {
864
- status: message.status === "completed" ? "completed" : message.status === "stopped" ? "interrupted" : "errored",
865
- message: message.summary,
866
- };
867
- }
868
- else if (item.type === "dynamicToolCall") {
869
- item.status = message.status === "completed" ? "completed" : "failed";
870
- item.success = message.status === "completed";
871
- item.durationMs = message.usage?.duration_ms ?? null;
872
- item.contentItems = message.summary ? [{ type: "inputText", text: message.summary }] : [];
873
- }
874
- this.persistTaskTurn(turn);
875
- if (this.active?.turn.id === turn.id)
876
- this.active.completedItems.add(item.id);
877
- this.emit("item/completed", { item, threadId: this.threadId, turnId: turn.id, completedAtMs: Date.now() }, turn.id);
878
- if (!this.active && !this.backgroundTaskIds.has(message.task_id))
879
- this.completeBackgroundTurn();
880
- }
881
- taskForEvent(taskId, description, subagent) {
882
- if (this.hiddenTaskIds.has(taskId))
883
- return undefined;
884
- const existing = this.tasks.get(taskId);
885
- if (existing && this.taskItem(existing).turn)
886
- return existing;
887
- const projectedSubagent = existing?.subagent ?? subagent;
888
- this.startTask({
889
- type: "system", subtype: "task_started", task_id: taskId, description,
890
- ...(projectedSubagent ? { subagent_type: "agent" } : {}),
891
- uuid: "00000000-0000-4000-8000-000000000001", session_id: this.record.claudeSessionId,
892
- });
893
- return this.tasks.get(taskId);
894
- }
895
- taskTurn() {
896
- if (this.active)
897
- return this.active.turn;
898
- if (this.backgroundTurn)
899
- return this.backgroundTurn;
900
- const turn = {
901
- id: uuidv7(), items: [], itemsView: "full", status: "inProgress", error: null,
902
- startedAt: nowSeconds(), completedAt: null, durationMs: null,
903
- };
904
- this.backgroundTurn = turn;
905
- this.store.createTurn(this.threadId, turn);
906
- this.updateThread({ ...this.record.thread, status: { type: "active", activeFlags: [] }, updatedAt: nowSeconds(), recencyAt: nowSeconds() });
907
- this.emit("turn/started", { threadId: this.threadId, turn }, turn.id);
908
- return turn;
909
- }
910
- taskItem(task) {
911
- const turn = this.active?.turn.id === task.turnId
912
- ? this.active.turn
913
- : this.backgroundTurn?.id === task.turnId
914
- ? this.backgroundTurn
915
- : undefined;
916
- return { turn, item: turn?.items.find((item) => item.id === task.itemId) };
917
- }
918
- persistTaskTurn(turn) {
919
- if (this.active?.turn.id === turn.id)
920
- this.persistActive();
921
- else
922
- this.store.updateTurn(this.threadId, turn);
923
- }
924
- completeBackgroundTurn(status = "completed", errorMessage) {
925
- const turn = this.backgroundTurn;
926
- if (!turn)
927
- return;
928
- const completedAt = nowSeconds();
929
- for (const item of turn.items) {
930
- if (item.type === "collabAgentToolCall" && item.status === "inProgress")
931
- item.status = "completed";
932
- else if (item.type === "dynamicToolCall" && item.status === "inProgress")
933
- item.status = "completed";
934
- }
935
- turn.status = status;
936
- turn.completedAt = completedAt;
937
- turn.durationMs = Math.max(0, (completedAt - (turn.startedAt ?? completedAt)) * 1_000);
938
- turn.error = status === "failed" ? { message: errorMessage ?? "Background Claude work failed.", codexErrorInfo: null, additionalDetails: null } : null;
939
- this.store.updateTurn(this.threadId, turn);
940
- this.backgroundTurn = undefined;
941
- for (const task of this.tasks.values())
942
- if (task.turnId === turn.id)
943
- task.turnId = null;
944
- this.record = { ...this.record, lastCompletedTurnId: turn.id };
945
- this.updateThread({
946
- ...this.record.thread,
947
- status: this.backgroundTaskIds.size > 0 ? { type: "active", activeFlags: [] } : { type: "idle" },
948
- updatedAt: completedAt, recencyAt: completedAt,
949
- });
950
- if (turn.error) {
951
- this.emit("error", { error: turn.error, willRetry: false, threadId: this.threadId, turnId: turn.id }, turn.id);
952
- }
953
- this.emit("turn/completed", { threadId: this.threadId, turn }, turn.id);
954
- }
955
- async handleCompaction(metadata) {
956
- const item = { type: "contextCompaction", id: uuidv7() };
957
- let turnId;
958
- if (this.active) {
959
- this.active.turn.items.push(item);
960
- this.active.completedItems.add(item.id);
961
- this.persistActive();
962
- turnId = this.active.turn.id;
963
- }
964
- else {
965
- const now = nowSeconds();
966
- const turn = {
967
- id: uuidv7(), items: [item], itemsView: "full", status: "completed", error: null,
968
- startedAt: now, completedAt: now, durationMs: 0,
969
- };
970
- this.store.createTurn(this.threadId, turn);
971
- turnId = turn.id;
972
- this.record = { ...this.record, lastCompletedTurnId: turn.id };
973
- this.updateThread({ ...this.record.thread, updatedAt: now, recencyAt: now, status: { type: "idle" } });
974
- }
975
- const timestamp = Date.now();
976
- this.emit("item/started", { item, threadId: this.threadId, turnId, startedAtMs: timestamp });
977
- this.emit("item/completed", { item, threadId: this.threadId, turnId, completedAtMs: timestamp });
978
- this.emit("thread/compacted", { threadId: this.threadId, turnId });
979
- let context;
980
- try {
981
- context = await this.sdkQuery.getContextUsage();
982
- }
983
- catch (error) {
984
- this.logger.debug("claude.context-usage.failed", { threadId: this.threadId, error: String(error) });
985
- }
986
- const contextTokens = context?.totalTokens ?? metadata.post_tokens;
987
- if (contextTokens !== undefined) {
988
- const last = {
989
- totalTokens: contextTokens, inputTokens: contextTokens,
990
- cachedInputTokens: 0, outputTokens: 0, reasoningOutputTokens: 0,
991
- };
992
- this.emit("thread/tokenUsage/updated", {
993
- threadId: this.threadId, turnId,
994
- tokenUsage: { total: this.record.tokenUsageTotal, last, modelContextWindow: context?.maxTokens ?? null },
995
- });
996
- }
997
- const pending = this.pendingCompaction;
998
- if (pending) {
999
- this.pendingCompaction = undefined;
1000
- clearTimeout(pending.timer);
1001
- pending.resolve();
1002
- }
1003
- }
1004
- emitUsage(message) {
1005
- if (!this.active)
1006
- return;
1007
- const inputTokens = message.usage.input_tokens + message.usage.cache_creation_input_tokens + message.usage.cache_read_input_tokens;
1008
- const last = {
1009
- totalTokens: inputTokens + message.usage.output_tokens,
1010
- inputTokens,
1011
- cachedInputTokens: message.usage.cache_read_input_tokens,
1012
- outputTokens: message.usage.output_tokens,
1013
- reasoningOutputTokens: 0,
1014
- };
1015
- const total = {
1016
- totalTokens: this.record.tokenUsageTotal.totalTokens + last.totalTokens,
1017
- inputTokens: this.record.tokenUsageTotal.inputTokens + last.inputTokens,
1018
- cachedInputTokens: this.record.tokenUsageTotal.cachedInputTokens + last.cachedInputTokens,
1019
- outputTokens: this.record.tokenUsageTotal.outputTokens + last.outputTokens,
1020
- reasoningOutputTokens: this.record.tokenUsageTotal.reasoningOutputTokens + last.reasoningOutputTokens,
1021
- };
1022
- this.record = { ...this.record, tokenUsageTotal: total };
1023
- this.store.updateThread(this.record);
1024
- const contextWindows = Object.values(message.modelUsage).map((usage) => usage.contextWindow);
1025
- this.emit("thread/tokenUsage/updated", {
1026
- threadId: this.threadId,
1027
- turnId: this.active.turn.id,
1028
- tokenUsage: {
1029
- total,
1030
- last,
1031
- modelContextWindow: contextWindows.length > 0 ? Math.max(...contextWindows) : null,
1032
- },
1033
- });
1034
- }
1035
- async handleStreamEvent(message) {
1036
- const event = message.event;
1037
- if (!this.active)
1038
- return;
1039
- if (event.type === "message_start") {
1040
- this.active.blockItems.clear();
1041
- return;
1042
- }
1043
- if (event.type === "content_block_start") {
1044
- const block = event.content_block;
1045
- if (block.type === "text")
1046
- this.createAgentItem(event.index);
1047
- else if (block.type === "thinking")
1048
- this.createReasoningItem(event.index);
1049
- else if (block.type === "tool_use" || block.type === "server_tool_use" || block.type === "mcp_tool_use") {
1050
- this.createToolItem(event.index, block);
1051
- }
1052
- else
1053
- this.completeServerToolResult(serverToolResult(block));
1054
- return;
1055
- }
1056
- if (event.type === "content_block_delta") {
1057
- if (event.delta.type === "text_delta")
1058
- this.appendAgentText(event.index, event.delta.text);
1059
- else if (event.delta.type === "thinking_delta")
1060
- this.appendReasoning(event.index, event.delta.thinking);
1061
- else if (event.delta.type === "input_json_delta")
1062
- this.appendToolInput(event.index, event.delta.partial_json);
1063
- return;
1064
- }
1065
- if (event.type === "content_block_stop" && !this.toolAt(event.index))
1066
- this.completeItem(event.index);
1067
- }
1068
- createToolItem(index, block) {
1069
- if (!this.active)
1070
- throw new Error("No active turn.");
1071
- const existing = this.itemAt(index);
1072
- if (existing)
1073
- return existing;
1074
- const { state, item } = startTool(index, block, this.record.thread.cwd, this.threadId);
1075
- this.active.blockItems.set(index, item.id);
1076
- this.active.tools.set(state.providerId, state);
1077
- this.active.turn.items.push(item);
1078
- this.persistActive();
1079
- this.startToolItem(state, item);
1080
- return item;
1081
- }
1082
- appendToolInput(index, delta) {
1083
- const tool = this.toolAt(index);
1084
- const item = this.itemAt(index);
1085
- if (!tool || !item)
1086
- return;
1087
- tool.partialInput += delta;
1088
- try {
1089
- const input = JSON.parse(tool.partialInput);
1090
- updateToolInput(item, tool, input);
1091
- this.persistActive();
1092
- this.startToolItem(tool, item);
1093
- this.emitPlan(item, input);
1094
- }
1095
- catch {
1096
- // Partial JSON is expected until the final input_json_delta.
1097
- }
1098
- }
1099
- handleToolResults(message) {
1100
- if (!this.active)
1101
- return;
1102
- const structured = message.tool_use_result;
1103
- const result = structured && typeof structured === "object" ? structured : undefined;
1104
- for (const toolResult of toolResults(message)) {
1105
- const tool = this.active.tools.get(toolResult.toolUseId);
1106
- if (!tool || this.active.completedItems.has(tool.itemId))
1107
- continue;
1108
- const index = this.active.turn.items.findIndex((item) => item.id === tool.itemId);
1109
- const item = this.active.turn.items[index];
1110
- if (!item)
1111
- continue;
1112
- this.startToolItem(tool, item, true);
1113
- const previousChanges = item.type === "fileChange" ? JSON.stringify(item.changes) : undefined;
1114
- const completed = completeTool(item, toolResult.output, toolResult.isError, result, tool.startedAtMs);
1115
- this.active.turn.items[index] = completed;
1116
- this.active.completedItems.add(completed.id);
1117
- this.persistActive();
1118
- if (completed.type === "commandExecution" && toolResult.output) {
1119
- this.emit("item/commandExecution/outputDelta", { threadId: this.threadId, turnId: this.active.turn.id, itemId: completed.id, delta: toolResult.output });
1120
- }
1121
- if (completed.type === "fileChange" && completed.changes.length > 0 && JSON.stringify(completed.changes) !== previousChanges)
1122
- this.emitFileDiff(completed);
1123
- this.emit("item/completed", { item: completed, threadId: this.threadId, turnId: this.active.turn.id, completedAtMs: Date.now() });
1124
- }
1125
- }
1126
- completeServerToolResult(result) {
1127
- if (!this.active || !result)
1128
- return;
1129
- const tool = this.active.tools.get(result.toolUseId);
1130
- if (!tool || this.active.completedItems.has(tool.itemId))
1131
- return;
1132
- const index = this.active.turn.items.findIndex((item) => item.id === tool.itemId);
1133
- const item = this.active.turn.items[index];
1134
- if (!item)
1135
- return;
1136
- this.startToolItem(tool, item, true);
1137
- const completed = completeTool(item, result.output, result.isError, undefined, tool.startedAtMs);
1138
- this.active.turn.items[index] = completed;
1139
- this.active.completedItems.add(completed.id);
1140
- this.persistActive();
1141
- this.emit("item/completed", {
1142
- item: completed, threadId: this.threadId, turnId: this.active.turn.id, completedAtMs: Date.now(),
1143
- });
1144
- }
1145
- emitFileDiff(item) {
1146
- if (!this.active)
1147
- return;
1148
- this.emit("item/fileChange/patchUpdated", {
1149
- threadId: this.threadId, turnId: this.active.turn.id, itemId: item.id, changes: item.changes,
1150
- });
1151
- const diff = this.active.turn.items.flatMap((candidate) => candidate.type === "fileChange" ? candidate.changes.map((change) => change.diff) : []).join("\n");
1152
- this.emit("turn/diff/updated", { threadId: this.threadId, turnId: this.active.turn.id, diff });
1153
- }
1154
- emitPlan(item, input) {
1155
- if (!this.active || item.type !== "plan")
1156
- return;
1157
- const plan = planSteps(input);
1158
- if (plan.length > 0)
1159
- this.emit("turn/plan/updated", { threadId: this.threadId, turnId: this.active.turn.id, explanation: null, plan });
1160
- }
1161
- startToolItem(tool, item, force = false) {
1162
- if (!this.active || tool.started || (item.type === "commandExecution" && !item.command))
1163
- return;
1164
- if (!force && !this.sessionApprovedTools.has(tool.name)) {
1165
- const policy = toolPolicy(tool.name, tool.input, this.record.thread.cwd, this.record.approvalPolicy, this.record.sandboxPolicy);
1166
- if (policy.decision === "ask")
1167
- return;
1168
- }
1169
- tool.started = true;
1170
- this.emit("item/started", {
1171
- item, threadId: this.threadId, turnId: this.active.turn.id, startedAtMs: tool.startedAtMs,
1172
- });
1173
- this.emitPlan(item, tool.input);
1174
- }
1175
- itemAt(index) {
1176
- if (!this.active)
1177
- return undefined;
1178
- const itemId = this.active.blockItems.get(index);
1179
- return itemId ? this.active.turn.items.find((item) => item.id === itemId) : undefined;
1180
- }
1181
- toolAt(index) {
1182
- if (!this.active)
1183
- return undefined;
1184
- const itemId = this.active.blockItems.get(index);
1185
- return itemId ? [...this.active.tools.values()].find((tool) => tool.itemId === itemId) : undefined;
1186
- }
1187
- createAgentItem(index) {
1188
- if (!this.active)
1189
- throw new Error("No active turn.");
1190
- const existingId = this.active.blockItems.get(index);
1191
- const existing = existingId && this.active.turn.items.find((item) => item.id === existingId);
1192
- if (existing)
1193
- return existing;
1194
- const item = { type: "agentMessage", id: uuidv7(), text: "", phase: null, memoryCitation: null };
1195
- this.active.blockItems.set(index, item.id);
1196
- this.active.turn.items.push(item);
1197
- this.persistActive();
1198
- this.emit("item/started", { item, threadId: this.threadId, turnId: this.active.turn.id, startedAtMs: Date.now() });
1199
- return item;
1200
- }
1201
- createReasoningItem(index) {
1202
- if (!this.active)
1203
- throw new Error("No active turn.");
1204
- const existingId = this.active.blockItems.get(index);
1205
- const existing = existingId && this.active.turn.items.find((item) => item.id === existingId);
1206
- if (existing)
1207
- return existing;
1208
- const item = { type: "reasoning", id: uuidv7(), summary: [], content: [] };
1209
- this.active.blockItems.set(index, item.id);
1210
- this.active.turn.items.push(item);
1211
- this.persistActive();
1212
- this.emit("item/started", { item, threadId: this.threadId, turnId: this.active.turn.id, startedAtMs: Date.now() });
1213
- return item;
1214
- }
1215
- appendAgentText(index, delta) {
1216
- if (!this.active)
1217
- return;
1218
- const item = this.createAgentItem(index);
1219
- if (item.type !== "agentMessage")
1220
- return;
1221
- item.text += delta;
1222
- this.persistActive();
1223
- this.emit("item/agentMessage/delta", { threadId: this.threadId, turnId: this.active.turn.id, itemId: item.id, delta });
1224
- }
1225
- appendReasoning(index, delta) {
1226
- if (!this.active)
1227
- return;
1228
- const item = this.createReasoningItem(index);
1229
- if (item.type !== "reasoning")
1230
- return;
1231
- item.content[0] = `${item.content[0] ?? ""}${delta}`;
1232
- this.persistActive();
1233
- this.emit("item/reasoning/textDelta", { threadId: this.threadId, turnId: this.active.turn.id, itemId: item.id, delta, contentIndex: 0 });
1234
- }
1235
- completeItem(index) {
1236
- if (!this.active)
1237
- return;
1238
- const itemId = this.active.blockItems.get(index);
1239
- const item = itemId && this.active.turn.items.find((candidate) => candidate.id === itemId);
1240
- if (!item || this.active.completedItems.has(item.id))
1241
- return;
1242
- this.active.completedItems.add(item.id);
1243
- this.persistActive();
1244
- this.emit("item/completed", { item, threadId: this.threadId, turnId: this.active.turn.id, completedAtMs: Date.now() });
1245
- }
1246
- async backfillAssistant(message) {
1247
- if (!this.active)
1248
- return;
1249
- for (const text of textFromAssistant(message)) {
1250
- const alreadyPresent = this.active.turn.items.some((item) => item.type === "agentMessage" && item.text === text);
1251
- if (alreadyPresent)
1252
- continue;
1253
- const item = { type: "agentMessage", id: uuidv7(), text, phase: null, memoryCitation: null };
1254
- this.active.turn.items.push(item);
1255
- this.active.completedItems.add(item.id);
1256
- this.persistActive();
1257
- this.emit("item/started", { item, threadId: this.threadId, turnId: this.active.turn.id, startedAtMs: Date.now() });
1258
- this.emit("item/agentMessage/delta", { threadId: this.threadId, turnId: this.active.turn.id, itemId: item.id, delta: text });
1259
- this.emit("item/completed", { item, threadId: this.threadId, turnId: this.active.turn.id, completedAtMs: Date.now() });
1260
- }
1261
- }
1262
- async failActive(message) {
1263
- await this.completeActive("failed", message, classifyClaudeRuntimeError(message));
1264
- }
1265
- async completeActive(status, errorMessage, codexErrorInfo = null) {
1266
- if (!this.active)
1267
- return;
1268
- for (const tool of this.active.tools.values()) {
1269
- if (this.active.completedItems.has(tool.itemId))
1270
- continue;
1271
- const index = this.active.turn.items.findIndex((item) => item.id === tool.itemId);
1272
- const item = this.active.turn.items[index];
1273
- if (!item)
1274
- continue;
1275
- this.active.turn.items[index] = completeTool(item, "Claude turn ended before the tool returned a result.", true, undefined, tool.startedAtMs);
1276
- }
1277
- for (const index of this.active.blockItems.keys())
1278
- this.completeItem(index);
1279
- if (this.active.review) {
1280
- const item = { type: "exitedReviewMode", id: uuidv7(), review: this.active.review };
1281
- this.active.turn.items.push(item);
1282
- this.active.completedItems.add(item.id);
1283
- this.persistActive();
1284
- this.emit("item/started", { item, threadId: this.threadId, turnId: this.active.turn.id, startedAtMs: Date.now() });
1285
- this.emit("item/completed", { item, threadId: this.threadId, turnId: this.active.turn.id, completedAtMs: Date.now() });
1286
- }
1287
- for (const task of this.tasks.values()) {
1288
- if (task.turnId !== this.active.turn.id || task.terminal)
1289
- continue;
1290
- const item = this.active.turn.items.find((candidate) => candidate.id === task.itemId);
1291
- if (item?.type === "collabAgentToolCall")
1292
- item.status = "completed";
1293
- else if (item?.type === "dynamicToolCall")
1294
- item.status = "completed";
1295
- if (item) {
1296
- this.active.completedItems.add(item.id);
1297
- this.emit("item/completed", { item, threadId: this.threadId, turnId: this.active.turn.id, completedAtMs: Date.now() });
1298
- }
1299
- task.turnId = null;
1300
- }
1301
- const completedAt = nowSeconds();
1302
- const startedAt = this.active.turn.startedAt ?? completedAt;
1303
- this.active.turn = {
1304
- ...this.active.turn,
1305
- status,
1306
- completedAt,
1307
- durationMs: Math.max(0, (completedAt - startedAt) * 1_000),
1308
- error: status === "failed"
1309
- ? { message: errorMessage ?? "Claude turn failed.", codexErrorInfo, additionalDetails: null }
1310
- : null,
1311
- };
1312
- this.persistActive();
1313
- const turn = this.active.turn;
1314
- this.active = undefined;
1315
- this.lastActivityMs = Date.now();
1316
- this.record = { ...this.record, lastCompletedTurnId: turn.id };
1317
- this.updateThread({
1318
- ...this.record.thread,
1319
- status: this.backgroundTaskIds.size > 0 ? { type: "active", activeFlags: [] } : { type: "idle" },
1320
- updatedAt: completedAt, recencyAt: completedAt,
1321
- });
1322
- if (turn.error) {
1323
- this.emit("error", { error: turn.error, willRetry: false, threadId: this.threadId, turnId: turn.id }, turn.id);
1324
- }
1325
- this.emit("turn/completed", { threadId: this.threadId, turn });
1326
- }
1327
- persistActive() {
1328
- if (this.active)
1329
- this.store.updateTurn(this.threadId, this.active.turn);
1330
- }
1331
- updateThread(thread) {
1332
- this.record = { ...this.record, thread };
1333
- this.store.updateThread(this.record);
1334
- this.emit("thread/status/changed", { threadId: this.threadId, status: thread.status });
1335
- }
1336
- emit(method, params, turnId = this.active?.turn.id ?? null) {
1337
- const value = params && typeof params === "object" ? params : undefined;
1338
- const parameterTurn = value?.turn && typeof value.turn === "object" && "items" in value.turn
1339
- ? value.turn
1340
- : undefined;
1341
- const runtimeTurn = this.active?.turn.id === turnId
1342
- ? this.active.turn
1343
- : this.backgroundTurn?.id === turnId
1344
- ? this.backgroundTurn
1345
- : undefined;
1346
- const persistedTurn = parameterTurn ?? runtimeTurn;
1347
- const sequence = this.store.appendEvent(this.threadId, turnId, method, params, {
1348
- ...(persistedTurn ? { turn: persistedTurn } : {}),
1349
- providerEventId: this.providerEventId,
1350
- providerEventType: this.providerEventType,
1351
- dedupKey: eventDedupKey(method, params),
1352
- });
1353
- if (sequence === 0)
1354
- return;
1355
- if (method === "turn/completed" && parameterTurn && parameterTurn.status !== "inProgress") {
1356
- this.metrics.turnCompleted(parameterTurn.status);
1357
- }
1358
- this.hub.emit(this.threadId, method, params);
1359
- }
1360
- }
1361
- //# sourceMappingURL=sessionRuntime.js.map