@adecore/agents 0.39.0 → 0.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/dist/async.d.ts +5 -0
  2. package/dist/async.js +13 -5
  3. package/dist/chat/acp-protocol.js +3 -5
  4. package/dist/chat/chat-core.js +0 -2
  5. package/dist/chat/chat-process.d.ts +1 -0
  6. package/dist/chat/chat-process.js +1 -1
  7. package/dist/chat/chat-session.js +6 -8
  8. package/dist/chat/claude-protocol.js +1 -6
  9. package/dist/chat/claude-transcript.js +1 -3
  10. package/dist/chat/codex-protocol.js +1 -6
  11. package/dist/chat/projector.js +1 -3
  12. package/dist/chat/request-summary.js +1 -3
  13. package/dist/coded-error.js +1 -5
  14. package/dist/context/argv.js +1 -2
  15. package/dist/context/verb.js +1 -3
  16. package/dist/error-text.d.ts +1 -0
  17. package/dist/error-text.js +4 -0
  18. package/dist/fields.d.ts +3 -0
  19. package/dist/fields.js +12 -0
  20. package/dist/fs.d.ts +1 -0
  21. package/dist/fs.js +8 -4
  22. package/dist/lineage.js +11 -5
  23. package/dist/messages/notice-store.js +3 -4
  24. package/dist/providers/accounts/acp-account.js +3 -20
  25. package/dist/providers/accounts/service.d.ts +1 -2
  26. package/dist/providers/accounts/service.js +5 -9
  27. package/dist/providers/accounts/status.js +1 -3
  28. package/dist/providers/acp-agent-state.js +1 -3
  29. package/dist/providers/claude.js +0 -1
  30. package/dist/providers/codex-models.json +3 -2
  31. package/dist/providers/codex.js +0 -1
  32. package/dist/providers/detect.js +1 -1
  33. package/dist/providers/shell-commands.js +1 -2
  34. package/dist/run-process.js +1 -3
  35. package/dist/skills.d.ts +1 -1
  36. package/dist/skills.js +5 -8
  37. package/dist/tasks/background-limit.js +3 -2
  38. package/dist/tasks/task-coordinator.js +11 -17
  39. package/dist/testing/watch.js +0 -1
  40. package/dist/usage/limits/monitor.js +1 -2
  41. package/dist/usage/prices-snapshot.json +8 -0
  42. package/dist/usage/readers/claude.js +3 -10
  43. package/dist/usage/readers/codex.js +2 -9
  44. package/dist/usage/readers/grok.js +3 -10
  45. package/dist/usage/readers/host-log.js +3 -10
  46. package/dist/usage/record.d.ts +2 -0
  47. package/dist/usage/record.js +13 -0
  48. package/package.json +6 -6
  49. package/src/async.ts +23 -5
  50. package/src/chat/acp-backend.ts +121 -0
  51. package/src/chat/acp-protocol.ts +172 -6
  52. package/src/chat/attachment-store.ts +21 -0
  53. package/src/chat/backend.ts +0 -2
  54. package/src/chat/bookmark-store.ts +23 -0
  55. package/src/chat/chat-core.ts +208 -7
  56. package/src/chat/chat-log.ts +19 -0
  57. package/src/chat/chat-process.ts +52 -1
  58. package/src/chat/chat-session.ts +401 -10
  59. package/src/chat/chat-store.ts +50 -0
  60. package/src/chat/chat-title.ts +9 -0
  61. package/src/chat/claude-backend.ts +50 -0
  62. package/src/chat/claude-protocol.ts +155 -8
  63. package/src/chat/claude-title.ts +10 -0
  64. package/src/chat/claude-transcript.ts +47 -4
  65. package/src/chat/codex-backend.ts +131 -0
  66. package/src/chat/codex-protocol.ts +180 -8
  67. package/src/chat/codex-thread.ts +23 -0
  68. package/src/chat/composer-preferences.ts +6 -0
  69. package/src/chat/context-breakdown.ts +11 -0
  70. package/src/chat/delta-coalescer.ts +8 -0
  71. package/src/chat/external-sessions.ts +28 -0
  72. package/src/chat/generated-image.ts +25 -0
  73. package/src/chat/index.ts +5 -0
  74. package/src/chat/input.ts +13 -0
  75. package/src/chat/json-rpc-transport.ts +28 -0
  76. package/src/chat/limit-resume.ts +13 -0
  77. package/src/chat/projector.ts +160 -4
  78. package/src/chat/readable-text.ts +3 -0
  79. package/src/chat/request-summary.ts +18 -4
  80. package/src/chat/subagent-projection.ts +3 -0
  81. package/src/chat/subagent-reader.ts +76 -0
  82. package/src/chat/subagent-settlement.ts +24 -0
  83. package/src/chat/thread-search.ts +13 -0
  84. package/src/chat/thread.ts +31 -0
  85. package/src/chat/ui-queries.ts +122 -0
  86. package/src/chat/ui-replies.ts +34 -0
  87. package/src/chat/visual-store.ts +49 -0
  88. package/src/chat/wake-chat.ts +3 -0
  89. package/src/client-sinks.ts +2 -0
  90. package/src/coded-error.ts +1 -5
  91. package/src/context/argv.ts +18 -2
  92. package/src/context/index.ts +1 -0
  93. package/src/context/refusal.ts +3 -0
  94. package/src/context/verb.ts +32 -4
  95. package/src/coordination/index.ts +10 -0
  96. package/src/error-text.ts +7 -0
  97. package/src/fields.ts +14 -0
  98. package/src/fs.ts +20 -4
  99. package/src/host/agent-host.ts +20 -0
  100. package/src/host/environment.ts +6 -0
  101. package/src/host/handlers.ts +18 -0
  102. package/src/host/memory-port.ts +4 -0
  103. package/src/host/wiring.ts +10 -0
  104. package/src/index.ts +1 -0
  105. package/src/lineage.ts +24 -5
  106. package/src/messages/deliver-message.ts +6 -0
  107. package/src/messages/deliver-notice.ts +13 -0
  108. package/src/messages/notice-store.ts +16 -4
  109. package/src/modes.ts +1 -0
  110. package/src/outbox/outbox-worker.ts +31 -0
  111. package/src/outbox/outbox.ts +5 -0
  112. package/src/providers/accounts/accounts.ts +36 -0
  113. package/src/providers/accounts/acp-account.ts +22 -27
  114. package/src/providers/accounts/launch.ts +4 -0
  115. package/src/providers/accounts/service.ts +110 -12
  116. package/src/providers/accounts/shadow-home.ts +10 -0
  117. package/src/providers/accounts/status.ts +13 -4
  118. package/src/providers/accounts/store.ts +25 -0
  119. package/src/providers/accounts/variables.ts +11 -1
  120. package/src/providers/acp-agent-state.ts +24 -3
  121. package/src/providers/acp-provider.ts +5 -0
  122. package/src/providers/acp-usage-log.ts +2 -0
  123. package/src/providers/catalog.ts +17 -0
  124. package/src/providers/claude.ts +10 -1
  125. package/src/providers/codex-models.json +3 -2
  126. package/src/providers/codex-rules.ts +3 -0
  127. package/src/providers/codex.ts +2 -1
  128. package/src/providers/detect.ts +6 -1
  129. package/src/providers/provider.ts +1 -1
  130. package/src/providers/registry.ts +18 -2
  131. package/src/providers/shell-commands.ts +37 -2
  132. package/src/record-directory.ts +19 -0
  133. package/src/run-process.ts +24 -3
  134. package/src/serializer.ts +3 -0
  135. package/src/skills.ts +73 -10
  136. package/src/tasks/background-limit.ts +9 -2
  137. package/src/tasks/end-children.ts +9 -0
  138. package/src/tasks/give-task.ts +8 -0
  139. package/src/tasks/task-coordinator.ts +81 -17
  140. package/src/tasks/task-store.ts +28 -0
  141. package/src/tasks/waiting-child.ts +21 -0
  142. package/src/tasks/wake-parent.ts +26 -0
  143. package/src/tasks/wiring.ts +22 -0
  144. package/src/testing/accounts.ts +2 -0
  145. package/src/testing/fake-acp-capture.ts +16 -0
  146. package/src/testing/fake-acp.ts +66 -0
  147. package/src/testing/fake-claude.ts +177 -0
  148. package/src/testing/fake-cli.ts +29 -0
  149. package/src/testing/fake-codex.ts +122 -0
  150. package/src/testing/manual-clock.ts +2 -0
  151. package/src/testing/watch.ts +7 -1
  152. package/src/title-file.ts +12 -0
  153. package/src/usage/accounts.ts +6 -0
  154. package/src/usage/aggregate.ts +19 -0
  155. package/src/usage/exchange.ts +12 -0
  156. package/src/usage/index-file.ts +27 -0
  157. package/src/usage/limits/monitor.ts +31 -3
  158. package/src/usage/limits/normalize.ts +30 -0
  159. package/src/usage/limits/probe.ts +17 -0
  160. package/src/usage/prices-snapshot.json +8 -0
  161. package/src/usage/pricing.ts +35 -0
  162. package/src/usage/projects.ts +8 -0
  163. package/src/usage/readers/claude.ts +12 -9
  164. package/src/usage/readers/codex.ts +40 -8
  165. package/src/usage/readers/grok.ts +17 -9
  166. package/src/usage/readers/host-log.ts +9 -9
  167. package/src/usage/record.ts +22 -0
  168. package/src/usage/roots.ts +6 -0
  169. package/src/usage/scanner.ts +61 -0
  170. package/src/usage/usage-service.ts +14 -0
  171. package/src/watch-seam.ts +8 -0
  172. package/src/windows-command.ts +12 -0
