@gkorepanov/ccodex 0.3.0 → 0.3.4

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 (176) hide show
  1. package/README.md +27 -9
  2. package/RELEASING.md +14 -15
  3. package/SSH_VERIFY.md +11 -0
  4. package/archive/README.md +23 -0
  5. package/compatibility.json +1 -2
  6. package/config.example.toml +12 -4
  7. package/dist/claude/asyncQueue.d.ts +3 -0
  8. package/dist/claude/asyncQueue.js +13 -2
  9. package/dist/claude/asyncQueue.js.map +1 -1
  10. package/dist/claude/commandActions.d.ts +6 -0
  11. package/dist/claude/commandActions.js +57 -0
  12. package/dist/claude/commandActions.js.map +1 -0
  13. package/dist/claude/compactCommand.d.ts +2 -0
  14. package/dist/claude/compactCommand.js +7 -0
  15. package/dist/claude/compactCommand.js.map +1 -0
  16. package/dist/claude/goalTools.d.ts +15 -0
  17. package/dist/claude/goalTools.js +173 -0
  18. package/dist/claude/goalTools.js.map +1 -0
  19. package/dist/claude/hookMapper.d.ts +17 -4
  20. package/dist/claude/hookMapper.js +7 -5
  21. package/dist/claude/hookMapper.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 +1 -1
  30. package/dist/claude/resultClassifier.js.map +1 -1
  31. package/dist/claude/service.d.ts +108 -28
  32. package/dist/claude/service.js +1276 -668
  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 +855 -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/providerFacts.d.ts +47 -0
  50. package/dist/claude/session/providerFacts.js +84 -0
  51. package/dist/claude/session/providerFacts.js.map +1 -0
  52. package/dist/claude/session/providerRuntime.d.ts +1 -0
  53. package/dist/claude/session/providerRuntime.js +2 -0
  54. package/dist/claude/session/providerRuntime.js.map +1 -0
  55. package/dist/claude/session/providerRuntimeFactory.d.ts +49 -0
  56. package/dist/claude/session/providerRuntimeFactory.js +127 -0
  57. package/dist/claude/session/providerRuntimeFactory.js.map +1 -0
  58. package/dist/claude/session/repository.d.ts +57 -0
  59. package/dist/claude/session/repository.js +161 -0
  60. package/dist/claude/session/repository.js.map +1 -0
  61. package/dist/claude/session/runtime.d.ts +46 -0
  62. package/dist/claude/session/runtime.js +140 -0
  63. package/dist/claude/session/runtime.js.map +1 -0
  64. package/dist/claude/session/scopeState.d.ts +61 -0
  65. package/dist/claude/session/scopeState.js +74 -0
  66. package/dist/claude/session/scopeState.js.map +1 -0
  67. package/dist/claude/session/session.d.ts +281 -0
  68. package/dist/claude/session/session.js +6664 -0
  69. package/dist/claude/session/session.js.map +1 -0
  70. package/dist/claude/session/shellRunner.d.ts +13 -0
  71. package/dist/claude/session/shellRunner.js +103 -0
  72. package/dist/claude/session/shellRunner.js.map +1 -0
  73. package/dist/claude/session/usage.d.ts +2 -0
  74. package/dist/claude/session/usage.js +10 -0
  75. package/dist/claude/session/usage.js.map +1 -0
  76. package/dist/claude/sessionRegistry.d.ts +26 -0
  77. package/dist/claude/sessionRegistry.js +118 -0
  78. package/dist/claude/sessionRegistry.js.map +1 -0
  79. package/dist/claude/shellSupervisor.d.ts +1 -0
  80. package/dist/claude/shellSupervisor.js +69 -0
  81. package/dist/claude/shellSupervisor.js.map +1 -0
  82. package/dist/claude/statusCommand.d.ts +21 -0
  83. package/dist/claude/statusCommand.js +97 -0
  84. package/dist/claude/statusCommand.js.map +1 -0
  85. package/dist/claude/toolMapper.d.ts +9 -1
  86. package/dist/claude/toolMapper.js +92 -16
  87. package/dist/claude/toolMapper.js.map +1 -1
  88. package/dist/claude/transcriptBrancher.d.ts +37 -0
  89. package/dist/claude/transcriptBrancher.js +103 -0
  90. package/dist/claude/transcriptBrancher.js.map +1 -0
  91. package/dist/cli/delegate.js +23 -1
  92. package/dist/cli/delegate.js.map +1 -1
  93. package/dist/cli/main.js +28 -12
  94. package/dist/cli/main.js.map +1 -1
  95. package/dist/codex/generated/ResponseItemRuntimeSchema.d.ts +8 -0
  96. package/dist/codex/generated/ResponseItemRuntimeSchema.js +7 -0
  97. package/dist/codex/generated/ResponseItemRuntimeSchema.js.map +1 -0
  98. package/dist/compatibility/probe.js +11 -6
  99. package/dist/compatibility/probe.js.map +1 -1
  100. package/dist/config/config.d.ts +9 -0
  101. package/dist/config/config.js +80 -11
  102. package/dist/config/config.js.map +1 -1
  103. package/dist/daemon/daemon.js +91 -43
  104. package/dist/daemon/daemon.js.map +1 -1
  105. package/dist/daemon/ownership.d.ts +15 -0
  106. package/dist/daemon/ownership.js +185 -0
  107. package/dist/daemon/ownership.js.map +1 -0
  108. package/dist/daemon/supervisor.d.ts +3 -0
  109. package/dist/daemon/supervisor.js +37 -1
  110. package/dist/daemon/supervisor.js.map +1 -1
  111. package/dist/gateway/clientConnection.d.ts +7 -1
  112. package/dist/gateway/clientConnection.js +435 -48
  113. package/dist/gateway/clientConnection.js.map +1 -1
  114. package/dist/gateway/remoteRelay.js +0 -1
  115. package/dist/gateway/remoteRelay.js.map +1 -1
  116. package/dist/gateway/server.d.ts +1 -1
  117. package/dist/gateway/server.js +37 -9
  118. package/dist/gateway/server.js.map +1 -1
  119. package/dist/gateway/subscriptions.d.ts +9 -2
  120. package/dist/gateway/subscriptions.js +87 -9
  121. package/dist/gateway/subscriptions.js.map +1 -1
  122. package/dist/gateway/transientNotice.d.ts +2 -0
  123. package/dist/gateway/transientNotice.js +42 -1
  124. package/dist/gateway/transientNotice.js.map +1 -1
  125. package/dist/handoff/service.d.ts +32 -3
  126. package/dist/handoff/service.js +304 -26
  127. package/dist/handoff/service.js.map +1 -1
  128. package/dist/handoff/titleGeneration.d.ts +28 -0
  129. package/dist/handoff/titleGeneration.js +151 -0
  130. package/dist/handoff/titleGeneration.js.map +1 -0
  131. package/dist/management/doctor.d.ts +1 -1
  132. package/dist/management/doctor.js +32 -23
  133. package/dist/management/doctor.js.map +1 -1
  134. package/dist/management/lifecycle.js +14 -2
  135. package/dist/management/lifecycle.js.map +1 -1
  136. package/dist/management/remoteShim.d.ts +10 -0
  137. package/dist/management/remoteShim.js +65 -0
  138. package/dist/management/remoteShim.js.map +1 -0
  139. package/dist/management/setup.d.ts +4 -0
  140. package/dist/management/setup.js +108 -39
  141. package/dist/management/setup.js.map +1 -1
  142. package/dist/observability/metrics.d.ts +2 -0
  143. package/dist/observability/metrics.js +6 -0
  144. package/dist/observability/metrics.js.map +1 -1
  145. package/dist/protocol/errors.d.ts +1 -0
  146. package/dist/protocol/errors.js +4 -1
  147. package/dist/protocol/errors.js.map +1 -1
  148. package/dist/runtime/dependencies.js +2 -2
  149. package/dist/runtime/dependencies.js.map +1 -1
  150. package/dist/runtime/providerAvailability.d.ts +24 -0
  151. package/dist/runtime/providerAvailability.js +107 -0
  152. package/dist/runtime/providerAvailability.js.map +1 -0
  153. package/dist/state/stateCommand.d.ts +21 -0
  154. package/dist/state/stateCommand.js +198 -0
  155. package/dist/state/stateCommand.js.map +1 -0
  156. package/dist/state/stockStateTracker.d.ts +14 -0
  157. package/dist/state/stockStateTracker.js +120 -0
  158. package/dist/state/stockStateTracker.js.map +1 -0
  159. package/dist/store/HybridStore.d.ts +118 -2
  160. package/dist/store/HybridStore.js +23 -1
  161. package/dist/store/HybridStore.js.map +1 -1
  162. package/dist/store/memoryStore.d.ts +57 -7
  163. package/dist/store/memoryStore.js +337 -5
  164. package/dist/store/memoryStore.js.map +1 -1
  165. package/dist/store/sqliteStore.d.ts +30 -4
  166. package/dist/store/sqliteStore.js +395 -42
  167. package/dist/store/sqliteStore.js.map +1 -1
  168. package/install.sh +2 -2
  169. package/package.json +18 -10
  170. package/dist/claude/interactions.d.ts +0 -23
  171. package/dist/claude/interactions.js +0 -77
  172. package/dist/claude/interactions.js.map +0 -1
  173. package/dist/claude/sessionRuntime.d.ts +0 -102
  174. package/dist/claude/sessionRuntime.js +0 -1361
  175. package/dist/claude/sessionRuntime.js.map +0 -1
  176. /package/{BUILD_BENCHMARKS.md → archive/BUILD_BENCHMARKS.md} +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