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