@adecore/agents 0.0.1 → 0.17.0-beta.2

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 (348) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +48 -0
  3. package/dist/async.d.ts +2 -0
  4. package/dist/async.js +31 -0
  5. package/dist/chat/attachment-store.d.ts +10 -0
  6. package/dist/chat/attachment-store.js +93 -0
  7. package/dist/chat/backend.d.ts +214 -0
  8. package/dist/chat/backend.js +1 -0
  9. package/dist/chat/background-work.d.ts +8 -0
  10. package/dist/chat/background-work.js +33 -0
  11. package/dist/chat/bookmark-store.d.ts +25 -0
  12. package/dist/chat/bookmark-store.js +141 -0
  13. package/dist/chat/chat-core.d.ts +311 -0
  14. package/dist/chat/chat-core.js +1025 -0
  15. package/dist/chat/chat-log.d.ts +33 -0
  16. package/dist/chat/chat-log.js +163 -0
  17. package/dist/chat/chat-process.d.ts +46 -0
  18. package/dist/chat/chat-process.js +190 -0
  19. package/dist/chat/chat-session.d.ts +209 -0
  20. package/dist/chat/chat-session.js +1511 -0
  21. package/dist/chat/chat-store.d.ts +37 -0
  22. package/dist/chat/chat-store.js +202 -0
  23. package/dist/chat/chat-title.d.ts +10 -0
  24. package/dist/chat/chat-title.js +74 -0
  25. package/dist/chat/claude-backend.d.ts +35 -0
  26. package/dist/chat/claude-backend.js +214 -0
  27. package/dist/chat/claude-protocol.d.ts +41 -0
  28. package/dist/chat/claude-protocol.js +642 -0
  29. package/dist/chat/claude-title.d.ts +11 -0
  30. package/dist/chat/claude-title.js +97 -0
  31. package/dist/chat/claude-transcript.d.ts +28 -0
  32. package/dist/chat/claude-transcript.js +211 -0
  33. package/dist/chat/codex-backend.d.ts +51 -0
  34. package/dist/chat/codex-backend.js +364 -0
  35. package/dist/chat/codex-protocol.d.ts +56 -0
  36. package/dist/chat/codex-protocol.js +767 -0
  37. package/dist/chat/codex-thread.d.ts +32 -0
  38. package/dist/chat/codex-thread.js +131 -0
  39. package/dist/chat/codex-transport.d.ts +42 -0
  40. package/dist/chat/codex-transport.js +188 -0
  41. package/dist/chat/composer-preferences.d.ts +15 -0
  42. package/dist/chat/composer-preferences.js +46 -0
  43. package/dist/chat/context-breakdown.d.ts +2 -0
  44. package/dist/chat/context-breakdown.js +74 -0
  45. package/dist/chat/delta-coalescer.d.ts +12 -0
  46. package/dist/chat/delta-coalescer.js +55 -0
  47. package/dist/chat/errors.d.ts +4 -0
  48. package/dist/chat/errors.js +4 -0
  49. package/dist/chat/fake-claude.d.ts +3 -0
  50. package/dist/chat/fake-claude.js +982 -0
  51. package/dist/chat/fake-cli.d.ts +25 -0
  52. package/dist/chat/fake-cli.js +157 -0
  53. package/dist/chat/fake-codex.d.ts +10 -0
  54. package/dist/chat/fake-codex.js +502 -0
  55. package/dist/chat/fixtures/claude-2.1.285-auto-fallback.json +24 -0
  56. package/dist/chat/fixtures/claude-2.1.285-native-children.json +6600 -0
  57. package/dist/chat/fixtures/claude-2.1.285-resume-direct-compact.json +202 -0
  58. package/dist/chat/fixtures/claude-2.1.285-resume-task-notification.json +57 -0
  59. package/dist/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a4c2e8f10b3d5a7e9.jsonl +10 -0
  60. package/dist/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a4c2e8f10b3d5a7e9.meta.json +1 -0
  61. package/dist/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a9b8c7d6e5f4a3b21.jsonl +2 -0
  62. package/dist/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a9b8c7d6e5f4a3b21.meta.json +1 -0
  63. package/dist/chat/fixtures/codex-0.159.2-native-approval.json +119 -0
  64. package/dist/chat/fixtures/codex-0.159.2-native-file-approval.json +136 -0
  65. package/dist/chat/fixtures/codex-0.159.2-native-interacted.json +220 -0
  66. package/dist/chat/fixtures/codex-0.159.2-usage-windows.json +74 -0
  67. package/dist/chat/input.d.ts +19 -0
  68. package/dist/chat/input.js +71 -0
  69. package/dist/chat/limit-resume.d.ts +11 -0
  70. package/dist/chat/limit-resume.js +68 -0
  71. package/dist/chat/projector.d.ts +60 -0
  72. package/dist/chat/projector.js +860 -0
  73. package/dist/chat/request-summary.d.ts +3 -0
  74. package/dist/chat/request-summary.js +143 -0
  75. package/dist/chat/subagent-projection.d.ts +8 -0
  76. package/dist/chat/subagent-projection.js +40 -0
  77. package/dist/chat/subagent-reader.d.ts +66 -0
  78. package/dist/chat/subagent-reader.js +396 -0
  79. package/dist/chat/subagent-settlement.d.ts +6 -0
  80. package/dist/chat/subagent-settlement.js +103 -0
  81. package/dist/chat/thread.d.ts +30 -0
  82. package/dist/chat/thread.js +147 -0
  83. package/dist/chat/wake-chat.d.ts +20 -0
  84. package/dist/chat/wake-chat.js +27 -0
  85. package/dist/client-sinks.d.ts +9 -0
  86. package/dist/client-sinks.js +34 -0
  87. package/dist/coded-error.d.ts +5 -0
  88. package/dist/coded-error.js +17 -0
  89. package/dist/context/argv.d.ts +11 -0
  90. package/dist/context/argv.js +58 -0
  91. package/dist/context/refusal.d.ts +9 -0
  92. package/dist/context/refusal.js +38 -0
  93. package/dist/context/verb.d.ts +85 -0
  94. package/dist/context/verb.js +233 -0
  95. package/dist/error-text.d.ts +5 -0
  96. package/dist/error-text.js +24 -0
  97. package/dist/events.d.ts +12 -0
  98. package/dist/events.js +1 -0
  99. package/dist/fs.d.ts +9 -0
  100. package/dist/fs.js +107 -0
  101. package/dist/host/agent-host.d.ts +28 -0
  102. package/dist/host/agent-host.js +105 -0
  103. package/dist/host/environment.d.ts +7 -0
  104. package/dist/host/environment.js +22 -0
  105. package/dist/host/handlers.d.ts +30 -0
  106. package/dist/host/handlers.js +111 -0
  107. package/dist/host/memory-port.d.ts +2 -0
  108. package/dist/host/memory-port.js +25 -0
  109. package/dist/host/wiring.d.ts +40 -0
  110. package/dist/host/wiring.js +120 -0
  111. package/dist/lineage.d.ts +44 -0
  112. package/dist/lineage.js +134 -0
  113. package/dist/messages/deliver-message.d.ts +21 -0
  114. package/dist/messages/deliver-message.js +42 -0
  115. package/dist/messages/deliver-notice.d.ts +25 -0
  116. package/dist/messages/deliver-notice.js +75 -0
  117. package/dist/messages/notice-notes.d.ts +15 -0
  118. package/dist/messages/notice-notes.js +26 -0
  119. package/dist/messages/notice-store.d.ts +30 -0
  120. package/dist/messages/notice-store.js +120 -0
  121. package/dist/modes.d.ts +11 -0
  122. package/dist/modes.js +28 -0
  123. package/dist/outbox/manual-clock.d.ts +10 -0
  124. package/dist/outbox/manual-clock.js +25 -0
  125. package/dist/outbox/outbox-worker.d.ts +44 -0
  126. package/dist/outbox/outbox-worker.js +173 -0
  127. package/dist/outbox/outbox.d.ts +37 -0
  128. package/dist/outbox/outbox.js +76 -0
  129. package/dist/providers/accounts/accounts.d.ts +20 -0
  130. package/dist/providers/accounts/accounts.js +158 -0
  131. package/dist/providers/accounts/launch.d.ts +17 -0
  132. package/dist/providers/accounts/launch.js +30 -0
  133. package/dist/providers/accounts/service.d.ts +87 -0
  134. package/dist/providers/accounts/service.js +538 -0
  135. package/dist/providers/accounts/shadow-home.d.ts +8 -0
  136. package/dist/providers/accounts/shadow-home.js +70 -0
  137. package/dist/providers/accounts/status.d.ts +12 -0
  138. package/dist/providers/accounts/status.js +85 -0
  139. package/dist/providers/accounts/store.d.ts +14 -0
  140. package/dist/providers/accounts/store.js +112 -0
  141. package/dist/providers/accounts/test-accounts.d.ts +12 -0
  142. package/dist/providers/accounts/test-accounts.js +38 -0
  143. package/dist/providers/accounts/variables.d.ts +27 -0
  144. package/dist/providers/accounts/variables.js +94 -0
  145. package/dist/providers/catalog.d.ts +17 -0
  146. package/dist/providers/catalog.js +76 -0
  147. package/dist/providers/claude-models.json +114 -0
  148. package/dist/providers/claude-provider.d.ts +4 -0
  149. package/dist/providers/claude-provider.js +29 -0
  150. package/dist/providers/claude.d.ts +15 -0
  151. package/dist/providers/claude.js +105 -0
  152. package/dist/providers/codex-models.json +135 -0
  153. package/dist/providers/codex-provider.d.ts +4 -0
  154. package/dist/providers/codex-provider.js +27 -0
  155. package/dist/providers/codex-rules.d.ts +9 -0
  156. package/dist/providers/codex-rules.js +35 -0
  157. package/dist/providers/codex.d.ts +14 -0
  158. package/dist/providers/codex.js +52 -0
  159. package/dist/providers/detect.d.ts +6 -0
  160. package/dist/providers/detect.js +30 -0
  161. package/dist/providers/provider.d.ts +25 -0
  162. package/dist/providers/provider.js +1 -0
  163. package/dist/providers/registry.d.ts +28 -0
  164. package/dist/providers/registry.js +118 -0
  165. package/dist/record-directory.d.ts +27 -0
  166. package/dist/record-directory.js +114 -0
  167. package/dist/run-process.d.ts +13 -0
  168. package/dist/run-process.js +42 -0
  169. package/dist/serializer.d.ts +9 -0
  170. package/dist/serializer.js +34 -0
  171. package/dist/skills.d.ts +21 -0
  172. package/dist/skills.js +174 -0
  173. package/dist/tasks/background-limit.d.ts +16 -0
  174. package/dist/tasks/background-limit.js +55 -0
  175. package/dist/tasks/end-children.d.ts +34 -0
  176. package/dist/tasks/end-children.js +53 -0
  177. package/dist/tasks/give-task.d.ts +14 -0
  178. package/dist/tasks/give-task.js +33 -0
  179. package/dist/tasks/task-coordinator.d.ts +52 -0
  180. package/dist/tasks/task-coordinator.js +323 -0
  181. package/dist/tasks/task-store.d.ts +49 -0
  182. package/dist/tasks/task-store.js +197 -0
  183. package/dist/tasks/task-work.d.ts +47 -0
  184. package/dist/tasks/task-work.js +28 -0
  185. package/dist/tasks/waiting-child.d.ts +51 -0
  186. package/dist/tasks/waiting-child.js +143 -0
  187. package/dist/tasks/wake-parent.d.ts +19 -0
  188. package/dist/tasks/wake-parent.js +109 -0
  189. package/dist/tasks/wiring.d.ts +51 -0
  190. package/dist/tasks/wiring.js +151 -0
  191. package/dist/title-file.d.ts +2 -0
  192. package/dist/title-file.js +55 -0
  193. package/dist/usage/accounts.d.ts +15 -0
  194. package/dist/usage/accounts.js +64 -0
  195. package/dist/usage/aggregate.d.ts +12 -0
  196. package/dist/usage/aggregate.js +154 -0
  197. package/dist/usage/exchange.d.ts +16 -0
  198. package/dist/usage/exchange.js +84 -0
  199. package/dist/usage/index-file.d.ts +15 -0
  200. package/dist/usage/index-file.js +123 -0
  201. package/dist/usage/limits/monitor.d.ts +50 -0
  202. package/dist/usage/limits/monitor.js +179 -0
  203. package/dist/usage/limits/normalize.d.ts +21 -0
  204. package/dist/usage/limits/normalize.js +160 -0
  205. package/dist/usage/limits/probe.d.ts +9 -0
  206. package/dist/usage/limits/probe.js +113 -0
  207. package/dist/usage/prices-snapshot.json +1071 -0
  208. package/dist/usage/pricing.d.ts +34 -0
  209. package/dist/usage/pricing.js +186 -0
  210. package/dist/usage/projects.d.ts +17 -0
  211. package/dist/usage/projects.js +49 -0
  212. package/dist/usage/readers/claude.d.ts +3 -0
  213. package/dist/usage/readers/claude.js +59 -0
  214. package/dist/usage/readers/codex.d.ts +23 -0
  215. package/dist/usage/readers/codex.js +155 -0
  216. package/dist/usage/record.d.ts +15 -0
  217. package/dist/usage/record.js +39 -0
  218. package/dist/usage/roots.d.ts +12 -0
  219. package/dist/usage/roots.js +29 -0
  220. package/dist/usage/scanner.d.ts +30 -0
  221. package/dist/usage/scanner.js +234 -0
  222. package/dist/usage/usage-service.d.ts +35 -0
  223. package/dist/usage/usage-service.js +130 -0
  224. package/dist/watch-seam.d.ts +29 -0
  225. package/dist/watch-seam.js +78 -0
  226. package/dist/watch-test-helpers.d.ts +28 -0
  227. package/dist/watch-test-helpers.js +86 -0
  228. package/package.json +42 -3
  229. package/src/async.ts +40 -0
  230. package/src/chat/attachment-store.ts +108 -0
  231. package/src/chat/backend.ts +199 -0
  232. package/src/chat/background-work.ts +42 -0
  233. package/src/chat/bookmark-store.ts +166 -0
  234. package/src/chat/chat-core.ts +1228 -0
  235. package/src/chat/chat-log.ts +187 -0
  236. package/src/chat/chat-process.ts +237 -0
  237. package/src/chat/chat-session.ts +1723 -0
  238. package/src/chat/chat-store.ts +261 -0
  239. package/src/chat/chat-title.ts +90 -0
  240. package/src/chat/claude-backend.ts +235 -0
  241. package/src/chat/claude-protocol.ts +684 -0
  242. package/src/chat/claude-title.ts +106 -0
  243. package/src/chat/claude-transcript.ts +233 -0
  244. package/src/chat/codex-backend.ts +397 -0
  245. package/src/chat/codex-protocol.ts +872 -0
  246. package/src/chat/codex-thread.ts +162 -0
  247. package/src/chat/codex-transport.ts +224 -0
  248. package/src/chat/composer-preferences.ts +67 -0
  249. package/src/chat/context-breakdown.ts +80 -0
  250. package/src/chat/delta-coalescer.ts +62 -0
  251. package/src/chat/errors.ts +25 -0
  252. package/src/chat/fake-claude.ts +1006 -0
  253. package/src/chat/fake-cli.ts +199 -0
  254. package/src/chat/fake-codex.ts +534 -0
  255. package/src/chat/fixtures/claude-2.1.285-auto-fallback.json +24 -0
  256. package/src/chat/fixtures/claude-2.1.285-native-children.json +6600 -0
  257. package/src/chat/fixtures/claude-2.1.285-resume-direct-compact.json +202 -0
  258. package/src/chat/fixtures/claude-2.1.285-resume-task-notification.json +57 -0
  259. package/src/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a4c2e8f10b3d5a7e9.jsonl +10 -0
  260. package/src/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a4c2e8f10b3d5a7e9.meta.json +1 -0
  261. package/src/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a9b8c7d6e5f4a3b21.jsonl +2 -0
  262. package/src/chat/fixtures/claude-projects/-work-demo/5f1c2a9e-0b7d-4c1e-9a53-3e2f8d6b7a10/subagents/agent-a9b8c7d6e5f4a3b21.meta.json +1 -0
  263. package/src/chat/fixtures/codex-0.159.2-native-approval.json +119 -0
  264. package/src/chat/fixtures/codex-0.159.2-native-file-approval.json +136 -0
  265. package/src/chat/fixtures/codex-0.159.2-native-interacted.json +220 -0
  266. package/src/chat/fixtures/codex-0.159.2-usage-windows.json +74 -0
  267. package/src/chat/input.ts +89 -0
  268. package/src/chat/limit-resume.ts +74 -0
  269. package/src/chat/projector.ts +969 -0
  270. package/src/chat/request-summary.ts +168 -0
  271. package/src/chat/subagent-projection.ts +45 -0
  272. package/src/chat/subagent-reader.ts +469 -0
  273. package/src/chat/subagent-settlement.ts +111 -0
  274. package/src/chat/thread.ts +167 -0
  275. package/src/chat/wake-chat.ts +45 -0
  276. package/src/client-sinks.ts +39 -0
  277. package/src/coded-error.ts +18 -0
  278. package/src/context/argv.ts +61 -0
  279. package/src/context/refusal.ts +50 -0
  280. package/src/context/verb.ts +392 -0
  281. package/src/error-text.ts +28 -0
  282. package/src/events.ts +9 -0
  283. package/src/fs.ts +118 -0
  284. package/src/host/agent-host.ts +124 -0
  285. package/src/host/environment.ts +31 -0
  286. package/src/host/handlers.ts +142 -0
  287. package/src/host/memory-port.ts +27 -0
  288. package/src/host/wiring.ts +164 -0
  289. package/src/lineage.ts +159 -0
  290. package/src/messages/deliver-message.ts +61 -0
  291. package/src/messages/deliver-notice.ts +120 -0
  292. package/src/messages/notice-notes.ts +34 -0
  293. package/src/messages/notice-store.ts +142 -0
  294. package/src/modes.ts +38 -0
  295. package/src/outbox/manual-clock.ts +31 -0
  296. package/src/outbox/outbox-worker.ts +214 -0
  297. package/src/outbox/outbox.ts +106 -0
  298. package/src/providers/accounts/accounts.ts +192 -0
  299. package/src/providers/accounts/launch.ts +55 -0
  300. package/src/providers/accounts/service.ts +632 -0
  301. package/src/providers/accounts/shadow-home.ts +85 -0
  302. package/src/providers/accounts/status.ts +106 -0
  303. package/src/providers/accounts/store.ts +158 -0
  304. package/src/providers/accounts/test-accounts.ts +48 -0
  305. package/src/providers/accounts/variables.ts +132 -0
  306. package/src/providers/catalog.ts +90 -0
  307. package/src/providers/claude-models.json +114 -0
  308. package/src/providers/claude-provider.ts +32 -0
  309. package/src/providers/claude.ts +123 -0
  310. package/src/providers/codex-models.json +135 -0
  311. package/src/providers/codex-provider.ts +30 -0
  312. package/src/providers/codex-rules.ts +54 -0
  313. package/src/providers/codex.ts +69 -0
  314. package/src/providers/detect.ts +40 -0
  315. package/src/providers/provider.ts +46 -0
  316. package/src/providers/registry.ts +150 -0
  317. package/src/record-directory.ts +136 -0
  318. package/src/run-process.ts +61 -0
  319. package/src/serializer.ts +41 -0
  320. package/src/skills.ts +199 -0
  321. package/src/tasks/background-limit.ts +72 -0
  322. package/src/tasks/end-children.ts +100 -0
  323. package/src/tasks/give-task.ts +52 -0
  324. package/src/tasks/task-coordinator.ts +384 -0
  325. package/src/tasks/task-store.ts +236 -0
  326. package/src/tasks/task-work.ts +62 -0
  327. package/src/tasks/waiting-child.ts +203 -0
  328. package/src/tasks/wake-parent.ts +142 -0
  329. package/src/tasks/wiring.ts +231 -0
  330. package/src/title-file.ts +57 -0
  331. package/src/usage/accounts.ts +86 -0
  332. package/src/usage/aggregate.ts +220 -0
  333. package/src/usage/exchange.ts +93 -0
  334. package/src/usage/index-file.ts +178 -0
  335. package/src/usage/limits/monitor.ts +239 -0
  336. package/src/usage/limits/normalize.ts +188 -0
  337. package/src/usage/limits/probe.ts +120 -0
  338. package/src/usage/prices-snapshot.json +1071 -0
  339. package/src/usage/pricing.ts +236 -0
  340. package/src/usage/projects.ts +65 -0
  341. package/src/usage/readers/claude.ts +60 -0
  342. package/src/usage/readers/codex.ts +196 -0
  343. package/src/usage/record.ts +60 -0
  344. package/src/usage/roots.ts +39 -0
  345. package/src/usage/scanner.ts +265 -0
  346. package/src/usage/usage-service.ts +161 -0
  347. package/src/watch-seam.ts +111 -0
  348. package/src/watch-test-helpers.ts +102 -0
