@geteigen/cli 0.0.0-stage → 3.0.0

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 (168) hide show
  1. package/README.md +154 -2
  2. package/dist/agent.d.ts +32 -0
  3. package/dist/agent.js +246 -0
  4. package/dist/auth.d.ts +5 -0
  5. package/dist/auth.js +33 -0
  6. package/dist/cli/src/agent.d.ts +24 -0
  7. package/dist/cli/src/agent.js +260 -0
  8. package/dist/cli/src/agentTools.d.ts +10 -0
  9. package/dist/cli/src/agentTools.js +70 -0
  10. package/dist/cli/src/auth.d.ts +5 -0
  11. package/dist/cli/src/auth.js +33 -0
  12. package/dist/cli/src/config.d.ts +11 -0
  13. package/dist/cli/src/config.js +51 -0
  14. package/dist/cli/src/cursorAuth.d.ts +10 -0
  15. package/dist/cli/src/cursorAuth.js +92 -0
  16. package/dist/cli/src/index.d.ts +2 -0
  17. package/dist/cli/src/index.js +244 -0
  18. package/dist/cli/src/listModels.d.ts +14 -0
  19. package/dist/cli/src/listModels.js +196 -0
  20. package/dist/cli/src/listSessions.d.ts +9 -0
  21. package/dist/cli/src/listSessions.js +37 -0
  22. package/dist/cli/src/machineConfig.d.ts +20 -0
  23. package/dist/cli/src/machineConfig.js +66 -0
  24. package/dist/cli/src/pickFolder.d.ts +5 -0
  25. package/dist/cli/src/pickFolder.js +65 -0
  26. package/dist/cli/src/proc.d.ts +23 -0
  27. package/dist/cli/src/proc.js +77 -0
  28. package/dist/cli/src/service.d.ts +17 -0
  29. package/dist/cli/src/service.js +246 -0
  30. package/dist/cli/src/workRoots.d.ts +30 -0
  31. package/dist/cli/src/workRoots.js +221 -0
  32. package/dist/cli/src/worker.d.ts +8 -0
  33. package/dist/cli/src/worker.js +51 -0
  34. package/dist/cli/src/workerCore.d.ts +47 -0
  35. package/dist/cli/src/workerCore.js +702 -0
  36. package/dist/cli/src/workspace.d.ts +8 -0
  37. package/dist/cli/src/workspace.js +269 -0
  38. package/dist/config.d.ts +11 -0
  39. package/dist/config.js +51 -0
  40. package/dist/cursorAuth.d.ts +10 -0
  41. package/dist/cursorAuth.js +95 -0
  42. package/dist/index.d.ts +2 -0
  43. package/dist/index.js +108 -0
  44. package/dist/listModels.d.ts +6 -0
  45. package/dist/listModels.js +62 -0
  46. package/dist/listSessions.d.ts +9 -0
  47. package/dist/listSessions.js +37 -0
  48. package/dist/pickFolder.d.ts +5 -0
  49. package/dist/pickFolder.js +65 -0
  50. package/dist/shared/actionDrafts.d.ts +83 -0
  51. package/dist/shared/actionDrafts.js +169 -0
  52. package/dist/shared/agentArtifacts.d.ts +49 -0
  53. package/dist/shared/agentArtifacts.js +169 -0
  54. package/dist/shared/agentLabel.d.ts +18 -0
  55. package/dist/shared/agentLabel.js +22 -0
  56. package/dist/shared/agentMentions.d.ts +38 -0
  57. package/dist/shared/agentMentions.js +110 -0
  58. package/dist/shared/agentRepos.d.ts +28 -0
  59. package/dist/shared/agentRepos.js +60 -0
  60. package/dist/shared/agentRunStatus.d.ts +12 -0
  61. package/dist/shared/agentRunStatus.js +17 -0
  62. package/dist/shared/agentUsage.d.ts +24 -0
  63. package/dist/shared/agentUsage.js +139 -0
  64. package/dist/shared/approvals.d.ts +44 -0
  65. package/dist/shared/approvals.js +97 -0
  66. package/dist/shared/askEigen.d.ts +190 -0
  67. package/dist/shared/askEigen.js +76 -0
  68. package/dist/shared/attribution.d.ts +25 -0
  69. package/dist/shared/attribution.js +63 -0
  70. package/dist/shared/automations.d.ts +136 -0
  71. package/dist/shared/automations.js +546 -0
  72. package/dist/shared/backends/claudeCode.d.ts +42 -0
  73. package/dist/shared/backends/claudeCode.js +540 -0
  74. package/dist/shared/backends/codex.d.ts +15 -0
  75. package/dist/shared/backends/codex.js +287 -0
  76. package/dist/shared/backends/cursor.d.ts +102 -0
  77. package/dist/shared/backends/cursor.js +1197 -0
  78. package/dist/shared/backends/index.d.ts +18 -0
  79. package/dist/shared/backends/index.js +58 -0
  80. package/dist/shared/backends/opencode.d.ts +40 -0
  81. package/dist/shared/backends/opencode.js +397 -0
  82. package/dist/shared/backends/types.d.ts +171 -0
  83. package/dist/shared/backends/types.js +19 -0
  84. package/dist/shared/backends/usage.d.ts +8 -0
  85. package/dist/shared/backends/usage.js +71 -0
  86. package/dist/shared/billingPlans.d.ts +60 -0
  87. package/dist/shared/billingPlans.js +262 -0
  88. package/dist/shared/brain.d.ts +353 -0
  89. package/dist/shared/brain.js +487 -0
  90. package/dist/shared/clarifyingQuestions.d.ts +13 -0
  91. package/dist/shared/clarifyingQuestions.js +207 -0
  92. package/dist/shared/claudeModels.d.ts +10 -0
  93. package/dist/shared/claudeModels.js +52 -0
  94. package/dist/shared/cloudBackends.d.ts +21 -0
  95. package/dist/shared/cloudBackends.js +29 -0
  96. package/dist/shared/codexModels.d.ts +10 -0
  97. package/dist/shared/codexModels.js +28 -0
  98. package/dist/shared/contextWindows.d.ts +2 -0
  99. package/dist/shared/contextWindows.js +30 -0
  100. package/dist/shared/cursorMcpConfig.d.ts +14 -0
  101. package/dist/shared/cursorMcpConfig.js +43 -0
  102. package/dist/shared/cursorProjectMcp.d.ts +10 -0
  103. package/dist/shared/cursorProjectMcp.js +42 -0
  104. package/dist/shared/docs.d.ts +59 -0
  105. package/dist/shared/docs.js +87 -0
  106. package/dist/shared/events.d.ts +929 -0
  107. package/dist/shared/events.js +59 -0
  108. package/dist/shared/integrations.d.ts +266 -0
  109. package/dist/shared/integrations.js +449 -0
  110. package/dist/shared/issueTranscript.d.ts +64 -0
  111. package/dist/shared/issueTranscript.js +387 -0
  112. package/dist/shared/issues.d.ts +139 -0
  113. package/dist/shared/issues.js +217 -0
  114. package/dist/shared/machines.d.ts +115 -0
  115. package/dist/shared/machines.js +58 -0
  116. package/dist/shared/markdownMedia.d.ts +13 -0
  117. package/dist/shared/markdownMedia.js +41 -0
  118. package/dist/shared/notificationText.d.ts +6 -0
  119. package/dist/shared/notificationText.js +38 -0
  120. package/dist/shared/opencodeModels.d.ts +19 -0
  121. package/dist/shared/opencodeModels.js +62 -0
  122. package/dist/shared/opencodeSession.d.ts +7 -0
  123. package/dist/shared/opencodeSession.js +30 -0
  124. package/dist/shared/orchestration.d.ts +58 -0
  125. package/dist/shared/orchestration.js +12 -0
  126. package/dist/shared/orgs.d.ts +47 -0
  127. package/dist/shared/orgs.js +49 -0
  128. package/dist/shared/personalOrg.d.ts +5 -0
  129. package/dist/shared/personalOrg.js +12 -0
  130. package/dist/shared/plans.d.ts +3 -0
  131. package/dist/shared/plans.js +19 -0
  132. package/dist/shared/providerModels.d.ts +40 -0
  133. package/dist/shared/providerModels.js +503 -0
  134. package/dist/shared/roomAttention.d.ts +17 -0
  135. package/dist/shared/roomAttention.js +36 -0
  136. package/dist/shared/roomContext.d.ts +120 -0
  137. package/dist/shared/roomContext.js +133 -0
  138. package/dist/shared/roomPermissions.d.ts +56 -0
  139. package/dist/shared/roomPermissions.js +133 -0
  140. package/dist/shared/sessionSeats.d.ts +29 -0
  141. package/dist/shared/sessionSeats.js +57 -0
  142. package/dist/shared/slashCommands.d.ts +39 -0
  143. package/dist/shared/slashCommands.js +166 -0
  144. package/dist/shared/space.d.ts +33 -0
  145. package/dist/shared/space.js +54 -0
  146. package/dist/shared/spaceFeed.d.ts +78 -0
  147. package/dist/shared/spaceFeed.js +194 -0
  148. package/dist/shared/stringifyUnknown.d.ts +1 -0
  149. package/dist/shared/stringifyUnknown.js +1 -0
  150. package/dist/shared/swarm.d.ts +320 -0
  151. package/dist/shared/swarm.js +328 -0
  152. package/dist/shared/swarmVisual.d.ts +79 -0
  153. package/dist/shared/swarmVisual.js +212 -0
  154. package/dist/shared/terminalFilter.d.ts +13 -0
  155. package/dist/shared/terminalFilter.js +46 -0
  156. package/dist/shared/toolIntegration.d.ts +56 -0
  157. package/dist/shared/toolIntegration.js +180 -0
  158. package/dist/shared/typing.d.ts +9 -0
  159. package/dist/shared/typing.js +37 -0
  160. package/dist/shared/uploads.d.ts +31 -0
  161. package/dist/shared/uploads.js +65 -0
  162. package/dist/shared/voiceApprovals.d.ts +36 -0
  163. package/dist/shared/voiceApprovals.js +229 -0
  164. package/dist/shared/workspace.d.ts +47 -0
  165. package/dist/shared/workspace.js +40 -0
  166. package/dist/worker.d.ts +1 -0
  167. package/dist/worker.js +266 -0
  168. package/package.json +51 -4