@@ -23,29 +23,35 @@ export function fakeClaude(io: FakeIo): FakeProgram {
23
23
  const assistantText = (text: string): void => {
24
24
  const id = `msg_${nonce}_${++messageCounter}`;
25
25
  out({ type: 'stream_event', event: { type: 'message_start', message: { id, model } }, session_id: sessionId });
26
+
26
27
  out({
27
28
  type: 'stream_event',
28
29
  event: { type: 'content_block_start', index: 0, content_block: { type: 'thinking', thinking: '' } },
29
30
  session_id: sessionId
30
31
  });
32
+
31
33
  out({
32
34
  type: 'stream_event',
33
35
  event: { type: 'content_block_delta', index: 0, delta: { type: 'thinking_delta', thinking: 'weighing it' } },
34
36
  session_id: sessionId
35
37
  });
38
+
36
39
  out({ type: 'stream_event', event: { type: 'content_block_stop', index: 0 }, session_id: sessionId });
37
40
  out({ type: 'stream_event', event: { type: 'content_block_start', index: 1, content_block: { type: 'text', text: '' } }, session_id: sessionId });
38
41
  const half = Math.ceil(text.length / 2);
42
+
39
43
  out({
40
44
  type: 'stream_event',
41
45
  event: { type: 'content_block_delta', index: 1, delta: { type: 'text_delta', text: text.slice(0, half) } },
42
46
  session_id: sessionId
43
47
  });
48
+
44
49
  out({
45
50
  type: 'stream_event',
46
51
  event: { type: 'content_block_delta', index: 1, delta: { type: 'text_delta', text: text.slice(half) } },
47
52
  session_id: sessionId
48
53
  });
54
+
49
55
  // Every frame carries the uuid of its line in the transcript, which is what a fork is cut at.
50
56
  out({
51
57
  type: 'assistant',
@@ -53,12 +59,14 @@ export function fakeClaude(io: FakeIo): FakeProgram {
53
59
  message: { id, model, role: 'assistant', content: [{ type: 'thinking', thinking: '' }], usage },
54
60
  session_id: sessionId
55
61
  });
62
+
56
63
  out({
57
64
  type: 'assistant',
58
65
  uuid: `${id}-text`,
59
66
  message: { id, model, role: 'assistant', content: [{ type: 'text', text }], usage },
60
67
  session_id: sessionId
61
68
  });
69
+
62
70
  out({ type: 'stream_event', event: { type: 'message_stop' }, session_id: sessionId });
63
71
  };
64
72
 
@@ -79,6 +87,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
79
87
  // A request the API turned away: a synthetic message with the reason, then a result that failed without saying which error.
80
88
  const refusal = (text: string, error: string, status: number): void => {
81
89
  const id = `msg_${nonce}_${++messageCounter}`;
90
+
82
91
  out({
83
92
  type: 'assistant',
84
93
  uuid: `${id}-text`,
@@ -87,6 +96,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
87
96
  error,
88
97
  session_id: sessionId
89
98
  });
99
+
90
100
  out({
91
101
  type: 'result',
92
102
  subtype: 'success',
@@ -111,6 +121,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
111
121
  session_id: sessionId
112
122
  });
113
123
  };
124
+
114
125
  const childResult = (toolUseId: string, content: string): void => {
115
126
  out({
116
127
  type: 'user',
@@ -119,10 +130,12 @@ export function fakeClaude(io: FakeIo): FakeProgram {
119
130
  session_id: sessionId
120
131
  });
121
132
  };
133
+
122
134
  child([{ type: 'tool_use', id: `${agentToolUseId}_read`, name: 'Read', input: { file_path: 'README.md' } }]);
123
135
  childResult(`${agentToolUseId}_read`, '# Notes');
124
136
  child([{ type: 'tool_use', id: `${agentToolUseId}_bash`, name: 'Bash', input: { command: 'ls', description: 'List files' } }]);
125
137
  childResult(`${agentToolUseId}_bash`, 'README.md');
138
+
126
139
  out({
127
140
  type: 'system',
128
141
  subtype: 'task_progress',
@@ -134,6 +147,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
134
147
  last_tool_name: 'Bash',
135
148
  session_id: sessionId
136
149
  });
150
+
137
151
  child([{ type: 'text', text: report }]);
138
152
  };
139
153
 
@@ -151,11 +165,14 @@ export function fakeClaude(io: FakeIo): FakeProgram {
151
165
 
152
166
  const settleTwo = (name: 'a-one' | 'a-two', stopped: boolean): void => {
153
167
  const toolUseId = name === 'a-one' ? 'toolu_two_a' : 'toolu_two_b';
168
+
154
169
  if (!openTwo.delete(name)) {
155
170
  return;
156
171
  }
172
+
157
173
  if (stopped) {
158
174
  out({ type: 'system', subtype: 'task_updated', task_id: name, patch: { status: 'killed', end_time: 0 }, session_id: sessionId });
175
+
159
176
  out({
160
177
  type: 'system',
161
178
  subtype: 'task_notification',
@@ -167,6 +184,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
167
184
  session_id: sessionId
168
185
  });
169
186
  }
187
+
170
188
  out({
171
189
  type: 'user',
172
190
  message: {
@@ -175,6 +193,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
175
193
  },
176
194
  session_id: sessionId
177
195
  });
196
+
178
197
  if (openTwo.size === 0) {
179
198
  assistantText('summarized B');
180
199
  result();
@@ -184,6 +203,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
184
203
  // The rest of a background agent once its request was answered: its call's result, its report, and the turn the CLI opens about it.
185
204
  const finishBackgroundAgent = (command: string, allowed: boolean, message: string | undefined): void => {
186
205
  const toolUseId = 'toolu_bgask';
206
+
187
207
  out({
188
208
  type: 'user',
189
209
  message: {
@@ -200,13 +220,16 @@ export function fakeClaude(io: FakeIo): FakeProgram {
200
220
  parent_tool_use_id: toolUseId,
201
221
  session_id: sessionId
202
222
  });
223
+
203
224
  const report = allowed ? `the agent ran ${command}` : 'the agent could not run it';
225
+
204
226
  out({
205
227
  type: 'assistant',
206
228
  message: { id: `msg_${nonce}_${++messageCounter}`, model, role: 'assistant', content: [{ type: 'text', text: report }], usage },
207
229
  parent_tool_use_id: toolUseId,
208
230
  session_id: sessionId
209
231
  });
232
+
210
233
  out({
211
234
  type: 'system',
212
235
  subtype: 'task_notification',
@@ -217,6 +240,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
217
240
  summary: report,
218
241
  session_id: sessionId
219
242
  });
243
+
220
244
  out({ type: 'system', subtype: 'init', session_id: sessionId, model, cwd: io.cwd, tools: ['Bash'], slash_commands: [], argv: args });
221
245
  assistantText(report);
222
246
  result();
@@ -225,11 +249,14 @@ export function fakeClaude(io: FakeIo): FakeProgram {
225
249
  const handleUser = (text: string): void => {
226
250
  if (text === 'crash') {
227
251
  io.exit(1);
252
+
228
253
  return;
229
254
  }
255
+
230
256
  // Two foreground subagents beside each other; the turn ends once both settled: a-two when the test runs what is put off, a-one by a `stop_task`.
231
257
  if (text === 'two subagents') {
232
258
  openTwo.add('a-one').add('a-two');
259
+
233
260
  for (const [name, toolUseId] of [
234
261
  ['a-one', 'toolu_two_a'],
235
262
  ['a-two', 'toolu_two_b']
@@ -247,6 +274,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
247
274
  },
248
275
  session_id: sessionId
249
276
  });
277
+
250
278
  out({
251
279
  type: 'system',
252
280
  subtype: 'task_started',
@@ -261,21 +289,28 @@ export function fakeClaude(io: FakeIo): FakeProgram {
261
289
  session_id: sessionId
262
290
  });
263
291
  }
292
+
264
293
  io.later(() => settleTwo('a-two', false));
294
+
265
295
  return;
266
296
  }
297
+
267
298
  if (text === 'crash loudly') {
268
299
  io.err(`${'warming up\n'.repeat(2000)}Error: the fake lost its key\n at handleUser (fake-claude.ts)\n`);
269
300
  io.exit(1);
301
+
270
302
  return;
271
303
  }
304
+
272
305
  // Over stdio only: a process of the CLI's own, as a dev server an agent started would be.
273
306
  if (text === 'start a grandchild') {
274
307
  const grandchild = spawn('sleep', ['600'], { stdio: 'ignore' });
275
308
  assistantText(`grandchild ${grandchild.pid}`);
276
309
  result();
310
+
277
311
  return;
278
312
  }
313
+
279
314
  /*
280
315
  * A background agent that asks to run a command while the turn that launched it ends, framed the way Claude
281
316
  * Code 2.1.282 sends it: the request names the agent's own call and its `agent_id`, the result of the turn
@@ -287,6 +322,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
287
322
  const [first = '', ...rest] = text.split('\n');
288
323
  const command = first.slice(20).trim() || 'ls';
289
324
  const toolUseId = 'toolu_bgask';
325
+
290
326
  out({
291
327
  type: 'assistant',
292
328
  message: {
@@ -300,6 +336,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
300
336
  },
301
337
  session_id: sessionId
302
338
  });
339
+
303
340
  out({
304
341
  type: 'system',
305
342
  subtype: 'task_started',
@@ -311,6 +348,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
311
348
  task_type: 'local_agent',
312
349
  session_id: sessionId
313
350
  });
351
+
314
352
  out({
315
353
  type: 'user',
316
354
  message: {
@@ -319,6 +357,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
319
357
  },
320
358
  session_id: sessionId
321
359
  });
360
+
322
361
  const ask = (): void => {
323
362
  out({
324
363
  type: 'assistant',
@@ -332,7 +371,9 @@ export function fakeClaude(io: FakeIo): FakeProgram {
332
371
  parent_tool_use_id: toolUseId,
333
372
  session_id: sessionId
334
373
  });
374
+
335
375
  backgroundAsk = { requestId: 'req-bg', command };
376
+
336
377
  out({
337
378
  type: 'control_request',
338
379
  request_id: 'req-bg',
@@ -347,45 +388,62 @@ export function fakeClaude(io: FakeIo): FakeProgram {
347
388
  }
348
389
  });
349
390
  };
391
+
350
392
  if (rest.includes('late')) {
351
393
  assistantText('the agent runs');
352
394
  result();
353
395
  io.later(ask);
396
+
354
397
  return;
355
398
  }
399
+
356
400
  ask();
401
+
357
402
  if (rest.includes('slow')) {
358
403
  slow = true;
404
+
359
405
  return;
360
406
  }
407
+
361
408
  assistantText('the agent runs');
362
409
  result();
410
+
363
411
  return;
364
412
  }
413
+
365
414
  // A line of its own, so a prompt with a task brief under it or a message above it still waits.
366
415
  if (text.split('\n').some((line) => line === 'slow')) {
367
416
  slow = true;
368
417
  out({ type: 'stream_event', event: { type: 'message_start', message: { id: `msg_${nonce}_${++messageCounter}`, model } }, session_id: sessionId });
418
+
369
419
  return;
370
420
  }
421
+
371
422
  // Lines of their own, as the two ways Claude Code 2.1.281 ends a turn it could not get an answer for.
372
423
  const limit = text.split('\n').find((line) => line.startsWith('limit:'));
424
+
373
425
  if (limit !== undefined) {
374
426
  const resetsAt = Number(limit.slice(6));
427
+
375
428
  out({
376
429
  type: 'rate_limit_event',
377
430
  rate_limit_info: { status: 'rejected', resetsAt, rateLimitType: 'five_hour', utilization: 1 },
378
431
  uuid: `rate-${nonce}`,
379
432
  session_id: sessionId
380
433
  });
434
+
381
435
  refusal("You've hit your session limit · resets 3pm", 'rate_limit', 429);
436
+
382
437
  return;
383
438
  }
439
+
384
440
  // A line of its own as well: a tool call whose result is that many bytes, the way a long log or a big file reads back.
385
441
  const output = text.split('\n').find((line) => line.startsWith('output:'));
442
+
386
443
  if (output !== undefined) {
387
444
  const bytes = Number(output.slice(7));
388
445
  const toolUseId = `toolu_${nonce}_${++messageCounter}`;
446
+
389
447
  out({
390
448
  type: 'assistant',
391
449
  message: {
@@ -397,7 +455,9 @@ export function fakeClaude(io: FakeIo): FakeProgram {
397
455
  },
398
456
  session_id: sessionId
399
457
  });
458
+
400
459
  const line = 'build step finished without warnings\n';
460
+
401
461
  out({
402
462
  type: 'user',
403
463
  message: {
@@ -408,23 +468,31 @@ export function fakeClaude(io: FakeIo): FakeProgram {
408
468
  },
409
469
  session_id: sessionId
410
470
  });
471
+
411
472
  assistantText(`read ${bytes} bytes of build log`);
412
473
  result();
474
+
413
475
  return;
414
476
  }
477
+
415
478
  if (text.split('\n').includes('overloaded')) {
416
479
  refusal('API Error: Repeated 529 Overloaded errors', 'overloaded', 529);
480
+
417
481
  return;
418
482
  }
483
+
419
484
  if (text === 'compact') {
420
485
  out({ type: 'system', subtype: 'compact_boundary', compact_metadata: { trigger: 'manual', pre_tokens: 5000 }, session_id: sessionId });
421
486
  assistantText('compacted');
422
487
  result();
488
+
423
489
  return;
424
490
  }
491
+
425
492
  if (text.startsWith('ask:')) {
426
493
  const question = text.slice(4).trim();
427
494
  pendingQuestion = question;
495
+
428
496
  out({
429
497
  type: 'control_request',
430
498
  request_id: 'req-q',
@@ -447,14 +515,17 @@ export function fakeClaude(io: FakeIo): FakeProgram {
447
515
  tool_use_id: 'toolu_q'
448
516
  }
449
517
  });
518
+
450
519
  return;
451
520
  }
521
+
452
522
  if (text.startsWith('write:')) {
453
523
  const [path = '', ...rest] = text.slice(6).trim().split(' ');
454
524
  const content = `${rest.join(' ')}\n`;
455
525
  // Against the CLI's own folder: in a test the fake shares the test runner's working directory.
456
526
  writeFileSync(resolve(io.cwd, path), content);
457
527
  const id = `msg_${nonce}_${++messageCounter}`;
528
+
458
529
  out({
459
530
  type: 'assistant',
460
531
  message: {
@@ -466,20 +537,26 @@ export function fakeClaude(io: FakeIo): FakeProgram {
466
537
  },
467
538
  session_id: sessionId
468
539
  });
540
+
469
541
  out({
470
542
  type: 'user',
471
543
  message: { role: 'user', content: [{ type: 'tool_result', tool_use_id: 'toolu_write', content: `wrote ${path}`, is_error: false }] },
472
544
  session_id: sessionId
473
545
  });
546
+
474
547
  assistantText('written');
475
548
  result();
549
+
476
550
  return;
477
551
  }
552
+
478
553
  // `delegate late:` sends the subagent's first work before `task_started`, an order the CLI does not promise to avoid.
479
554
  const late = text.startsWith('delegate late:');
555
+
480
556
  if (late || text.startsWith('delegate:')) {
481
557
  const description = text.slice(text.indexOf(':') + 1).trim();
482
558
  const report = '# Report\n\n- one\n- two';
559
+
483
560
  out({
484
561
  type: 'assistant',
485
562
  message: {
@@ -498,6 +575,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
498
575
  },
499
576
  session_id: sessionId
500
577
  });
578
+
501
579
  const taskStarted = (): void => {
502
580
  out({
503
581
  type: 'system',
@@ -513,13 +591,17 @@ export function fakeClaude(io: FakeIo): FakeProgram {
513
591
  session_id: sessionId
514
592
  });
515
593
  };
594
+
516
595
  if (!late) {
517
596
  taskStarted();
518
597
  }
598
+
519
599
  subagentWork('toolu_delegate', report);
600
+
520
601
  if (late) {
521
602
  taskStarted();
522
603
  }
604
+
523
605
  out({
524
606
  type: 'user',
525
607
  message: {
@@ -528,10 +610,13 @@ export function fakeClaude(io: FakeIo): FakeProgram {
528
610
  },
529
611
  session_id: sessionId
530
612
  });
613
+
531
614
  assistantText('summarized');
532
615
  result();
616
+
533
617
  return;
534
618
  }
619
+
535
620
  /*
536
621
  * A background agent that opens a foreground one of its own, in the order Claude Code 2.1.282 streams it:
537
622
  * the grandchild's call and result come as frames of its parent, its own steps not at all, and its
@@ -539,12 +624,15 @@ export function fakeClaude(io: FakeIo): FakeProgram {
539
624
  */
540
625
  if (text.startsWith('nested:')) {
541
626
  const summary = text.slice(7).trim() || 'done';
627
+
542
628
  const task = (frame: Record<string, unknown>): void => {
543
629
  out({ type: 'system', session_id: sessionId, ...frame });
544
630
  };
631
+
545
632
  const underMiddle = (message: Record<string, unknown>, type: 'assistant' | 'user'): void => {
546
633
  out({ type, message, parent_tool_use_id: 'toolu_middle', session_id: sessionId });
547
634
  };
635
+
548
636
  out({
549
637
  type: 'assistant',
550
638
  message: {
@@ -563,6 +651,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
563
651
  },
564
652
  session_id: sessionId
565
653
  });
654
+
566
655
  task({
567
656
  subtype: 'task_started',
568
657
  task_id: 'a-middle',
@@ -574,6 +663,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
574
663
  task_type: 'local_agent',
575
664
  prompt: summary
576
665
  });
666
+
577
667
  out({
578
668
  type: 'user',
579
669
  message: {
@@ -582,8 +672,10 @@ export function fakeClaude(io: FakeIo): FakeProgram {
582
672
  },
583
673
  session_id: sessionId
584
674
  });
675
+
585
676
  assistantText('sent');
586
677
  result();
678
+
587
679
  io.later(() => {
588
680
  underMiddle(
589
681
  {
@@ -595,6 +687,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
595
687
  },
596
688
  'assistant'
597
689
  );
690
+
598
691
  task({
599
692
  subtype: 'task_started',
600
693
  task_id: 'a-leaf',
@@ -606,17 +699,22 @@ export function fakeClaude(io: FakeIo): FakeProgram {
606
699
  task_type: 'local_agent',
607
700
  prompt: 'echo PONG'
608
701
  });
702
+
609
703
  task({ subtype: 'task_updated', task_id: 'a-leaf', patch: { status: 'completed', end_time: 0 } });
610
704
  task({ subtype: 'task_notification', task_id: 'a-leaf', tool_use_id: 'toolu_leaf', status: 'completed', output_file: '', summary: 'PONG' });
705
+
611
706
  underMiddle(
612
707
  { role: 'user', content: [{ type: 'tool_result', tool_use_id: 'toolu_leaf', content: `PONG${agentFooter('a-leaf')}`, is_error: false }] },
613
708
  'user'
614
709
  );
710
+
615
711
  underMiddle(
616
712
  { id: `msg_${nonce}_${++messageCounter}`, model, role: 'assistant', content: [{ type: 'text', text: `the leaf said PONG` }], usage },
617
713
  'assistant'
618
714
  );
715
+
619
716
  task({ subtype: 'task_updated', task_id: 'a-middle', patch: { status: 'completed', end_time: 0 } });
717
+
620
718
  task({
621
719
  subtype: 'task_notification',
622
720
  task_id: 'a-middle',
@@ -626,15 +724,19 @@ export function fakeClaude(io: FakeIo): FakeProgram {
626
724
  summary,
627
725
  usage: { total_tokens: 1500, tool_uses: 1, duration_ms: 250 }
628
726
  });
727
+
629
728
  out({ type: 'system', subtype: 'init', session_id: sessionId, model, cwd: io.cwd, tools: ['Bash'], slash_commands: [], argv: args });
630
729
  assistantText(`done: ${summary}`);
631
730
  result();
632
731
  });
732
+
633
733
  return;
634
734
  }
735
+
635
736
  if (text.startsWith('background:')) {
636
737
  const summary = text.slice(11).trim() || 'done';
637
738
  const id = `msg_${nonce}_${++messageCounter}`;
739
+
638
740
  out({
639
741
  type: 'assistant',
640
742
  message: {
@@ -648,6 +750,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
648
750
  },
649
751
  session_id: sessionId
650
752
  });
753
+
651
754
  out({
652
755
  type: 'system',
653
756
  subtype: 'task_started',
@@ -659,6 +762,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
659
762
  task_type: 'local_agent',
660
763
  session_id: sessionId
661
764
  });
765
+
662
766
  out({
663
767
  type: 'user',
664
768
  message: {
@@ -667,12 +771,15 @@ export function fakeClaude(io: FakeIo): FakeProgram {
667
771
  },
668
772
  session_id: sessionId
669
773
  });
774
+
670
775
  assistantText('I will report back');
671
776
  result();
777
+
672
778
  // What the real CLI does after the turn ended: the subagent works on, and only later come a
673
779
  // notification, a fresh init, an assistant message of its own and a result, with no user frame.
674
780
  io.later(() => {
675
781
  subagentWork('toolu_agent', `the subagent says: ${summary}`);
782
+
676
783
  io.later(() => {
677
784
  out({
678
785
  type: 'system',
@@ -685,13 +792,16 @@ export function fakeClaude(io: FakeIo): FakeProgram {
685
792
  usage: { total_tokens: 1500, tool_uses: 2, duration_ms: 250 },
686
793
  session_id: sessionId
687
794
  });
795
+
688
796
  out({ type: 'system', subtype: 'init', session_id: sessionId, model, cwd: io.cwd, tools: ['Bash'], slash_commands: [], argv: args });
689
797
  assistantText(`the subagent says: ${summary}`);
690
798
  result();
691
799
  });
692
800
  });
801
+
693
802
  return;
694
803
  }
804
+
695
805
  /*
696
806
  * A background subagent that sends a command to the background and answers at once, the way Claude
697
807
  * Code 2.1.282 runs one: its notification opens a turn with that answer while the command runs on.
@@ -703,6 +813,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
703
813
  const command = text.split('\n')[0]!.slice(19).trim() || 'sleep 30';
704
814
  const agentUse = 'toolu_cmd_agent';
705
815
  const bashUse = 'toolu_cmd_bash';
816
+
706
817
  const subagent = (content: unknown[]): void => {
707
818
  out({
708
819
  type: 'assistant',
@@ -711,6 +822,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
711
822
  session_id: sessionId
712
823
  });
713
824
  };
825
+
714
826
  const subagentResult = (toolUseId: string, content: string): void => {
715
827
  out({
716
828
  type: 'user',
@@ -719,6 +831,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
719
831
  session_id: sessionId
720
832
  });
721
833
  };
834
+
722
835
  const agentNotified = (summary: string): void => {
723
836
  out({
724
837
  type: 'system',
@@ -730,8 +843,10 @@ export function fakeClaude(io: FakeIo): FakeProgram {
730
843
  usage: { total_tokens: 1500, tool_uses: 1, duration_ms: 250 },
731
844
  session_id: sessionId
732
845
  });
846
+
733
847
  out({ type: 'system', subtype: 'init', session_id: sessionId, model, cwd: io.cwd, tools: ['Bash'], slash_commands: [], argv: args });
734
848
  };
849
+
735
850
  out({
736
851
  type: 'assistant',
737
852
  message: {
@@ -743,6 +858,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
743
858
  },
744
859
  session_id: sessionId
745
860
  });
861
+
746
862
  out({
747
863
  type: 'system',
748
864
  subtype: 'task_started',
@@ -754,6 +870,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
754
870
  task_type: 'local_agent',
755
871
  session_id: sessionId
756
872
  });
873
+
757
874
  out({
758
875
  type: 'user',
759
876
  message: {
@@ -762,10 +879,13 @@ export function fakeClaude(io: FakeIo): FakeProgram {
762
879
  },
763
880
  session_id: sessionId
764
881
  });
882
+
765
883
  assistantText('started');
766
884
  result();
885
+
767
886
  io.later(() => {
768
887
  subagent([{ type: 'tool_use', id: bashUse, name: 'Bash', input: { command, run_in_background: true } }]);
888
+
769
889
  out({
770
890
  type: 'system',
771
891
  subtype: 'task_started',
@@ -776,13 +896,16 @@ export function fakeClaude(io: FakeIo): FakeProgram {
776
896
  is_backgrounded: true,
777
897
  session_id: sessionId
778
898
  });
899
+
779
900
  subagentResult(bashUse, 'Command running in background with ID: b-cmd.');
780
901
  subagent([{ type: 'text', text: 'Command started in the background.' }]);
781
902
  agentNotified('Agent "Run it" finished');
782
903
  assistantText('the subagent says: Command started in the background.');
783
904
  result();
905
+
784
906
  io.later(() => {
785
907
  out({ type: 'system', subtype: 'task_updated', task_id: 'b-cmd', patch: { status: 'completed', end_time: 30000 }, session_id: sessionId });
908
+
786
909
  out({
787
910
  type: 'system',
788
911
  subtype: 'task_notification',
@@ -793,6 +916,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
793
916
  summary: `Background command "${command}" completed (exit code 0)`,
794
917
  session_id: sessionId
795
918
  });
919
+
796
920
  subagent([{ type: 'tool_use', id: 'toolu_cmd_read', name: 'Read', input: { file_path: 'b-cmd.output' } }]);
797
921
  subagentResult('toolu_cmd_read', 'DONE');
798
922
  subagent([{ type: 'text', text: 'The command printed DONE.' }]);
@@ -801,24 +925,30 @@ export function fakeClaude(io: FakeIo): FakeProgram {
801
925
  result();
802
926
  });
803
927
  });
928
+
804
929
  return;
805
930
  }
931
+
806
932
  /*
807
933
  * A two-phase workflow, framed the way Claude Code 2.1.282 streams one: the call answers at launch,
808
934
  * and its agents send no frames of their own; only some progress frames carry a report of the whole run.
809
935
  */
810
936
  if (text.startsWith('workflow:')) {
811
937
  const summary = text.slice(9).trim() || 'Write a file, then read it';
938
+
812
939
  const task = (frame: Record<string, unknown>): void => {
813
940
  out({ type: 'system', task_id: 'wf-task', session_id: sessionId, ...frame });
814
941
  };
942
+
815
943
  const phases = [
816
944
  { type: 'workflow_phase', index: 1, title: 'Write' },
817
945
  { type: 'workflow_phase', index: 2, title: 'Read' }
818
946
  ];
947
+
819
948
  const writer = { type: 'workflow_agent', index: 1, label: 'write-file', phaseIndex: 1, phaseTitle: 'Write', agentId: 'a-writer', startedAt: 1000 };
820
949
  const reader = { type: 'workflow_agent', index: 2, label: 'read-file', phaseIndex: 2, phaseTitle: 'Read', agentId: 'a-reader', startedAt: 6000 };
821
950
  const writerDone = { ...writer, state: 'done', lastToolName: 'Write', tokens: 16264, toolCalls: 1, durationMs: 5000 };
951
+
822
952
  const progress = (description: string, report?: unknown[]): void => {
823
953
  task({
824
954
  subtype: 'task_progress',
@@ -830,6 +960,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
830
960
  ...(report === undefined ? {} : { workflow_progress: report })
831
961
  });
832
962
  };
963
+
833
964
  out({
834
965
  type: 'assistant',
835
966
  message: {
@@ -841,13 +972,16 @@ export function fakeClaude(io: FakeIo): FakeProgram {
841
972
  },
842
973
  session_id: sessionId
843
974
  });
975
+
844
976
  out({
845
977
  type: 'system',
846
978
  subtype: 'background_tasks_changed',
847
979
  tasks: [{ task_id: 'wf-task', task_type: 'local_workflow', description: summary }],
848
980
  session_id: sessionId
849
981
  });
982
+
850
983
  task({ subtype: 'task_started', tool_use_id: 'toolu_wf', description: summary, task_type: 'local_workflow', workflow_name: 'write-and-read' });
984
+
851
985
  out({
852
986
  type: 'user',
853
987
  message: {
@@ -863,15 +997,18 @@ export function fakeClaude(io: FakeIo): FakeProgram {
863
997
  },
864
998
  session_id: sessionId
865
999
  });
1000
+
866
1001
  progress('Write: write-file', [...phases, { ...writer, state: 'start' }]);
867
1002
  assistantText('the workflow runs');
868
1003
  result();
1004
+
869
1005
  io.later(() => {
870
1006
  progress('Write: write-file');
871
1007
  progress('Read: read-file', [...phases, writerDone, { ...reader, state: 'start' }]);
872
1008
  progress('Read: read-file', [...phases, writerDone, { ...reader, state: 'done', lastToolName: 'Read', durationMs: 4000 }]);
873
1009
  out({ type: 'system', subtype: 'background_tasks_changed', tasks: [], session_id: sessionId });
874
1010
  task({ subtype: 'task_updated', patch: { status: 'completed', end_time: 10000 } });
1011
+
875
1012
  task({
876
1013
  subtype: 'task_notification',
877
1014
  tool_use_id: 'toolu_wf',
@@ -880,20 +1017,25 @@ export function fakeClaude(io: FakeIo): FakeProgram {
880
1017
  summary: `Dynamic workflow "${summary}" completed`,
881
1018
  usage: { total_tokens: 32403, tool_uses: 2, duration_ms: 9000 }
882
1019
  });
1020
+
883
1021
  out({ type: 'system', subtype: 'init', session_id: sessionId, model, cwd: io.cwd, tools: ['Bash'], slash_commands: [], argv: args });
884
1022
  assistantText('the workflow is done');
885
1023
  result();
886
1024
  });
1025
+
887
1026
  return;
888
1027
  }
1028
+
889
1029
  if (text.startsWith('run:')) {
890
1030
  const command = text.slice(4).trim();
891
1031
  const id = `msg_${nonce}_${++messageCounter}`;
1032
+
892
1033
  out({
893
1034
  type: 'assistant',
894
1035
  message: { id, model, role: 'assistant', content: [{ type: 'tool_use', id: 'toolu_run', name: 'Bash', input: { command } }], usage },
895
1036
  session_id: sessionId
896
1037
  });
1038
+
897
1039
  // The frames the real CLI emits around a long Bash call, in its order and shape.
898
1040
  out({
899
1041
  type: 'system',
@@ -905,6 +1047,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
905
1047
  task_type: 'local_bash',
906
1048
  session_id: sessionId
907
1049
  });
1050
+
908
1051
  out({
909
1052
  type: 'tool_progress',
910
1053
  tool_use_id: 'toolu_run',
@@ -914,6 +1057,7 @@ export function fakeClaude(io: FakeIo): FakeProgram {
914
1057
  task_id: 'task-1',
915
1058
  session_id: sessionId
916
1059
  });
1060
+
917
1061
  out({
918
1062
  type: 'system',
919
1063
  subtype: 'task_notification',
@@ -924,24 +1068,31 @@ export function fakeClaude(io: FakeIo): FakeProgram {
924
1068
  summary: `Run ${command}`,
925
1069
  session_id: sessionId
926
1070
  });
1071
+
927
1072
  out({
928
1073
  type: 'user',
929
1074
  message: { role: 'user', content: [{ type: 'tool_result', tool_use_id: 'toolu_run', content: `ran: ${command}`, is_error: false }] },
930
1075
  session_id: sessionId
931
1076
  });
1077
+
932
1078
  assistantText('done');
933
1079
  result();
1080
+
934
1081
  return;
935
1082
  }
1083
+
936
1084
  if (text.startsWith('tool:')) {
937
1085
  const command = text.slice(5).trim();
938
1086
  const id = `msg_${nonce}_${++messageCounter}`;
1087
+
939
1088
  out({
940
1089
  type: 'assistant',
941
1090
  message: { id, model, role: 'assistant', content: [{ type: 'tool_use', id: 'toolu_1', name: 'Bash', input: { command } }], usage },
942
1091
  session_id: sessionId
943
1092
  });
1093
+
944
1094
  pendingApproval = { command };
1095
+
945
1096
  out({
946
1097
  type: 'control_request',
947
1098
  request_id: 'req-1',
@@ -956,14 +1107,18 @@ export function fakeClaude(io: FakeIo): FakeProgram {
956
1107
  ]
957
1108
  }
958
1109
  });
1110
+
959
1111
  return;
960
1112
  }
1113
+
961
1114
  if (text === 'system?') {
962
1115
  const at = args.indexOf('--append-system-prompt');
963
1116
  assistantText(at >= 0 ? args[at + 1]! : 'none');
964
1117
  result();
1118
+
965
1119
  return;
966
1120
  }
1121
+
967
1122
  assistantText(`echo: ${text}`);
968
1123
  result();
969
1124
  };
@@ -972,31 +1127,40 @@ export function fakeClaude(io: FakeIo): FakeProgram {
972
1127
  const inner = response.response as
973
1128
  | { behavior?: string; message?: string; updatedInput?: { answers?: Record<string, string> }; updatedPermissions?: unknown[] }
974
1129
  | undefined;
1130
+
975
1131
  if (backgroundAsk !== null && response.request_id === backgroundAsk.requestId) {
976
1132
  const { command } = backgroundAsk;
977
1133
  backgroundAsk = null;
978
1134
  finishBackgroundAgent(command, inner?.behavior === 'allow', inner?.message);
1135
+
979
1136
  return;
980
1137
  }
1138
+
981
1139
  if (pendingQuestion) {
982
1140
  const answer = inner?.updatedInput?.answers?.[pendingQuestion] ?? 'no answer';
983
1141
  pendingQuestion = null;
984
1142
  assistantText(`you chose ${answer}`);
985
1143
  result();
1144
+
986
1145
  return;
987
1146
  }
1147
+
988
1148
  const approval = pendingApproval;
989
1149
  pendingApproval = null;
1150
+
990
1151
  if (!approval) {
991
1152
  return;
992
1153
  }
1154
+
993
1155
  if (inner?.behavior === 'allow') {
994
1156
  const always = Array.isArray(inner.updatedPermissions) && inner.updatedPermissions.length > 0;
1157
+
995
1158
  out({
996
1159
  type: 'user',
997
1160
  message: { role: 'user', content: [{ type: 'tool_result', tool_use_id: 'toolu_1', content: `ran: ${approval.command}`, is_error: false }] },
998
1161
  session_id: sessionId
999
1162
  });
1163
+
1000
1164
  assistantText(always ? 'done, remembered' : 'done');
1001
1165
  } else {
1002
1166
  out({
@@ -1004,8 +1168,10 @@ export function fakeClaude(io: FakeIo): FakeProgram {
1004
1168
  message: { role: 'user', content: [{ type: 'tool_result', tool_use_id: 'toolu_1', content: 'denied by user', is_error: true }] },
1005
1169
  session_id: sessionId
1006
1170
  });
1171
+
1007
1172
  assistantText('denied');
1008
1173
  }
1174
+
1009
1175
  result();
1010
1176
  };
1011
1177
 
@@ -1014,10 +1180,12 @@ export function fakeClaude(io: FakeIo): FakeProgram {
1014
1180
  return {
1015
1181
  onLine: (line) => {
1016
1182
  const frame = JSON.parse(line) as Record<string, unknown>;
1183
+
1017
1184
  if (frame.type === 'user') {
1018
1185
  if (args.includes('--replay-user-messages') && typeof frame.uuid === 'string') {
1019
1186
  out(frame);
1020
1187
  }
1188
+
1021
1189
  const message = frame.message as { content: Array<{ text: string }> };
1022
1190
  handleUser(message.content[0]?.text ?? '');
1023
1191
  } else if (frame.type === 'control_response') {
@@ -1025,9 +1193,11 @@ export function fakeClaude(io: FakeIo): FakeProgram {
1025
1193
  } else if (frame.type === 'control_request') {
1026
1194
  const request = frame.request as { subtype: string; task_id?: string };
1027
1195
  out({ type: 'control_response', response: { subtype: 'success', request_id: frame.request_id, response: {} } });
1196
+
1028
1197
  if (request.subtype === 'stop_task' && (request.task_id === 'a-one' || request.task_id === 'a-two')) {
1029
1198
  settleTwo(request.task_id, true);
1030
1199
  }
1200
+
1031
1201
  if (request.subtype === 'interrupt' && slow) {
1032
1202
  slow = false;
1033
1203
  assistantText('[interrupted]');
@@ -1047,14 +1217,19 @@ export function claudeStoppingOnResume(sessionIds: ReadonlySet<string>, how: 'mi
1047
1217
  return (io) => {
1048
1218
  const resumeAt = io.argv.indexOf('--resume');
1049
1219
  const sessionId = resumeAt >= 0 ? io.argv[resumeAt + 1] : undefined;
1220
+
1050
1221
  if (sessionId === undefined || !sessionIds.has(sessionId)) {
1051
1222
  return fakeClaude(io);
1052
1223
  }
1224
+
1053
1225
  if (how === 'exit') {
1054
1226
  fakeClaude(io);
1227
+
1055
1228
  return { onLine: () => io.exit(1) };
1056
1229
  }
1230
+
1057
1231
  const message = `No conversation found with session ID: ${sessionId}`;
1232
+
1058
1233
  io.out({
1059
1234
  type: 'result',
1060
1235
  subtype: 'error_during_execution',
@@ -1069,8 +1244,10 @@ export function claudeStoppingOnResume(sessionIds: ReadonlySet<string>, how: 'mi
1069
1244
  permission_denials: [],
1070
1245
  errors: [message]
1071
1246
  });
1247
+
1072
1248
  io.err(`${message}\n`);
1073
1249
  io.exit(1);
1250
+
1074
1251
  return { onLine: () => undefined };
1075
1252
  };
1076
1253
  }