@@ -0,0 +1,982 @@
1
+ /*
2
+ * Network-free Claude stream-json fake. Prompt commands exercise permissions, tools, questions,
3
+ * compaction, edits, subagents, interrupts and crashes through the real frame shapes.
4
+ */
5
+ import { spawn } from 'node:child_process';
6
+ import { writeFileSync } from 'node:fs';
7
+ import { resolve } from 'node:path';
8
+ import { runOverStdio } from './fake-cli.js';
9
+ export function fakeClaude(io) {
10
+ const args = io.argv;
11
+ const resumeAt = args.indexOf('--resume');
12
+ const sessionId = resumeAt >= 0 ? args[resumeAt + 1] : `fake-${Math.random().toString(36).slice(2, 8)}`;
13
+ const modelAt = args.indexOf('--model');
14
+ const model = modelAt >= 0 ? args[modelAt + 1] : 'fake-model';
15
+ const out = io.out;
16
+ let messageCounter = 0;
17
+ // A real message id is unique across processes, which a fork's thread holding a resumed CLI's replies relies on.
18
+ const nonce = Math.random().toString(36).slice(2, 6);
19
+ const usage = { input_tokens: 10, cache_creation_input_tokens: 100, cache_read_input_tokens: 1000, output_tokens: 5 };
20
+ const assistantText = (text) => {
21
+ const id = `msg_${nonce}_${++messageCounter}`;
22
+ out({ type: 'stream_event', event: { type: 'message_start', message: { id, model } }, session_id: sessionId });
23
+ out({
24
+ type: 'stream_event',
25
+ event: { type: 'content_block_start', index: 0, content_block: { type: 'thinking', thinking: '' } },
26
+ session_id: sessionId
27
+ });
28
+ out({
29
+ type: 'stream_event',
30
+ event: { type: 'content_block_delta', index: 0, delta: { type: 'thinking_delta', thinking: 'weighing it' } },
31
+ session_id: sessionId
32
+ });
33
+ out({ type: 'stream_event', event: { type: 'content_block_stop', index: 0 }, session_id: sessionId });
34
+ out({ type: 'stream_event', event: { type: 'content_block_start', index: 1, content_block: { type: 'text', text: '' } }, session_id: sessionId });
35
+ const half = Math.ceil(text.length / 2);
36
+ out({
37
+ type: 'stream_event',
38
+ event: { type: 'content_block_delta', index: 1, delta: { type: 'text_delta', text: text.slice(0, half) } },
39
+ session_id: sessionId
40
+ });
41
+ out({
42
+ type: 'stream_event',
43
+ event: { type: 'content_block_delta', index: 1, delta: { type: 'text_delta', text: text.slice(half) } },
44
+ session_id: sessionId
45
+ });
46
+ // Every frame carries the uuid of its line in the transcript, which is what a fork is cut at.
47
+ out({
48
+ type: 'assistant',
49
+ uuid: `${id}-thinking`,
50
+ message: { id, model, role: 'assistant', content: [{ type: 'thinking', thinking: '' }], usage },
51
+ session_id: sessionId
52
+ });
53
+ out({
54
+ type: 'assistant',
55
+ uuid: `${id}-text`,
56
+ message: { id, model, role: 'assistant', content: [{ type: 'text', text }], usage },
57
+ session_id: sessionId
58
+ });
59
+ out({ type: 'stream_event', event: { type: 'message_stop' }, session_id: sessionId });
60
+ };
61
+ const result = () => {
62
+ out({
63
+ type: 'result',
64
+ subtype: 'success',
65
+ is_error: false,
66
+ num_turns: 1,
67
+ total_cost_usd: 0.01,
68
+ usage,
69
+ // The real CLI reports the model's maximum here whatever `[1m]` said, so the fake does too.
70
+ modelUsage: { [model]: { contextWindow: 1000000 } },
71
+ session_id: sessionId
72
+ });
73
+ };
74
+ // A request the API turned away: a synthetic message with the reason, then a result that failed without saying which error.
75
+ const refusal = (text, error, status) => {
76
+ const id = `msg_${nonce}_${++messageCounter}`;
77
+ out({
78
+ type: 'assistant',
79
+ uuid: `${id}-text`,
80
+ message: { id, model: '<synthetic>', role: 'assistant', content: [{ type: 'text', text }] },
81
+ parent_tool_use_id: null,
82
+ error,
83
+ session_id: sessionId
84
+ });
85
+ out({
86
+ type: 'result',
87
+ subtype: 'success',
88
+ is_error: true,
89
+ api_error_status: status,
90
+ result: text,
91
+ num_turns: 1,
92
+ total_cost_usd: 0,
93
+ usage,
94
+ session_id: sessionId
95
+ });
96
+ };
97
+ // What a subagent's own work looks like on the wire: frames of its own, under the call that spawned it.
98
+ const subagentWork = (agentToolUseId, report) => {
99
+ const child = (content) => {
100
+ out({
101
+ type: 'assistant',
102
+ message: { id: `msg_${nonce}_${++messageCounter}`, model, role: 'assistant', content, usage },
103
+ parent_tool_use_id: agentToolUseId,
104
+ subagent_type: 'general-purpose',
105
+ session_id: sessionId
106
+ });
107
+ };
108
+ const childResult = (toolUseId, content) => {
109
+ out({
110
+ type: 'user',
111
+ message: { role: 'user', content: [{ type: 'tool_result', tool_use_id: toolUseId, content, is_error: false }] },
112
+ parent_tool_use_id: agentToolUseId,
113
+ session_id: sessionId
114
+ });
115
+ };
116
+ child([{ type: 'tool_use', id: `${agentToolUseId}_read`, name: 'Read', input: { file_path: 'README.md' } }]);
117
+ childResult(`${agentToolUseId}_read`, '# Notes');
118
+ child([{ type: 'tool_use', id: `${agentToolUseId}_bash`, name: 'Bash', input: { command: 'ls', description: 'List files' } }]);
119
+ childResult(`${agentToolUseId}_bash`, 'README.md');
120
+ out({
121
+ type: 'system',
122
+ subtype: 'task_progress',
123
+ task_id: `task-${agentToolUseId}`,
124
+ tool_use_id: agentToolUseId,
125
+ description: 'Running List files',
126
+ subagent_type: 'general-purpose',
127
+ usage: { total_tokens: 1500, tool_uses: 2, duration_ms: 250 },
128
+ last_tool_name: 'Bash',
129
+ session_id: sessionId
130
+ });
131
+ child([{ type: 'text', text: report }]);
132
+ };
133
+ // The footer the CLI appends to a foreground report, in its own words.
134
+ const agentFooter = (agentId) => `\n\nagentId: ${agentId} (use SendMessage with to: '${agentId}', summary: 'the report' to continue this agent)\n<usage>subagent_tokens: 1500\ntool_uses: 2\nduration_ms: 250</usage>`;
135
+ let pendingApproval = null;
136
+ let pendingQuestion = null;
137
+ // What a background agent asked; its answer comes in after the turn that launched it, whenever a person gives one.
138
+ let backgroundAsk = null;
139
+ let slow = false;
140
+ // 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.
141
+ const finishBackgroundAgent = (command, allowed, message) => {
142
+ const toolUseId = 'toolu_bgask';
143
+ out({
144
+ type: 'user',
145
+ message: {
146
+ role: 'user',
147
+ content: [
148
+ {
149
+ type: 'tool_result',
150
+ tool_use_id: `${toolUseId}_bash`,
151
+ content: allowed ? `ran: ${command}` : (message ?? 'denied by user'),
152
+ is_error: !allowed
153
+ }
154
+ ]
155
+ },
156
+ parent_tool_use_id: toolUseId,
157
+ session_id: sessionId
158
+ });
159
+ const report = allowed ? `the agent ran ${command}` : 'the agent could not run it';
160
+ out({
161
+ type: 'assistant',
162
+ message: { id: `msg_${nonce}_${++messageCounter}`, model, role: 'assistant', content: [{ type: 'text', text: report }], usage },
163
+ parent_tool_use_id: toolUseId,
164
+ session_id: sessionId
165
+ });
166
+ out({
167
+ type: 'system',
168
+ subtype: 'task_notification',
169
+ task_id: 'task-bgask',
170
+ tool_use_id: toolUseId,
171
+ status: 'completed',
172
+ output_file: '',
173
+ summary: report,
174
+ session_id: sessionId
175
+ });
176
+ out({ type: 'system', subtype: 'init', session_id: sessionId, model, cwd: io.cwd, tools: ['Bash'], slash_commands: [], argv: args });
177
+ assistantText(report);
178
+ result();
179
+ };
180
+ const handleUser = (text) => {
181
+ if (text === 'crash') {
182
+ io.exit(1);
183
+ return;
184
+ }
185
+ if (text === 'crash loudly') {
186
+ io.err(`${'warming up\n'.repeat(2000)}Error: the fake lost its key\n at handleUser (fake-claude.ts)\n`);
187
+ io.exit(1);
188
+ return;
189
+ }
190
+ // Over stdio only: a process of the CLI's own, as a dev server an agent started would be.
191
+ if (text === 'start a grandchild') {
192
+ const grandchild = spawn('sleep', ['600'], { stdio: 'ignore' });
193
+ assistantText(`grandchild ${grandchild.pid}`);
194
+ result();
195
+ return;
196
+ }
197
+ /*
198
+ * A background agent that asks to run a command while the turn that launched it ends, framed the way Claude
199
+ * Code 2.1.282 sends it: the request names the agent's own call and its `agent_id`, the result of the turn
200
+ * comes anyway, and the request stays open until it is answered or the CLI takes it back. A line `slow`
201
+ * under it keeps the turn open instead, for a person to stop it; a line `late` holds the agent's command
202
+ * and its request until after the result, when the CLI runs no turn.
203
+ */
204
+ if (text.startsWith('background approval:')) {
205
+ const [first = '', ...rest] = text.split('\n');
206
+ const command = first.slice(20).trim() || 'ls';
207
+ const toolUseId = 'toolu_bgask';
208
+ out({
209
+ type: 'assistant',
210
+ message: {
211
+ id: `msg_${nonce}_${++messageCounter}`,
212
+ model,
213
+ role: 'assistant',
214
+ content: [
215
+ { type: 'tool_use', id: toolUseId, name: 'Agent', input: { description: 'check files', prompt: command, run_in_background: true } }
216
+ ],
217
+ usage
218
+ },
219
+ session_id: sessionId
220
+ });
221
+ out({
222
+ type: 'system',
223
+ subtype: 'task_started',
224
+ task_id: 'task-bgask',
225
+ tool_use_id: toolUseId,
226
+ description: 'check files',
227
+ subagent_type: 'general-purpose',
228
+ is_backgrounded: true,
229
+ task_type: 'local_agent',
230
+ session_id: sessionId
231
+ });
232
+ out({
233
+ type: 'user',
234
+ message: {
235
+ role: 'user',
236
+ content: [{ type: 'tool_result', tool_use_id: toolUseId, content: 'Async agent launched successfully.', is_error: false }]
237
+ },
238
+ session_id: sessionId
239
+ });
240
+ const ask = () => {
241
+ out({
242
+ type: 'assistant',
243
+ message: {
244
+ id: `msg_${nonce}_${++messageCounter}`,
245
+ model,
246
+ role: 'assistant',
247
+ content: [{ type: 'tool_use', id: `${toolUseId}_bash`, name: 'Bash', input: { command } }],
248
+ usage
249
+ },
250
+ parent_tool_use_id: toolUseId,
251
+ session_id: sessionId
252
+ });
253
+ backgroundAsk = { requestId: 'req-bg', command };
254
+ out({
255
+ type: 'control_request',
256
+ request_id: 'req-bg',
257
+ request: {
258
+ subtype: 'can_use_tool',
259
+ tool_name: 'Bash',
260
+ display_name: 'Bash',
261
+ input: { command },
262
+ description: `Run ${command}`,
263
+ tool_use_id: `${toolUseId}_bash`,
264
+ agent_id: 'a-bgask'
265
+ }
266
+ });
267
+ };
268
+ if (rest.includes('late')) {
269
+ assistantText('the agent runs');
270
+ result();
271
+ io.later(ask);
272
+ return;
273
+ }
274
+ ask();
275
+ if (rest.includes('slow')) {
276
+ slow = true;
277
+ return;
278
+ }
279
+ assistantText('the agent runs');
280
+ result();
281
+ return;
282
+ }
283
+ // A line of its own, so a prompt with a task brief under it or a message above it still waits.
284
+ if (text.split('\n').some((line) => line === 'slow')) {
285
+ slow = true;
286
+ out({ type: 'stream_event', event: { type: 'message_start', message: { id: `msg_${nonce}_${++messageCounter}`, model } }, session_id: sessionId });
287
+ return;
288
+ }
289
+ // Lines of their own, as the two ways Claude Code 2.1.281 ends a turn it could not get an answer for.
290
+ const limit = text.split('\n').find((line) => line.startsWith('limit:'));
291
+ if (limit !== undefined) {
292
+ const resetsAt = Number(limit.slice(6));
293
+ out({
294
+ type: 'rate_limit_event',
295
+ rate_limit_info: { status: 'rejected', resetsAt, rateLimitType: 'five_hour', utilization: 1 },
296
+ uuid: `rate-${nonce}`,
297
+ session_id: sessionId
298
+ });
299
+ refusal("You've hit your session limit · resets 3pm", 'rate_limit', 429);
300
+ return;
301
+ }
302
+ // 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.
303
+ const output = text.split('\n').find((line) => line.startsWith('output:'));
304
+ if (output !== undefined) {
305
+ const bytes = Number(output.slice(7));
306
+ const toolUseId = `toolu_${nonce}_${++messageCounter}`;
307
+ out({
308
+ type: 'assistant',
309
+ message: {
310
+ id: `msg_${nonce}_${messageCounter}`,
311
+ model,
312
+ role: 'assistant',
313
+ content: [{ type: 'tool_use', id: toolUseId, name: 'Bash', input: { command: 'cat build.log' } }],
314
+ usage
315
+ },
316
+ session_id: sessionId
317
+ });
318
+ const line = 'build step finished without warnings\n';
319
+ out({
320
+ type: 'user',
321
+ message: {
322
+ role: 'user',
323
+ content: [
324
+ { type: 'tool_result', tool_use_id: toolUseId, content: line.repeat(Math.ceil(bytes / line.length)).slice(0, bytes), is_error: false }
325
+ ]
326
+ },
327
+ session_id: sessionId
328
+ });
329
+ assistantText(`read ${bytes} bytes of build log`);
330
+ result();
331
+ return;
332
+ }
333
+ if (text.split('\n').includes('overloaded')) {
334
+ refusal('API Error: Repeated 529 Overloaded errors', 'overloaded', 529);
335
+ return;
336
+ }
337
+ if (text === 'compact') {
338
+ out({ type: 'system', subtype: 'compact_boundary', compact_metadata: { trigger: 'manual', pre_tokens: 5000 }, session_id: sessionId });
339
+ assistantText('compacted');
340
+ result();
341
+ return;
342
+ }
343
+ if (text.startsWith('ask:')) {
344
+ const question = text.slice(4).trim();
345
+ pendingQuestion = question;
346
+ out({
347
+ type: 'control_request',
348
+ request_id: 'req-q',
349
+ request: {
350
+ subtype: 'can_use_tool',
351
+ tool_name: 'AskUserQuestion',
352
+ input: {
353
+ questions: [
354
+ {
355
+ question,
356
+ header: 'Choice',
357
+ options: [
358
+ { label: 'Red', description: 'Warm' },
359
+ { label: 'Blue', description: 'Cool' }
360
+ ],
361
+ multiSelect: false
362
+ }
363
+ ]
364
+ },
365
+ tool_use_id: 'toolu_q'
366
+ }
367
+ });
368
+ return;
369
+ }
370
+ if (text.startsWith('write:')) {
371
+ const [path = '', ...rest] = text.slice(6).trim().split(' ');
372
+ const content = `${rest.join(' ')}\n`;
373
+ // Against the CLI's own folder: in a test the fake shares the test runner's working directory.
374
+ writeFileSync(resolve(io.cwd, path), content);
375
+ const id = `msg_${nonce}_${++messageCounter}`;
376
+ out({
377
+ type: 'assistant',
378
+ message: {
379
+ id,
380
+ model,
381
+ role: 'assistant',
382
+ content: [{ type: 'tool_use', id: 'toolu_write', name: 'Write', input: { file_path: path, content } }],
383
+ usage
384
+ },
385
+ session_id: sessionId
386
+ });
387
+ out({
388
+ type: 'user',
389
+ message: { role: 'user', content: [{ type: 'tool_result', tool_use_id: 'toolu_write', content: `wrote ${path}`, is_error: false }] },
390
+ session_id: sessionId
391
+ });
392
+ assistantText('written');
393
+ result();
394
+ return;
395
+ }
396
+ // `delegate late:` sends the subagent's first work before `task_started`, an order the CLI does not promise to avoid.
397
+ const late = text.startsWith('delegate late:');
398
+ if (late || text.startsWith('delegate:')) {
399
+ const description = text.slice(text.indexOf(':') + 1).trim();
400
+ const report = '# Report\n\n- one\n- two';
401
+ out({
402
+ type: 'assistant',
403
+ message: {
404
+ id: `msg_${nonce}_${++messageCounter}`,
405
+ model,
406
+ role: 'assistant',
407
+ content: [
408
+ {
409
+ type: 'tool_use',
410
+ id: 'toolu_delegate',
411
+ name: 'Agent',
412
+ input: { description, subagent_type: 'general-purpose', prompt: `Do this: ${description}` }
413
+ }
414
+ ],
415
+ usage
416
+ },
417
+ session_id: sessionId
418
+ });
419
+ const taskStarted = () => {
420
+ out({
421
+ type: 'system',
422
+ subtype: 'task_started',
423
+ task_id: 'task-delegate',
424
+ tool_use_id: 'toolu_delegate',
425
+ description,
426
+ subagent_type: 'general-purpose',
427
+ is_backgrounded: false,
428
+ spawn_depth: 1,
429
+ task_type: 'local_agent',
430
+ prompt: `Do this: ${description}`,
431
+ session_id: sessionId
432
+ });
433
+ };
434
+ if (!late) {
435
+ taskStarted();
436
+ }
437
+ subagentWork('toolu_delegate', report);
438
+ if (late) {
439
+ taskStarted();
440
+ }
441
+ out({
442
+ type: 'user',
443
+ message: {
444
+ role: 'user',
445
+ content: [{ type: 'tool_result', tool_use_id: 'toolu_delegate', content: `${report}${agentFooter('agent_fake')}`, is_error: false }]
446
+ },
447
+ session_id: sessionId
448
+ });
449
+ assistantText('summarized');
450
+ result();
451
+ return;
452
+ }
453
+ /*
454
+ * A background agent that opens a foreground one of its own, in the order Claude Code 2.1.282 streams it:
455
+ * the grandchild's call and result come as frames of its parent, its own steps not at all, and its
456
+ * `task_started` says how deep it runs and nothing of whose it is.
457
+ */
458
+ if (text.startsWith('nested:')) {
459
+ const summary = text.slice(7).trim() || 'done';
460
+ const task = (frame) => {
461
+ out({ type: 'system', session_id: sessionId, ...frame });
462
+ };
463
+ const underMiddle = (message, type) => {
464
+ out({ type, message, parent_tool_use_id: 'toolu_middle', session_id: sessionId });
465
+ };
466
+ out({
467
+ type: 'assistant',
468
+ message: {
469
+ id: `msg_${nonce}_${++messageCounter}`,
470
+ model,
471
+ role: 'assistant',
472
+ content: [
473
+ {
474
+ type: 'tool_use',
475
+ id: 'toolu_middle',
476
+ name: 'Agent',
477
+ input: { description: 'middle agent', subagent_type: 'general-purpose', prompt: summary, run_in_background: true }
478
+ }
479
+ ],
480
+ usage
481
+ },
482
+ session_id: sessionId
483
+ });
484
+ task({
485
+ subtype: 'task_started',
486
+ task_id: 'a-middle',
487
+ tool_use_id: 'toolu_middle',
488
+ description: 'middle agent',
489
+ subagent_type: 'general-purpose',
490
+ is_backgrounded: true,
491
+ spawn_depth: 1,
492
+ task_type: 'local_agent',
493
+ prompt: summary
494
+ });
495
+ out({
496
+ type: 'user',
497
+ message: {
498
+ role: 'user',
499
+ content: [{ type: 'tool_result', tool_use_id: 'toolu_middle', content: 'Async agent launched successfully.', is_error: false }]
500
+ },
501
+ session_id: sessionId
502
+ });
503
+ assistantText('sent');
504
+ result();
505
+ io.later(() => {
506
+ underMiddle({
507
+ id: `msg_${nonce}_${++messageCounter}`,
508
+ model,
509
+ role: 'assistant',
510
+ content: [{ type: 'tool_use', id: 'toolu_leaf', name: 'Agent', input: { description: 'leaf agent', prompt: 'echo PONG' } }],
511
+ usage
512
+ }, 'assistant');
513
+ task({
514
+ subtype: 'task_started',
515
+ task_id: 'a-leaf',
516
+ tool_use_id: 'toolu_leaf',
517
+ description: 'leaf agent',
518
+ subagent_type: 'general-purpose',
519
+ is_backgrounded: false,
520
+ spawn_depth: 2,
521
+ task_type: 'local_agent',
522
+ prompt: 'echo PONG'
523
+ });
524
+ task({ subtype: 'task_updated', task_id: 'a-leaf', patch: { status: 'completed', end_time: 0 } });
525
+ task({ subtype: 'task_notification', task_id: 'a-leaf', tool_use_id: 'toolu_leaf', status: 'completed', output_file: '', summary: 'PONG' });
526
+ underMiddle({ role: 'user', content: [{ type: 'tool_result', tool_use_id: 'toolu_leaf', content: `PONG${agentFooter('a-leaf')}`, is_error: false }] }, 'user');
527
+ underMiddle({ id: `msg_${nonce}_${++messageCounter}`, model, role: 'assistant', content: [{ type: 'text', text: `the leaf said PONG` }], usage }, 'assistant');
528
+ task({ subtype: 'task_updated', task_id: 'a-middle', patch: { status: 'completed', end_time: 0 } });
529
+ task({
530
+ subtype: 'task_notification',
531
+ task_id: 'a-middle',
532
+ tool_use_id: 'toolu_middle',
533
+ status: 'completed',
534
+ output_file: '',
535
+ summary,
536
+ usage: { total_tokens: 1500, tool_uses: 1, duration_ms: 250 }
537
+ });
538
+ out({ type: 'system', subtype: 'init', session_id: sessionId, model, cwd: io.cwd, tools: ['Bash'], slash_commands: [], argv: args });
539
+ assistantText(`done: ${summary}`);
540
+ result();
541
+ });
542
+ return;
543
+ }
544
+ if (text.startsWith('background:')) {
545
+ const summary = text.slice(11).trim() || 'done';
546
+ const id = `msg_${nonce}_${++messageCounter}`;
547
+ out({
548
+ type: 'assistant',
549
+ message: {
550
+ id,
551
+ model,
552
+ role: 'assistant',
553
+ content: [
554
+ { type: 'tool_use', id: 'toolu_agent', name: 'Agent', input: { description: summary, prompt: summary, run_in_background: true } }
555
+ ],
556
+ usage
557
+ },
558
+ session_id: sessionId
559
+ });
560
+ out({
561
+ type: 'system',
562
+ subtype: 'task_started',
563
+ task_id: 'task-agent',
564
+ tool_use_id: 'toolu_agent',
565
+ description: summary,
566
+ subagent_type: 'general-purpose',
567
+ is_backgrounded: true,
568
+ task_type: 'local_agent',
569
+ session_id: sessionId
570
+ });
571
+ out({
572
+ type: 'user',
573
+ message: {
574
+ role: 'user',
575
+ content: [{ type: 'tool_result', tool_use_id: 'toolu_agent', content: 'Async agent launched successfully.', is_error: false }]
576
+ },
577
+ session_id: sessionId
578
+ });
579
+ assistantText('I will report back');
580
+ result();
581
+ // What the real CLI does after the turn ended: the subagent works on, and only later come a
582
+ // notification, a fresh init, an assistant message of its own and a result, with no user frame.
583
+ io.later(() => {
584
+ subagentWork('toolu_agent', `the subagent says: ${summary}`);
585
+ io.later(() => {
586
+ out({
587
+ type: 'system',
588
+ subtype: 'task_notification',
589
+ task_id: 'task-agent',
590
+ tool_use_id: 'toolu_agent',
591
+ status: 'completed',
592
+ output_file: '',
593
+ summary,
594
+ usage: { total_tokens: 1500, tool_uses: 2, duration_ms: 250 },
595
+ session_id: sessionId
596
+ });
597
+ out({ type: 'system', subtype: 'init', session_id: sessionId, model, cwd: io.cwd, tools: ['Bash'], slash_commands: [], argv: args });
598
+ assistantText(`the subagent says: ${summary}`);
599
+ result();
600
+ });
601
+ });
602
+ return;
603
+ }
604
+ /*
605
+ * A background subagent that sends a command to the background and answers at once, the way Claude
606
+ * Code 2.1.282 runs one: its notification opens a turn with that answer while the command runs on.
607
+ * The command's end takes the subagent up again, which notifies once more, and only the turn that
608
+ * opens then has what the command came to.
609
+ */
610
+ if (text.startsWith('background command:')) {
611
+ // The first line only: a task brief follows it.
612
+ const command = text.split('\n')[0].slice(19).trim() || 'sleep 30';
613
+ const agentUse = 'toolu_cmd_agent';
614
+ const bashUse = 'toolu_cmd_bash';
615
+ const subagent = (content) => {
616
+ out({
617
+ type: 'assistant',
618
+ message: { id: `msg_${nonce}_${++messageCounter}`, model, role: 'assistant', content, usage },
619
+ parent_tool_use_id: agentUse,
620
+ session_id: sessionId
621
+ });
622
+ };
623
+ const subagentResult = (toolUseId, content) => {
624
+ out({
625
+ type: 'user',
626
+ message: { role: 'user', content: [{ type: 'tool_result', tool_use_id: toolUseId, content, is_error: false }] },
627
+ parent_tool_use_id: agentUse,
628
+ session_id: sessionId
629
+ });
630
+ };
631
+ const agentNotified = (summary) => {
632
+ out({
633
+ type: 'system',
634
+ subtype: 'task_notification',
635
+ task_id: 'task-cmd-agent',
636
+ status: 'completed',
637
+ output_file: '',
638
+ summary,
639
+ usage: { total_tokens: 1500, tool_uses: 1, duration_ms: 250 },
640
+ session_id: sessionId
641
+ });
642
+ out({ type: 'system', subtype: 'init', session_id: sessionId, model, cwd: io.cwd, tools: ['Bash'], slash_commands: [], argv: args });
643
+ };
644
+ out({
645
+ type: 'assistant',
646
+ message: {
647
+ id: `msg_${nonce}_${++messageCounter}`,
648
+ model,
649
+ role: 'assistant',
650
+ content: [{ type: 'tool_use', id: agentUse, name: 'Agent', input: { description: 'Run it', prompt: command, run_in_background: true } }],
651
+ usage
652
+ },
653
+ session_id: sessionId
654
+ });
655
+ out({
656
+ type: 'system',
657
+ subtype: 'task_started',
658
+ task_id: 'task-cmd-agent',
659
+ tool_use_id: agentUse,
660
+ description: 'Run it',
661
+ subagent_type: 'general-purpose',
662
+ is_backgrounded: true,
663
+ task_type: 'local_agent',
664
+ session_id: sessionId
665
+ });
666
+ out({
667
+ type: 'user',
668
+ message: {
669
+ role: 'user',
670
+ content: [{ type: 'tool_result', tool_use_id: agentUse, content: 'Async agent launched successfully.', is_error: false }]
671
+ },
672
+ session_id: sessionId
673
+ });
674
+ assistantText('started');
675
+ result();
676
+ io.later(() => {
677
+ subagent([{ type: 'tool_use', id: bashUse, name: 'Bash', input: { command, run_in_background: true } }]);
678
+ out({
679
+ type: 'system',
680
+ subtype: 'task_started',
681
+ task_id: 'b-cmd',
682
+ tool_use_id: bashUse,
683
+ description: command,
684
+ task_type: 'local_bash',
685
+ is_backgrounded: true,
686
+ session_id: sessionId
687
+ });
688
+ subagentResult(bashUse, 'Command running in background with ID: b-cmd.');
689
+ subagent([{ type: 'text', text: 'Command started in the background.' }]);
690
+ agentNotified('Agent "Run it" finished');
691
+ assistantText('the subagent says: Command started in the background.');
692
+ result();
693
+ io.later(() => {
694
+ out({ type: 'system', subtype: 'task_updated', task_id: 'b-cmd', patch: { status: 'completed', end_time: 30000 }, session_id: sessionId });
695
+ out({
696
+ type: 'system',
697
+ subtype: 'task_notification',
698
+ task_id: 'b-cmd',
699
+ tool_use_id: bashUse,
700
+ status: 'completed',
701
+ output_file: '',
702
+ summary: `Background command "${command}" completed (exit code 0)`,
703
+ session_id: sessionId
704
+ });
705
+ subagent([{ type: 'tool_use', id: 'toolu_cmd_read', name: 'Read', input: { file_path: 'b-cmd.output' } }]);
706
+ subagentResult('toolu_cmd_read', 'DONE');
707
+ subagent([{ type: 'text', text: 'The command printed DONE.' }]);
708
+ agentNotified('Agent "Run it" finished');
709
+ assistantText('the subagent reports: DONE');
710
+ result();
711
+ });
712
+ });
713
+ return;
714
+ }
715
+ /*
716
+ * A two-phase workflow, framed the way Claude Code 2.1.282 streams one: the call answers at launch,
717
+ * and its agents send no frames of their own; only some progress frames carry a report of the whole run.
718
+ */
719
+ if (text.startsWith('workflow:')) {
720
+ const summary = text.slice(9).trim() || 'Write a file, then read it';
721
+ const task = (frame) => {
722
+ out({ type: 'system', task_id: 'wf-task', session_id: sessionId, ...frame });
723
+ };
724
+ const phases = [
725
+ { type: 'workflow_phase', index: 1, title: 'Write' },
726
+ { type: 'workflow_phase', index: 2, title: 'Read' }
727
+ ];
728
+ const writer = { type: 'workflow_agent', index: 1, label: 'write-file', phaseIndex: 1, phaseTitle: 'Write', agentId: 'a-writer', startedAt: 1000 };
729
+ const reader = { type: 'workflow_agent', index: 2, label: 'read-file', phaseIndex: 2, phaseTitle: 'Read', agentId: 'a-reader', startedAt: 6000 };
730
+ const writerDone = { ...writer, state: 'done', lastToolName: 'Write', tokens: 16264, toolCalls: 1, durationMs: 5000 };
731
+ const progress = (description, report) => {
732
+ task({
733
+ subtype: 'task_progress',
734
+ tool_use_id: 'toolu_wf',
735
+ description,
736
+ usage: { total_tokens: 16264, tool_uses: 1, duration_ms: 5000 },
737
+ last_tool_name: description.split(': ')[1],
738
+ summary,
739
+ ...(report === undefined ? {} : { workflow_progress: report })
740
+ });
741
+ };
742
+ out({
743
+ type: 'assistant',
744
+ message: {
745
+ id: `msg_${nonce}_${++messageCounter}`,
746
+ model,
747
+ role: 'assistant',
748
+ content: [{ type: 'tool_use', id: 'toolu_wf', name: 'Workflow', input: { script: 'export const meta = { name: "write-and-read" }' } }],
749
+ usage
750
+ },
751
+ session_id: sessionId
752
+ });
753
+ out({
754
+ type: 'system',
755
+ subtype: 'background_tasks_changed',
756
+ tasks: [{ task_id: 'wf-task', task_type: 'local_workflow', description: summary }],
757
+ session_id: sessionId
758
+ });
759
+ task({ subtype: 'task_started', tool_use_id: 'toolu_wf', description: summary, task_type: 'local_workflow', workflow_name: 'write-and-read' });
760
+ out({
761
+ type: 'user',
762
+ message: {
763
+ role: 'user',
764
+ content: [
765
+ {
766
+ type: 'tool_result',
767
+ tool_use_id: 'toolu_wf',
768
+ content: `Workflow launched in background. Task ID: wf-task\nSummary: ${summary}\nRun ID: wf_fake`,
769
+ is_error: false
770
+ }
771
+ ]
772
+ },
773
+ session_id: sessionId
774
+ });
775
+ progress('Write: write-file', [...phases, { ...writer, state: 'start' }]);
776
+ assistantText('the workflow runs');
777
+ result();
778
+ io.later(() => {
779
+ progress('Write: write-file');
780
+ progress('Read: read-file', [...phases, writerDone, { ...reader, state: 'start' }]);
781
+ progress('Read: read-file', [...phases, writerDone, { ...reader, state: 'done', lastToolName: 'Read', durationMs: 4000 }]);
782
+ out({ type: 'system', subtype: 'background_tasks_changed', tasks: [], session_id: sessionId });
783
+ task({ subtype: 'task_updated', patch: { status: 'completed', end_time: 10000 } });
784
+ task({
785
+ subtype: 'task_notification',
786
+ tool_use_id: 'toolu_wf',
787
+ status: 'completed',
788
+ output_file: '',
789
+ summary: `Dynamic workflow "${summary}" completed`,
790
+ usage: { total_tokens: 32403, tool_uses: 2, duration_ms: 9000 }
791
+ });
792
+ out({ type: 'system', subtype: 'init', session_id: sessionId, model, cwd: io.cwd, tools: ['Bash'], slash_commands: [], argv: args });
793
+ assistantText('the workflow is done');
794
+ result();
795
+ });
796
+ return;
797
+ }
798
+ if (text.startsWith('run:')) {
799
+ const command = text.slice(4).trim();
800
+ const id = `msg_${nonce}_${++messageCounter}`;
801
+ out({
802
+ type: 'assistant',
803
+ message: { id, model, role: 'assistant', content: [{ type: 'tool_use', id: 'toolu_run', name: 'Bash', input: { command } }], usage },
804
+ session_id: sessionId
805
+ });
806
+ // The frames the real CLI emits around a long Bash call, in its order and shape.
807
+ out({
808
+ type: 'system',
809
+ subtype: 'task_started',
810
+ task_id: 'task-1',
811
+ tool_use_id: 'toolu_run',
812
+ description: `Run ${command}`,
813
+ is_backgrounded: false,
814
+ task_type: 'local_bash',
815
+ session_id: sessionId
816
+ });
817
+ out({
818
+ type: 'tool_progress',
819
+ tool_use_id: 'toolu_run',
820
+ tool_name: 'Bash',
821
+ parent_tool_use_id: null,
822
+ elapsed_time_seconds: 30,
823
+ task_id: 'task-1',
824
+ session_id: sessionId
825
+ });
826
+ out({
827
+ type: 'system',
828
+ subtype: 'task_notification',
829
+ task_id: 'task-1',
830
+ tool_use_id: 'toolu_run',
831
+ status: 'completed',
832
+ output_file: '',
833
+ summary: `Run ${command}`,
834
+ session_id: sessionId
835
+ });
836
+ out({
837
+ type: 'user',
838
+ message: { role: 'user', content: [{ type: 'tool_result', tool_use_id: 'toolu_run', content: `ran: ${command}`, is_error: false }] },
839
+ session_id: sessionId
840
+ });
841
+ assistantText('done');
842
+ result();
843
+ return;
844
+ }
845
+ if (text.startsWith('tool:')) {
846
+ const command = text.slice(5).trim();
847
+ const id = `msg_${nonce}_${++messageCounter}`;
848
+ out({
849
+ type: 'assistant',
850
+ message: { id, model, role: 'assistant', content: [{ type: 'tool_use', id: 'toolu_1', name: 'Bash', input: { command } }], usage },
851
+ session_id: sessionId
852
+ });
853
+ pendingApproval = { command };
854
+ out({
855
+ type: 'control_request',
856
+ request_id: 'req-1',
857
+ request: {
858
+ subtype: 'can_use_tool',
859
+ tool_name: 'Bash',
860
+ input: { command },
861
+ tool_use_id: 'toolu_1',
862
+ description: 'Run a command',
863
+ permission_suggestions: [
864
+ { type: 'addRules', rules: [{ toolName: 'Bash', ruleContent: `${command}:*` }], behavior: 'allow', destination: 'session' }
865
+ ]
866
+ }
867
+ });
868
+ return;
869
+ }
870
+ if (text === 'system?') {
871
+ const at = args.indexOf('--append-system-prompt');
872
+ assistantText(at >= 0 ? args[at + 1] : 'none');
873
+ result();
874
+ return;
875
+ }
876
+ assistantText(`echo: ${text}`);
877
+ result();
878
+ };
879
+ const handleControlResponse = (response) => {
880
+ const inner = response.response;
881
+ if (backgroundAsk !== null && response.request_id === backgroundAsk.requestId) {
882
+ const { command } = backgroundAsk;
883
+ backgroundAsk = null;
884
+ finishBackgroundAgent(command, inner?.behavior === 'allow', inner?.message);
885
+ return;
886
+ }
887
+ if (pendingQuestion) {
888
+ const answer = inner?.updatedInput?.answers?.[pendingQuestion] ?? 'no answer';
889
+ pendingQuestion = null;
890
+ assistantText(`you chose ${answer}`);
891
+ result();
892
+ return;
893
+ }
894
+ const approval = pendingApproval;
895
+ pendingApproval = null;
896
+ if (!approval) {
897
+ return;
898
+ }
899
+ if (inner?.behavior === 'allow') {
900
+ const always = Array.isArray(inner.updatedPermissions) && inner.updatedPermissions.length > 0;
901
+ out({
902
+ type: 'user',
903
+ message: { role: 'user', content: [{ type: 'tool_result', tool_use_id: 'toolu_1', content: `ran: ${approval.command}`, is_error: false }] },
904
+ session_id: sessionId
905
+ });
906
+ assistantText(always ? 'done, remembered' : 'done');
907
+ }
908
+ else {
909
+ out({
910
+ type: 'user',
911
+ message: { role: 'user', content: [{ type: 'tool_result', tool_use_id: 'toolu_1', content: 'denied by user', is_error: true }] },
912
+ session_id: sessionId
913
+ });
914
+ assistantText('denied');
915
+ }
916
+ result();
917
+ };
918
+ out({ type: 'system', subtype: 'init', session_id: sessionId, model, cwd: io.cwd, tools: ['Bash'], slash_commands: ['compact', 'review'], argv: args });
919
+ return {
920
+ onLine: (line) => {
921
+ const frame = JSON.parse(line);
922
+ if (frame.type === 'user') {
923
+ if (args.includes('--replay-user-messages') && typeof frame.uuid === 'string') {
924
+ out(frame);
925
+ }
926
+ const message = frame.message;
927
+ handleUser(message.content[0]?.text ?? '');
928
+ }
929
+ else if (frame.type === 'control_response') {
930
+ handleControlResponse(frame.response);
931
+ }
932
+ else if (frame.type === 'control_request') {
933
+ const request = frame.request;
934
+ out({ type: 'control_response', response: { subtype: 'success', request_id: frame.request_id, response: {} } });
935
+ if (request.subtype === 'interrupt' && slow) {
936
+ slow = false;
937
+ assistantText('[interrupted]');
938
+ result();
939
+ }
940
+ }
941
+ }
942
+ };
943
+ }
944
+ /*
945
+ * The fake, except that resuming one of `sessionIds` stops at once. `missing` is what Claude Code 2.1.281
946
+ * does for a session it has no transcript of: one error result, a line on stderr and exit 1, before it
947
+ * reads any input. `exit` is a CLI that did resume and then went on the first prompt.
948
+ */
949
+ export function claudeStoppingOnResume(sessionIds, how) {
950
+ return (io) => {
951
+ const resumeAt = io.argv.indexOf('--resume');
952
+ const sessionId = resumeAt >= 0 ? io.argv[resumeAt + 1] : undefined;
953
+ if (sessionId === undefined || !sessionIds.has(sessionId)) {
954
+ return fakeClaude(io);
955
+ }
956
+ if (how === 'exit') {
957
+ fakeClaude(io);
958
+ return { onLine: () => io.exit(1) };
959
+ }
960
+ const message = `No conversation found with session ID: ${sessionId}`;
961
+ io.out({
962
+ type: 'result',
963
+ subtype: 'error_during_execution',
964
+ duration_ms: 0,
965
+ is_error: true,
966
+ num_turns: 0,
967
+ stop_reason: null,
968
+ session_id: sessionId,
969
+ total_cost_usd: 0,
970
+ usage: { input_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0, output_tokens: 0 },
971
+ modelUsage: {},
972
+ permission_denials: [],
973
+ errors: [message]
974
+ });
975
+ io.err(`${message}\n`);
976
+ io.exit(1);
977
+ return { onLine: () => undefined };
978
+ };
979
+ }
980
+ if (import.meta.main) {
981
+ await runOverStdio(fakeClaude);
982
+ }