@@ -0,0 +1,540 @@
1
+ import { diffFromToolArgs, extractThinkingText, formatToolResultDetail, isEditTool, isQuestionTool, isTodoTool, parseQuestionToolArgs, todoStatusSummary, todosFromToolArgs, TOOL_RESULT_DETAIL_LIMIT, stringifyUnknown, } from "./cursor.js";
2
+ import { claudeToolIntegration } from "../toolIntegration.js";
3
+ import { claudeResultUsage } from "./usage.js";
4
+ function extractText(message) {
5
+ if (!message || typeof message !== "object")
6
+ return "";
7
+ const content = message.content;
8
+ if (typeof content === "string")
9
+ return content;
10
+ if (!Array.isArray(content))
11
+ return "";
12
+ return content
13
+ .map((part) => {
14
+ if (part &&
15
+ typeof part === "object" &&
16
+ part.type === "text" &&
17
+ "text" in part) {
18
+ return String(part.text ?? "");
19
+ }
20
+ return "";
21
+ })
22
+ .join("");
23
+ }
24
+ function asRecord(value) {
25
+ return value && typeof value === "object" && !Array.isArray(value)
26
+ ? value
27
+ : undefined;
28
+ }
29
+ function blockIndex(event) {
30
+ return typeof event.index === "number" && Number.isFinite(event.index)
31
+ ? event.index
32
+ : Number(event.index ?? 0) || 0;
33
+ }
34
+ function tryParseToolArgs(raw) {
35
+ const trimmed = raw.trim();
36
+ if (!trimmed)
37
+ return undefined;
38
+ try {
39
+ return asRecord(JSON.parse(trimmed));
40
+ }
41
+ catch {
42
+ return undefined;
43
+ }
44
+ }
45
+ /** `message.content` on assistant/user rows, or top-level `content` fallback. */
46
+ function messageContent(ev) {
47
+ const message = asRecord(ev.message);
48
+ if (message && message.content !== undefined)
49
+ return message.content;
50
+ return ev.content;
51
+ }
52
+ function toolPathFromArgs(args) {
53
+ if (!args)
54
+ return undefined;
55
+ for (const k of [
56
+ "file_path",
57
+ "filePath",
58
+ "path",
59
+ "target_file",
60
+ "targetFile",
61
+ ]) {
62
+ if (typeof args[k] === "string" && String(args[k]).trim()) {
63
+ return String(args[k]).trim();
64
+ }
65
+ }
66
+ return undefined;
67
+ }
68
+ /** Normalize Claude tool_result `content` (string or content-block array). */
69
+ function extractToolResultContent(content) {
70
+ if (typeof content === "string")
71
+ return content;
72
+ if (!Array.isArray(content)) {
73
+ if (content && typeof content === "object") {
74
+ try {
75
+ return JSON.stringify(content);
76
+ }
77
+ catch {
78
+ return "";
79
+ }
80
+ }
81
+ return "";
82
+ }
83
+ return content
84
+ .map((part) => {
85
+ if (typeof part === "string")
86
+ return part;
87
+ if (!part || typeof part !== "object")
88
+ return "";
89
+ const p = part;
90
+ if (typeof p.text === "string")
91
+ return p.text;
92
+ if (typeof p.content === "string")
93
+ return p.content;
94
+ return "";
95
+ })
96
+ .filter(Boolean)
97
+ .join("\n");
98
+ }
99
+ function toolDetail(name, args, path) {
100
+ if (!args)
101
+ return name;
102
+ const todos = todosFromToolArgs(args);
103
+ if (isTodoTool(name) || todos.length > 0) {
104
+ return todos.length
105
+ ? `${todos.length} todo${todos.length === 1 ? "" : "s"} · ${todoStatusSummary(todos)}`
106
+ : "Updating todos";
107
+ }
108
+ const command = typeof args.command === "string"
109
+ ? args.command
110
+ : typeof args.pattern === "string"
111
+ ? args.pattern
112
+ : typeof args.glob === "string"
113
+ ? args.glob
114
+ : typeof args.glob_pattern === "string"
115
+ ? args.glob_pattern
116
+ : undefined;
117
+ if (command)
118
+ return String(command);
119
+ if (path)
120
+ return path;
121
+ try {
122
+ return JSON.stringify(args).slice(0, 120);
123
+ }
124
+ catch {
125
+ return name;
126
+ }
127
+ }
128
+ /**
129
+ * Claude Code CLI headless backend.
130
+ * Spawns: `claude -p --output-format stream-json --verbose ...`
131
+ *
132
+ * Stateful: create a fresh instance per run so pending tool_use ids
133
+ * do not leak across concurrent agents.
134
+ */
135
+ export class ClaudeCodeBackend {
136
+ kind = "claude-code";
137
+ available = true;
138
+ command = "claude";
139
+ /** tool_use id → metadata, filled on assistant/stream tool_use, consumed on tool_result */
140
+ pendingTools = new Map();
141
+ /** content_block index → accumulating tool_use input JSON */
142
+ streamingBlocks = new Map();
143
+ /**
144
+ * Each assistant message's slice of the run's cumulative text. Messages
145
+ * are joined with a blank line so turns never run together, and a full
146
+ * `assistant` snapshot can correct its own slice without touching others.
147
+ */
148
+ segments = new Map();
149
+ currentMessageId = null;
150
+ anonymousMessages = 0;
151
+ /** Append (or correct) one message's text in the cumulative buffer. */
152
+ setMessageText(ctx, messageId, text, mode) {
153
+ const buf = ctx.assistantBuf;
154
+ const seg = this.segments.get(messageId);
155
+ if (!seg) {
156
+ if (!text)
157
+ return false;
158
+ if (buf.value && !buf.value.endsWith("\n\n")) {
159
+ buf.value += buf.value.endsWith("\n") ? "\n" : "\n\n";
160
+ }
161
+ this.segments.set(messageId, { start: buf.value.length, text });
162
+ buf.value += text;
163
+ return true;
164
+ }
165
+ const next = mode === "append" ? seg.text + text : text;
166
+ if (next === seg.text)
167
+ return false;
168
+ const isLast = seg.start + seg.text.length === buf.value.length;
169
+ if (!isLast)
170
+ return false;
171
+ buf.value = buf.value.slice(0, seg.start) + next;
172
+ seg.text = next;
173
+ return true;
174
+ }
175
+ buildArgs(opts) {
176
+ const args = [
177
+ "-p",
178
+ "--output-format",
179
+ "stream-json",
180
+ "--verbose",
181
+ "--include-partial-messages",
182
+ ];
183
+ if (opts.mode === "plan") {
184
+ // Plan mode: explore + propose, no edits until the room exits plan.
185
+ args.push("--permission-mode", "plan");
186
+ }
187
+ else {
188
+ args.push("--dangerously-skip-permissions");
189
+ }
190
+ if (opts.modelId && opts.modelId !== "auto") {
191
+ args.push("--model", opts.modelId);
192
+ }
193
+ if (opts.sessionId) {
194
+ args.push("--resume", opts.sessionId);
195
+ }
196
+ if (opts.mcpServers && Object.keys(opts.mcpServers).length) {
197
+ args.push("--mcp-config", JSON.stringify({ mcpServers: opts.mcpServers }));
198
+ }
199
+ // `--mcp-config` is variadic: without `--` the CLI swallows the prompt as
200
+ // another config file path (ENAMETOOLONG). `--` also keeps prompts that
201
+ // start with "-" from being read as flags.
202
+ args.push("--", opts.prompt);
203
+ return args;
204
+ }
205
+ parseLine(json, ctx = {
206
+ assistantBuf: { value: "" },
207
+ gotTerminalEvent: { value: false },
208
+ }) {
209
+ if (!json || typeof json !== "object")
210
+ return [];
211
+ const ev = json;
212
+ const type = ev.type;
213
+ const out = [];
214
+ if (type === "system" && ev.subtype === "init" && ev.session_id) {
215
+ out.push({ kind: "session", sessionId: String(ev.session_id) });
216
+ const names = (value) => Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
217
+ const commands = names(ev.slash_commands);
218
+ if (commands.length && !ctx.toolsOnly) {
219
+ out.push({
220
+ kind: "cli_commands",
221
+ catalog: {
222
+ commands,
223
+ terminalOnly: names(ev.terminal_slash_commands),
224
+ skills: names(ev.skills),
225
+ },
226
+ });
227
+ }
228
+ return out;
229
+ }
230
+ // Heartbeat while a long tool runs — use it to ensure a card exists even
231
+ // when the original tool_use line was dropped by Blaxel streamLogs.
232
+ if (type === "tool_progress") {
233
+ const callId = (typeof ev.parent_tool_use_id === "string" && ev.parent_tool_use_id) ||
234
+ (typeof ev.tool_use_id === "string" &&
235
+ !String(ev.tool_use_id).includes("-heartbeat-")
236
+ ? String(ev.tool_use_id)
237
+ : "") ||
238
+ "";
239
+ const name = typeof ev.tool_name === "string" && ev.tool_name
240
+ ? ev.tool_name
241
+ : "tool";
242
+ if (callId)
243
+ this.startTool(callId, name, undefined, out);
244
+ return out;
245
+ }
246
+ // Token-level streaming (requires --include-partial-messages).
247
+ // Tool calls start as content_block_start { type: tool_use, input: {} }
248
+ // with args arriving later as input_json_delta — must not return after
249
+ // text_delta only or the room UI never gets tool_start cards.
250
+ if (type === "stream_event") {
251
+ const event = asRecord(ev.event);
252
+ if (!event)
253
+ return out;
254
+ if (event.type === "message_start") {
255
+ const message = asRecord(event.message);
256
+ this.currentMessageId =
257
+ (typeof message?.id === "string" && message.id) ||
258
+ `anon-${(this.anonymousMessages += 1)}`;
259
+ return out;
260
+ }
261
+ this.parseContentBlockEvent(event, ctx, out);
262
+ return out;
263
+ }
264
+ if (type === "content_block_start" ||
265
+ type === "content_block_delta" ||
266
+ type === "content_block_stop") {
267
+ this.parseContentBlockEvent(ev, ctx, out);
268
+ return out;
269
+ }
270
+ if (type === "assistant") {
271
+ const message = ev.message;
272
+ if (!ctx.toolsOnly) {
273
+ const thinking = extractThinkingText(message);
274
+ if (thinking)
275
+ this.emitThinking(ctx, thinking, "replace", true, out);
276
+ const text = extractText(message);
277
+ if (text) {
278
+ const record = asRecord(message);
279
+ const id = (typeof record?.id === "string" && record.id) ||
280
+ this.currentMessageId ||
281
+ `anon-${(this.anonymousMessages += 1)}`;
282
+ // The snapshot is authoritative for its own message: it fills in
283
+ // any deltas the stream missed, then closes the bubble.
284
+ this.setMessageText(ctx, id, text, "replace");
285
+ out.push({ kind: "assistant_final", text: ctx.assistantBuf.value });
286
+ }
287
+ }
288
+ const content = messageContent(ev);
289
+ if (Array.isArray(content)) {
290
+ for (const block of content) {
291
+ const b = asRecord(block);
292
+ if (!b || b.type !== "tool_use")
293
+ continue;
294
+ this.startTool(String(b.id ?? ""), String(b.name ?? "tool"), asRecord(b.input), out);
295
+ }
296
+ }
297
+ return out;
298
+ }
299
+ if (type === "user") {
300
+ const content = messageContent(ev);
301
+ if (!Array.isArray(content))
302
+ return out;
303
+ for (const block of content) {
304
+ if (!block || typeof block !== "object")
305
+ continue;
306
+ const b = block;
307
+ if (b.type !== "tool_result")
308
+ continue;
309
+ const callId = String(b.tool_use_id ?? "");
310
+ if (!callId)
311
+ continue;
312
+ const pending = this.pendingTools.get(callId);
313
+ this.pendingTools.delete(callId);
314
+ const name = pending?.name || "tool";
315
+ const path = pending?.path;
316
+ const resultText = extractToolResultContent(b.content);
317
+ const startDetail = toolDetail(name, pending?.args, path);
318
+ const detail = formatToolResultDetail(name, pending?.args, resultText ? { success: { content: resultText } } : undefined, resultText || startDetail || path || "Done");
319
+ const diffPatch = isEditTool(name) && pending?.args
320
+ ? diffFromToolArgs(name, pending.args)
321
+ : undefined;
322
+ const todos = pending?.args ? todosFromToolArgs(pending.args) : [];
323
+ const questions = pending?.args
324
+ ? parseQuestionToolArgs(pending.args)
325
+ : [];
326
+ const integration = claudeToolIntegration(name, pending?.args);
327
+ out.push({
328
+ kind: "tool_done",
329
+ callId,
330
+ name: todos.length && !isTodoTool(name)
331
+ ? "todo"
332
+ : isQuestionTool(name)
333
+ ? "AskUserQuestion"
334
+ : name,
335
+ detail: todos.length
336
+ ? `${todos.length} todo${todos.length === 1 ? "" : "s"} · ${todoStatusSummary(todos)}`
337
+ : detail.slice(0, TOOL_RESULT_DETAIL_LIMIT),
338
+ path,
339
+ diffPatch: diffPatch || undefined,
340
+ todos: todos.length ? todos : undefined,
341
+ questions: questions.length ? questions : undefined,
342
+ ...(integration ? { integration } : {}),
343
+ });
344
+ }
345
+ return out;
346
+ }
347
+ if (type === "error") {
348
+ if (ctx.toolsOnly)
349
+ return out;
350
+ ctx.gotTerminalEvent.value = true;
351
+ out.push({
352
+ kind: "error",
353
+ message: stringifyUnknown(ev.error) ||
354
+ stringifyUnknown(ev.message) ||
355
+ stringifyUnknown(ev.result) ||
356
+ ctx.stderr ||
357
+ "Claude Code error",
358
+ });
359
+ return out;
360
+ }
361
+ if (type === "result") {
362
+ if (ctx.toolsOnly) {
363
+ if (ev.session_id) {
364
+ out.push({ kind: "session", sessionId: String(ev.session_id) });
365
+ }
366
+ return out;
367
+ }
368
+ ctx.gotTerminalEvent.value = true;
369
+ if (ev.session_id) {
370
+ out.push({ kind: "session", sessionId: String(ev.session_id) });
371
+ }
372
+ const subtype = String(ev.subtype ?? "");
373
+ const isError = Boolean(ev.is_error) ||
374
+ subtype.startsWith("error") ||
375
+ subtype === "error_during_execution" ||
376
+ subtype === "error_max_turns";
377
+ if (isError) {
378
+ const msg = stringifyUnknown(ev.result) ||
379
+ stringifyUnknown(ev.errors) ||
380
+ stringifyUnknown(ev.error) ||
381
+ ctx.stderr ||
382
+ `Claude Code error (${subtype || "unknown"})`;
383
+ out.push({ kind: "error", message: msg });
384
+ }
385
+ else {
386
+ out.push({
387
+ kind: "done",
388
+ result: stringifyUnknown(ev.result) ||
389
+ ctx.assistantBuf.value ||
390
+ "",
391
+ usage: claudeResultUsage(ev),
392
+ });
393
+ }
394
+ }
395
+ return out;
396
+ }
397
+ parseContentBlockEvent(event, ctx, out) {
398
+ const eventType = String(event.type ?? "");
399
+ const index = blockIndex(event);
400
+ if (eventType === "content_block_start") {
401
+ const block = asRecord(event.content_block);
402
+ if (!block)
403
+ return;
404
+ if (block.type === "thinking" || block.type === "reasoning") {
405
+ ctx.thinkingBuf = { value: "" };
406
+ const seed = extractThinkingText(block);
407
+ if (seed && !ctx.toolsOnly)
408
+ this.emitThinking(ctx, seed, "append", false, out);
409
+ return;
410
+ }
411
+ if (block.type !== "tool_use")
412
+ return;
413
+ const callId = String(block.id ?? "");
414
+ if (!callId)
415
+ return;
416
+ const name = String(block.name ?? "tool");
417
+ const args = asRecord(block.input);
418
+ this.streamingBlocks.set(index, {
419
+ callId,
420
+ name,
421
+ inputJson: args && Object.keys(args).length > 0 ? JSON.stringify(args) : "",
422
+ });
423
+ this.startTool(callId, name, args, out);
424
+ return;
425
+ }
426
+ if (eventType === "content_block_delta") {
427
+ const delta = asRecord(event.delta);
428
+ if (!delta)
429
+ return;
430
+ if (delta.type === "text_delta" && typeof delta.text === "string") {
431
+ if (ctx.toolsOnly)
432
+ return;
433
+ this.currentMessageId ??= `anon-${(this.anonymousMessages += 1)}`;
434
+ if (this.setMessageText(ctx, this.currentMessageId, delta.text, "append")) {
435
+ out.push({
436
+ kind: "assistant_delta",
437
+ text: ctx.assistantBuf.value,
438
+ });
439
+ }
440
+ return;
441
+ }
442
+ if ((delta.type === "thinking_delta" || delta.type === "reasoning_delta") &&
443
+ !ctx.toolsOnly) {
444
+ const chunk = typeof delta.thinking === "string"
445
+ ? delta.thinking
446
+ : typeof delta.text === "string"
447
+ ? delta.text
448
+ : extractThinkingText(delta);
449
+ if (chunk)
450
+ this.emitThinking(ctx, chunk, "append", false, out);
451
+ return;
452
+ }
453
+ if (delta.type === "input_json_delta" &&
454
+ typeof delta.partial_json === "string") {
455
+ const streaming = this.streamingBlocks.get(index);
456
+ if (!streaming)
457
+ return;
458
+ streaming.inputJson += delta.partial_json;
459
+ const args = tryParseToolArgs(streaming.inputJson);
460
+ if (args) {
461
+ this.startTool(streaming.callId, streaming.name, args, out);
462
+ }
463
+ }
464
+ return;
465
+ }
466
+ if (eventType === "content_block_stop") {
467
+ const streaming = this.streamingBlocks.get(index);
468
+ if (!streaming)
469
+ return;
470
+ this.streamingBlocks.delete(index);
471
+ const args = tryParseToolArgs(streaming.inputJson);
472
+ if (args) {
473
+ this.startTool(streaming.callId, streaming.name, args, out);
474
+ }
475
+ }
476
+ }
477
+ emitThinking(ctx, incoming, mode, final, out) {
478
+ if (!incoming)
479
+ return;
480
+ ctx.thinkingBuf ??= { value: "" };
481
+ const next = mode === "replace"
482
+ ? incoming
483
+ : ctx.thinkingBuf.value + incoming;
484
+ if (next === ctx.thinkingBuf.value)
485
+ return;
486
+ ctx.thinkingBuf.value = next;
487
+ out.push({
488
+ kind: final ? "thinking_final" : "thinking_delta",
489
+ text: next,
490
+ });
491
+ }
492
+ /**
493
+ * Record a tool_use and emit tool_start once. Re-emits when args go from
494
+ * empty `{}` (content_block_start placeholder) to a real path/command so
495
+ * the existing chat row can pick up a useful title.
496
+ *
497
+ * Nested Task/subagent tools (parent_tool_use_id set) still emit as
498
+ * `tool_start` / later `tool_done` so every Claude Code tool appears as a
499
+ * room tool card.
500
+ */
501
+ startTool(callId, name, args, out) {
502
+ if (!callId)
503
+ return;
504
+ const path = toolPathFromArgs(args);
505
+ const existing = this.pendingTools.get(callId);
506
+ const hasArgs = Boolean(args && Object.keys(args).length > 0);
507
+ const hadArgs = Boolean(existing?.args && Object.keys(existing.args).length > 0);
508
+ const mergedArgs = hasArgs ? args : existing?.args;
509
+ const mergedPath = path || existing?.path;
510
+ const mergedName = existing?.name || name;
511
+ this.pendingTools.set(callId, {
512
+ name: mergedName,
513
+ path: mergedPath,
514
+ args: mergedArgs,
515
+ });
516
+ const alreadyStarted = Boolean(existing);
517
+ const detailImproved = alreadyStarted && hasArgs && !hadArgs;
518
+ if (alreadyStarted && !detailImproved)
519
+ return;
520
+ const todos = mergedArgs ? todosFromToolArgs(mergedArgs) : [];
521
+ const questions = mergedArgs ? parseQuestionToolArgs(mergedArgs) : [];
522
+ const integration = claudeToolIntegration(mergedName, mergedArgs);
523
+ // Always surface as a first-class tool card (including nested Task tools).
524
+ out.push({
525
+ kind: "tool_start",
526
+ callId,
527
+ name: todos.length && !isTodoTool(mergedName)
528
+ ? "todo"
529
+ : isQuestionTool(mergedName)
530
+ ? "AskUserQuestion"
531
+ : mergedName,
532
+ detail: toolDetail(mergedName, mergedArgs, mergedPath),
533
+ path: mergedPath,
534
+ todos: todos.length ? todos : undefined,
535
+ questions: questions.length ? questions : undefined,
536
+ ...(integration ? { integration } : {}),
537
+ });
538
+ }
539
+ }
540
+ export const claudeCodeBackend = new ClaudeCodeBackend();
@@ -0,0 +1,15 @@
1
+ import type { BuildArgsOptions, NormalizedAgentEvent, ParseLineContext, WorkerBackend } from "./types.js";
2
+ /**
3
+ * OpenAI Codex CLI headless backend.
4
+ * Spawns: `codex exec --json --dangerously-bypass-approvals-and-sandbox …`
5
+ *
6
+ * Event stream is JSONL (`thread.*`, `item.*`, `turn.*`).
7
+ */
8
+ export declare class CodexBackend implements WorkerBackend {
9
+ readonly kind: "codex";
10
+ readonly available = true;
11
+ readonly command = "codex";
12
+ buildArgs(opts: BuildArgsOptions): string[];
13
+ parseLine(json: unknown, ctx?: ParseLineContext): NormalizedAgentEvent[];
14
+ }
15
+ export declare const codexBackend: CodexBackend;