@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,642 @@
1
+ import { readClaudeEvent } from '../usage/limits/normalize.js';
2
+ import { claudeRuntimeMode } from '../providers/claude.js';
3
+ function isRecord(value) {
4
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
5
+ }
6
+ function str(value) {
7
+ return typeof value === 'string' ? value : null;
8
+ }
9
+ function num(value) {
10
+ return typeof value === 'number' && Number.isFinite(value) ? value : 0;
11
+ }
12
+ // A tool result is a string or a list of content blocks; only the text is worth showing.
13
+ function resultText(content) {
14
+ if (typeof content === 'string') {
15
+ return content;
16
+ }
17
+ if (!Array.isArray(content)) {
18
+ return '';
19
+ }
20
+ return content
21
+ .map((block) => (isRecord(block) && block.type === 'text' ? str(block.text) : null))
22
+ .filter((text) => text !== null)
23
+ .join('\n');
24
+ }
25
+ // What a subagent spent, as `task_progress` and `task_notification` report it.
26
+ function taskUsage(usage) {
27
+ return isRecord(usage) ? { totalTokens: num(usage.total_tokens), toolUses: num(usage.tool_uses), durationMs: num(usage.duration_ms) } : null;
28
+ }
29
+ function contextTokens(usage) {
30
+ return isRecord(usage) ? num(usage.input_tokens) + num(usage.cache_creation_input_tokens) + num(usage.cache_read_input_tokens) : 0;
31
+ }
32
+ // The kinds of task Claude Code keeps beside its turns that are a command or a monitor; a subagent has a row of its own.
33
+ const BACKGROUND_TASK_TYPES = new Set(['local_bash', 'monitor_mcp', 'monitor_ws']);
34
+ const ENDED_TASK_STATUSES = new Set(['completed', 'failed', 'killed', 'stopped']);
35
+ // How Claude Code (2.1.273 through 2.1.282) answers a Workflow call, with the id of the task the workflow runs as.
36
+ const WORKFLOW_LAUNCHED = /^Workflow launched in background\. Task ID: (\S+)/;
37
+ const WORKFLOW_AGENT_STATUS = { done: 'done', error: 'failed' };
38
+ function intOrNull(value) {
39
+ return typeof value === 'number' && Number.isInteger(value) ? value : null;
40
+ }
41
+ /*
42
+ * A workflow's `workflow_progress` (Claude Code 2.1.282): every phase the script announced and every
43
+ * agent it started so far, one entry per index, beside log lines the thread does not show. An agent
44
+ * reads `start` while it waits and while it works, `done` or `error` once it stopped.
45
+ */
46
+ function parseWorkflowProgress(entries) {
47
+ const phases = new Map();
48
+ const agents = new Map();
49
+ for (const entry of entries) {
50
+ if (!isRecord(entry)) {
51
+ continue;
52
+ }
53
+ const index = intOrNull(entry.index);
54
+ if (index === null) {
55
+ continue;
56
+ }
57
+ if (entry.type === 'workflow_phase') {
58
+ phases.set(index, { index, title: str(entry.title) ?? '' });
59
+ }
60
+ else if (entry.type === 'workflow_agent') {
61
+ const durationMs = intOrNull(entry.durationMs);
62
+ agents.set(index, {
63
+ index,
64
+ label: str(entry.label) ?? '',
65
+ phaseIndex: intOrNull(entry.phaseIndex),
66
+ agentId: str(entry.agentId),
67
+ status: WORKFLOW_AGENT_STATUS[str(entry.state) ?? ''] ?? 'running',
68
+ startedAt: typeof entry.startedAt === 'number' ? entry.startedAt : null,
69
+ durationMs: durationMs !== null && durationMs >= 0 ? durationMs : null,
70
+ lastTool: str(entry.lastToolName)
71
+ });
72
+ }
73
+ }
74
+ const byIndex = (a, b) => a.index - b.index;
75
+ return { phases: [...phases.values()].sort(byIndex), agents: [...agents.values()].sort(byIndex) };
76
+ }
77
+ // The CLI's AskUserQuestion input, as far as the person needs to see it.
78
+ function parseQuestions(input) {
79
+ if (!isRecord(input) || !Array.isArray(input.questions)) {
80
+ return [];
81
+ }
82
+ return input.questions.filter(isRecord).map((question, index) => ({
83
+ id: String(index),
84
+ header: str(question.header) ?? '',
85
+ question: str(question.question) ?? '',
86
+ choices: Array.isArray(question.options)
87
+ ? question.options.filter(isRecord).map((option) => ({ label: str(option.label) ?? '', description: str(option.description) ?? '' }))
88
+ : [],
89
+ multiSelect: question.multiSelect === true
90
+ }));
91
+ }
92
+ /*
93
+ * Claude Code's stream-json frames, in and out. Text is keyed by message id plus the ordinal of the
94
+ * text block inside that message, because the streaming events and the final `assistant` frames
95
+ * number their blocks differently (the final frames arrive one block at a time). One instance
96
+ * belongs to one process, so a resumed CLI that numbers its messages from the start gets its own.
97
+ */
98
+ export class ClaudeProtocol {
99
+ promptId = null;
100
+ pending = new Map();
101
+ streamMessageId = null;
102
+ streamTextCount = 0;
103
+ streamThinkingCount = 0;
104
+ // Stream block index to the ref of the text block collecting its deltas.
105
+ streamBlocks = new Map();
106
+ frameTextCount = new Map();
107
+ frameThinkingCount = new Map();
108
+ // Commands started in the foreground, which the CLI may still send to the background, and what runs there now.
109
+ foregroundShells = new Map();
110
+ backgroundTasks = new Set();
111
+ // Workflows still running, by task id, to the Workflow call that launched each.
112
+ workflows = new Map();
113
+ // The uuid of the last main-chain assistant frame of the turn, which is the line of the transcript the turn ends on.
114
+ lastUuid = null;
115
+ // The model each subagent last answered with, so a frame only reports one it had not named yet.
116
+ subagentModels = new Map();
117
+ // Only main-chain errors classify the turn; each plan window can refuse it independently.
118
+ apiError = null;
119
+ refusedWindows = new Map();
120
+ beginPrompt(promptId) {
121
+ this.promptId = promptId;
122
+ this.apiError = null;
123
+ this.refusedWindows.clear();
124
+ this.lastUuid = null;
125
+ }
126
+ handle(frame) {
127
+ const events = [];
128
+ if (!isRecord(frame)) {
129
+ return events;
130
+ }
131
+ switch (frame.type) {
132
+ case 'system':
133
+ this.handleSystem(frame, events);
134
+ break;
135
+ case 'stream_event':
136
+ this.handleStreamEvent(frame, events);
137
+ break;
138
+ case 'assistant':
139
+ this.handleAssistant(frame, events);
140
+ break;
141
+ case 'user':
142
+ this.handleUser(frame, events);
143
+ break;
144
+ case 'result':
145
+ this.handleResult(frame, events);
146
+ break;
147
+ case 'control_request':
148
+ this.handleControlRequest(frame, events);
149
+ break;
150
+ case 'control_cancel_request': {
151
+ const requestId = str(frame.request_id);
152
+ if (requestId) {
153
+ this.pending.delete(requestId);
154
+ events.push({ type: 'request.withdrawn', requestId });
155
+ }
156
+ break;
157
+ }
158
+ case 'tool_progress':
159
+ this.handleToolProgress(frame, events);
160
+ break;
161
+ case 'rate_limit_event': {
162
+ this.noteRefusal(frame.rate_limit_info);
163
+ // The plan's own numbers, streamed while a turn runs; the usage monitor keeps them.
164
+ const update = readClaudeEvent(frame.rate_limit_info);
165
+ if (update !== null) {
166
+ events.push({ type: 'limits', update });
167
+ }
168
+ break;
169
+ }
170
+ default:
171
+ break;
172
+ }
173
+ return events;
174
+ }
175
+ /* The `control_response` that answers a permission request, or null when nothing waits under that id. */
176
+ approvalResponse(requestId, decision, message) {
177
+ const pending = this.pending.get(requestId);
178
+ if (pending?.type !== 'approval') {
179
+ return null;
180
+ }
181
+ this.pending.delete(requestId);
182
+ const toolUseID = pending.toolUseId ?? undefined;
183
+ const response = decision === 'deny'
184
+ ? { behavior: 'deny', message: message?.trim() || 'The user declined this action', toolUseID }
185
+ : {
186
+ behavior: 'allow',
187
+ updatedInput: pending.input,
188
+ toolUseID,
189
+ // The CLI's own suggestion of a rule; sending it back is what makes "always" stick.
190
+ ...(decision === 'allow-always' && pending.suggestions.length > 0 ? { updatedPermissions: pending.suggestions } : {})
191
+ };
192
+ return { type: 'control_response', response: { subtype: 'success', request_id: requestId, response } };
193
+ }
194
+ /* The `control_response` that answers an AskUserQuestion, or null when nothing waits under that id. */
195
+ questionResponse(requestId, answers) {
196
+ const pending = this.pending.get(requestId);
197
+ if (pending?.type !== 'question') {
198
+ return null;
199
+ }
200
+ this.pending.delete(requestId);
201
+ // The CLI keys answers by the question text, the client by the question's id.
202
+ const byText = {};
203
+ for (const question of parseQuestions(pending.input)) {
204
+ const given = answers[question.id];
205
+ if (given !== undefined) {
206
+ byText[question.question] = given;
207
+ }
208
+ }
209
+ const input = isRecord(pending.input) ? pending.input : {};
210
+ return {
211
+ type: 'control_response',
212
+ response: {
213
+ subtype: 'success',
214
+ request_id: requestId,
215
+ response: { behavior: 'allow', updatedInput: { ...input, answers: byText }, toolUseID: pending.toolUseId ?? undefined }
216
+ }
217
+ };
218
+ }
219
+ /* The `control_response` that turns down an approval or a question the CLI still holds, or null when nothing waits under that id. */
220
+ declineResponse(requestId, message) {
221
+ const pending = this.pending.get(requestId);
222
+ if (pending === undefined) {
223
+ return null;
224
+ }
225
+ this.pending.delete(requestId);
226
+ return {
227
+ type: 'control_response',
228
+ response: { subtype: 'success', request_id: requestId, response: { behavior: 'deny', message, toolUseID: pending.toolUseId ?? undefined } }
229
+ };
230
+ }
231
+ forgetPending() {
232
+ this.pending.clear();
233
+ }
234
+ handleSystem(frame, events) {
235
+ if (frame.subtype === 'init') {
236
+ const commands = Array.isArray(frame.slash_commands)
237
+ ? frame.slash_commands.filter((command) => typeof command === 'string')
238
+ : [];
239
+ const skills = Array.isArray(frame.skills) ? frame.skills.filter((skill) => typeof skill === 'string') : [];
240
+ const permissionMode = str(frame.permissionMode);
241
+ const effectiveRuntimeMode = permissionMode === null ? undefined : claudeRuntimeMode(permissionMode);
242
+ events.push({
243
+ type: 'session',
244
+ agentSessionId: str(frame.session_id),
245
+ model: str(frame.model),
246
+ slashCommands: commands,
247
+ skills,
248
+ ...(permissionMode === null ? {} : { permissionMode }),
249
+ ...(effectiveRuntimeMode === undefined ? {} : { effectiveRuntimeMode })
250
+ });
251
+ }
252
+ else if (frame.subtype === 'status' && typeof frame.permissionMode === 'string') {
253
+ const effectiveRuntimeMode = claudeRuntimeMode(frame.permissionMode);
254
+ events.push({ type: 'permissions', permissionMode: frame.permissionMode, ...(effectiveRuntimeMode === undefined ? {} : { effectiveRuntimeMode }) });
255
+ }
256
+ else if (frame.subtype === 'task_started') {
257
+ this.handleTaskStarted(frame, events);
258
+ }
259
+ else if (frame.subtype === 'task_updated') {
260
+ this.handleTaskUpdated(frame, events);
261
+ }
262
+ else if (frame.subtype === 'task_progress') {
263
+ const ref = str(frame.tool_use_id);
264
+ if (!ref) {
265
+ return;
266
+ }
267
+ // Only some of a workflow's progress frames carry it; the rest only count what it spent.
268
+ if (Array.isArray(frame.workflow_progress)) {
269
+ events.push({
270
+ type: 'workflow.progress',
271
+ ref,
272
+ workflow: {
273
+ name: null,
274
+ ...parseWorkflowProgress(frame.workflow_progress),
275
+ ...(typeof frame.task_id === 'string' ? { taskId: frame.task_id } : {})
276
+ }
277
+ });
278
+ }
279
+ // Only a subagent reports what it spent; a task without that is a command with a description.
280
+ if (frame.task_type === 'local_agent' || str(frame.subagent_type)) {
281
+ events.push({
282
+ type: 'task.progress',
283
+ ref,
284
+ taskId: str(frame.task_id),
285
+ summary: str(frame.description),
286
+ lastTool: str(frame.last_tool_name),
287
+ usage: taskUsage(frame.usage)
288
+ });
289
+ }
290
+ else {
291
+ const description = str(frame.description);
292
+ if (description) {
293
+ events.push({ type: 'tool.progress', ref, startedAt: null, description });
294
+ }
295
+ }
296
+ }
297
+ else if (frame.subtype === 'task_notification') {
298
+ const ref = str(frame.tool_use_id);
299
+ if (ref) {
300
+ this.subagentModels.delete(ref);
301
+ }
302
+ const taskId = str(frame.task_id);
303
+ if (taskId) {
304
+ this.endBackground(taskId, events);
305
+ this.workflows.delete(taskId);
306
+ }
307
+ // The CLI wakes the main agent itself when a background task settles; this frame is the only
308
+ // thing that says what it was about, and it arrives before the turn nobody asked for.
309
+ events.push({
310
+ type: 'task.done',
311
+ ref: str(frame.tool_use_id),
312
+ taskId,
313
+ summary: str(frame.summary),
314
+ ok: str(frame.status) === 'completed',
315
+ usage: taskUsage(frame.usage),
316
+ outputFile: str(frame.output_file) || null
317
+ });
318
+ }
319
+ else if (frame.subtype === 'compact_boundary') {
320
+ const meta = isRecord(frame.compact_metadata) ? frame.compact_metadata : {};
321
+ const preTokens = num(meta.pre_tokens);
322
+ events.push({ type: 'compaction', preTokens: preTokens > 0 ? preTokens : null });
323
+ }
324
+ }
325
+ handleTaskStarted(frame, events) {
326
+ const ref = str(frame.tool_use_id);
327
+ if (frame.task_type === 'local_agent') {
328
+ if (ref) {
329
+ events.push({
330
+ type: 'task.started',
331
+ ref,
332
+ taskId: str(frame.task_id),
333
+ description: str(frame.description),
334
+ subagentType: str(frame.subagent_type),
335
+ prompt: str(frame.prompt),
336
+ background: frame.is_backgrounded === true,
337
+ ...(typeof frame.spawn_depth === 'number' ? { depth: frame.spawn_depth } : {})
338
+ });
339
+ }
340
+ return;
341
+ }
342
+ const taskId = str(frame.task_id);
343
+ const description = str(frame.description);
344
+ if (taskId && ref && frame.task_type === 'local_workflow') {
345
+ this.workflows.set(taskId, ref);
346
+ events.push({ type: 'workflow.progress', ref, workflow: { name: str(frame.workflow_name), taskId, phases: [], agents: [] } });
347
+ }
348
+ if (taskId && typeof frame.task_type === 'string' && BACKGROUND_TASK_TYPES.has(frame.task_type) && frame.ambient !== true) {
349
+ if (frame.is_backgrounded === false) {
350
+ this.foregroundShells.set(taskId, { ref, description });
351
+ }
352
+ else {
353
+ this.startBackground(taskId, ref, frame.task_type !== 'local_bash', description, events);
354
+ }
355
+ }
356
+ // A Bash call became a task; its description is the CLI's own words for the work.
357
+ if (ref && description) {
358
+ events.push({ type: 'tool.progress', ref, startedAt: null, description });
359
+ }
360
+ }
361
+ /* A command the person or the CLI sent to the background halfway, or a task that ended. */
362
+ handleTaskUpdated(frame, events) {
363
+ const taskId = str(frame.task_id);
364
+ const patch = isRecord(frame.patch) ? frame.patch : {};
365
+ if (!taskId) {
366
+ return;
367
+ }
368
+ const shell = this.foregroundShells.get(taskId);
369
+ if (shell && patch.is_backgrounded === true) {
370
+ this.foregroundShells.delete(taskId);
371
+ this.startBackground(taskId, shell.ref, false, str(patch.description) ?? shell.description, events);
372
+ }
373
+ const status = str(patch.status);
374
+ if (status !== null && ENDED_TASK_STATUSES.has(status)) {
375
+ this.foregroundShells.delete(taskId);
376
+ this.endBackground(taskId, events);
377
+ this.endWorkflow(taskId, null, status === 'completed', events);
378
+ }
379
+ }
380
+ startBackground(taskId, ref, monitor, description, events) {
381
+ if (this.backgroundTasks.has(taskId)) {
382
+ return;
383
+ }
384
+ this.backgroundTasks.add(taskId);
385
+ events.push({ type: 'background.started', taskId, ref, monitor, description });
386
+ }
387
+ endBackground(taskId, events) {
388
+ if (this.backgroundTasks.delete(taskId)) {
389
+ events.push({ type: 'background.ended', taskId });
390
+ }
391
+ }
392
+ /*
393
+ * A workflow that ended, told by either its notification or the task's own status, whichever comes
394
+ * first: the stream of a workflow run was not seen live, and both are what every other task sends.
395
+ */
396
+ endWorkflow(taskId, summary, ok, events) {
397
+ const ref = this.workflows.get(taskId);
398
+ if (ref === undefined) {
399
+ return;
400
+ }
401
+ this.workflows.delete(taskId);
402
+ events.push({ type: 'task.done', ref, taskId, summary, ok });
403
+ }
404
+ /* The control request that ends one task the CLI runs beside its turns. */
405
+ stopTaskRequest(taskId) {
406
+ return { type: 'control_request', request_id: `stop-task-${taskId}-${Date.now()}`, request: { subtype: 'stop_task', task_id: taskId } };
407
+ }
408
+ handleStreamEvent(frame, events) {
409
+ const event = frame.event;
410
+ if (!isRecord(event) || str(frame.parent_tool_use_id)) {
411
+ return;
412
+ }
413
+ if (event.type === 'message_start') {
414
+ const message = isRecord(event.message) ? event.message : {};
415
+ this.streamMessageId = str(message.id);
416
+ this.streamTextCount = 0;
417
+ this.streamThinkingCount = 0;
418
+ this.streamBlocks.clear();
419
+ return;
420
+ }
421
+ const index = num(event.index);
422
+ if (event.type === 'content_block_start' && isRecord(event.content_block) && this.streamMessageId) {
423
+ if (event.content_block.type === 'text') {
424
+ const ref = this.textRef(this.streamMessageId, this.streamTextCount++);
425
+ this.streamBlocks.set(index, ref);
426
+ events.push({ type: 'text.delta', ref, text: str(event.content_block.text) ?? '' });
427
+ return;
428
+ }
429
+ if (event.content_block.type === 'thinking') {
430
+ const ref = this.thinkingRef(this.streamMessageId, this.streamThinkingCount++);
431
+ this.streamBlocks.set(index, ref);
432
+ events.push({ type: 'thinking.delta', ref, text: str(event.content_block.thinking) ?? '' });
433
+ return;
434
+ }
435
+ }
436
+ if (event.type === 'content_block_delta' && isRecord(event.delta)) {
437
+ const ref = this.streamBlocks.get(index);
438
+ const text = str(event.delta.text) ?? str(event.delta.thinking);
439
+ if (!ref || !text) {
440
+ return;
441
+ }
442
+ if (event.delta.type === 'text_delta') {
443
+ events.push({ type: 'text.delta', ref, text });
444
+ }
445
+ else if (event.delta.type === 'thinking_delta') {
446
+ events.push({ type: 'thinking.delta', ref, text });
447
+ }
448
+ }
449
+ }
450
+ handleAssistant(frame, events) {
451
+ const message = isRecord(frame.message) ? frame.message : {};
452
+ const messageId = str(message.id) ?? 'message';
453
+ const parentRef = str(frame.parent_tool_use_id);
454
+ const content = Array.isArray(message.content) ? message.content : [];
455
+ const uuid = str(frame.uuid);
456
+ if (!parentRef && uuid) {
457
+ this.lastUuid = uuid;
458
+ }
459
+ if (!parentRef && typeof frame.error === 'string') {
460
+ this.apiError = frame.error;
461
+ }
462
+ const model = str(message.model);
463
+ // The CLI answers some frames itself and names them `<synthetic>`; no model wrote those.
464
+ if (parentRef && model && model !== '<synthetic>' && this.subagentModels.get(parentRef) !== model) {
465
+ this.subagentModels.set(parentRef, model);
466
+ events.push({ type: 'task.model', ref: parentRef, model });
467
+ }
468
+ for (const block of content) {
469
+ if (!isRecord(block)) {
470
+ continue;
471
+ }
472
+ if (block.type === 'text') {
473
+ const ordinal = this.frameTextCount.get(messageId) ?? 0;
474
+ this.frameTextCount.set(messageId, ordinal + 1);
475
+ // A subagent's text is its own report, not the thread's answer; the parent says whose row it is.
476
+ events.push({ type: 'text.done', ref: this.textRef(messageId, ordinal), text: str(block.text) ?? '', ...(parentRef ? { parentRef } : {}) });
477
+ }
478
+ else if (block.type === 'thinking' && !parentRef) {
479
+ const ordinal = this.frameThinkingCount.get(messageId) ?? 0;
480
+ this.frameThinkingCount.set(messageId, ordinal + 1);
481
+ events.push({ type: 'thinking.done', ref: this.thinkingRef(messageId, ordinal), text: str(block.thinking) ?? '' });
482
+ }
483
+ else if (block.type === 'tool_use') {
484
+ events.push({
485
+ type: 'tool.started',
486
+ ref: str(block.id) ?? `tool-${Date.now()}`,
487
+ name: str(block.name) ?? 'tool',
488
+ input: block.input ?? {},
489
+ parentRef
490
+ });
491
+ }
492
+ }
493
+ const tokens = parentRef ? 0 : contextTokens(message.usage);
494
+ if (tokens > 0) {
495
+ events.push({ type: 'usage', contextTokens: tokens });
496
+ }
497
+ const error = str(frame.error);
498
+ if (error) {
499
+ events.push({ type: 'note', level: 'error', text: `The request failed: ${error.replaceAll('_', ' ')}` });
500
+ }
501
+ }
502
+ /*
503
+ * `tool_progress` carries how long the call has run (once per 30 s for a Bash under the CLI's
504
+ * remote gate, or a heartbeat for a slow MCP tool); the start is kept so the client can count on.
505
+ * That gate is `CLAUDE_CODE_REMOTE` or `CLAUDE_CODE_CONTAINER_ID`, never set here since both change other behavior.
506
+ */
507
+ handleToolProgress(frame, events) {
508
+ const elapsed = frame.elapsed_time_seconds;
509
+ const ref = str(frame.tool_use_id);
510
+ if (!ref || typeof elapsed !== 'number' || !Number.isFinite(elapsed) || elapsed < 0) {
511
+ return;
512
+ }
513
+ events.push({ type: 'tool.progress', ref, startedAt: Date.now() - Math.round(elapsed * 1000), description: null });
514
+ }
515
+ handleUser(frame, events) {
516
+ if (this.promptId !== null && frame.uuid === this.promptId) {
517
+ events.push({ type: 'turn.accepted', promptId: this.promptId });
518
+ }
519
+ const message = isRecord(frame.message) ? frame.message : {};
520
+ const content = Array.isArray(message.content) ? message.content : [];
521
+ for (const block of content) {
522
+ if (!isRecord(block) || block.type !== 'tool_result') {
523
+ continue;
524
+ }
525
+ const ref = str(block.tool_use_id);
526
+ if (ref) {
527
+ this.subagentModels.delete(ref);
528
+ const output = resultText(block.content);
529
+ const workflow = WORKFLOW_LAUNCHED.exec(output)?.[1];
530
+ if (workflow) {
531
+ this.workflows.set(workflow, ref);
532
+ events.push({ type: 'workflow.progress', ref, workflow: { name: null, taskId: workflow, phases: [], agents: [] } });
533
+ }
534
+ events.push({ type: 'tool.done', ref, output, state: block.is_error === true ? 'error' : 'done' });
535
+ }
536
+ }
537
+ }
538
+ handleResult(frame, events) {
539
+ const echoed = Array.isArray(frame.user_message_uuids) ? frame.user_message_uuids.filter((value) => typeof value === 'string') : [];
540
+ const origin = str(frame.origin) ?? (isRecord(frame.origin) ? str(frame.origin.kind) : null);
541
+ if (this.promptId !== null && (echoed.length > 0 ? !echoed.includes(this.promptId) : origin !== null && origin !== 'human' && origin !== 'user')) {
542
+ if (typeof frame.total_cost_usd === 'number') {
543
+ events.push({ type: 'usage', costUsd: num(frame.total_cost_usd) });
544
+ }
545
+ if (frame.is_error === true) {
546
+ events.push({ type: 'note', level: 'warning', text: 'A background Claude turn failed; the submitted prompt is still pending.' });
547
+ }
548
+ return;
549
+ }
550
+ const promptId = this.promptId !== null && echoed.includes(this.promptId) ? this.promptId : null;
551
+ this.promptId = null;
552
+ const failed = frame.is_error === true || (typeof frame.subtype === 'string' && frame.subtype.startsWith('error'));
553
+ const errors = Array.isArray(frame.errors) ? frame.errors.filter((error) => typeof error === 'string') : [];
554
+ const lastUuid = this.lastUuid;
555
+ this.lastUuid = null;
556
+ const limit = failed ? this.limit() : null;
557
+ this.apiError = null;
558
+ this.refusedWindows.clear();
559
+ events.push({
560
+ type: 'turn.done',
561
+ ...(promptId === null ? {} : { promptId }),
562
+ state: failed ? 'error' : 'done',
563
+ costUsd: num(frame.total_cost_usd),
564
+ ...(failed ? { error: errors[0] ?? str(frame.result) ?? `The turn ended with ${str(frame.subtype) ?? 'an error'}` } : {}),
565
+ ...(lastUuid === null ? {} : { native: { lastUuid } }),
566
+ ...(limit === null ? {} : { limit })
567
+ });
568
+ }
569
+ noteRefusal(info) {
570
+ if (!isRecord(info) || typeof info.status !== 'string') {
571
+ return;
572
+ }
573
+ const window = str(info.rateLimitType) ?? 'unknown';
574
+ const overageAllowed = info.overageStatus === 'allowed' || info.overageStatus === 'allowed_warning' || info.isUsingOverage === true || info.overageInUse === true;
575
+ if (info.status === 'rejected' && !overageAllowed) {
576
+ const resetsAt = typeof info.resetsAt === 'number' ? info.resetsAt * 1000 : NaN;
577
+ this.refusedWindows.set(window, Number.isFinite(resetsAt) && resetsAt > 0 && resetsAt < 8.64e15 ? resetsAt : null);
578
+ }
579
+ else if (info.status === 'allowed' || info.status === 'allowed_warning' || overageAllowed) {
580
+ this.refusedWindows.delete(window);
581
+ }
582
+ }
583
+ /*
584
+ * What stopped a failed turn, in the words of Claude Code 2.1.281: a window of the plan that refused
585
+ * the request is a usage limit, and an API error of `rate_limit` without one (a 429 the plan did
586
+ * not explain) or of `overloaded` is a model too busy to answer.
587
+ */
588
+ limit() {
589
+ if (this.refusedWindows.size > 0) {
590
+ let resetsAt = 0;
591
+ for (const reset of this.refusedWindows.values()) {
592
+ if (reset === null) {
593
+ return { kind: 'usage' };
594
+ }
595
+ resetsAt = Math.max(resetsAt, reset);
596
+ }
597
+ return { kind: 'usage', resetsAt };
598
+ }
599
+ if (this.apiError === 'rate_limit' || this.apiError === 'overloaded') {
600
+ return { kind: 'overload' };
601
+ }
602
+ return null;
603
+ }
604
+ handleControlRequest(frame, events) {
605
+ const request = isRecord(frame.request) ? frame.request : {};
606
+ const requestId = str(frame.request_id);
607
+ if (!requestId || request.subtype !== 'can_use_tool') {
608
+ return;
609
+ }
610
+ const toolUseId = str(request.tool_use_id);
611
+ const toolName = str(request.tool_name) ?? 'tool';
612
+ const background = Boolean(str(request.agent_id));
613
+ if (toolName === 'AskUserQuestion') {
614
+ const questions = parseQuestions(request.input);
615
+ if (questions.length === 0) {
616
+ return;
617
+ }
618
+ this.pending.set(requestId, { type: 'question', toolUseId, input: request.input });
619
+ events.push({ type: 'question.requested', requestId, questions, ...(background ? { background: true } : {}) });
620
+ return;
621
+ }
622
+ const suggestions = Array.isArray(request.permission_suggestions) ? request.permission_suggestions : [];
623
+ this.pending.set(requestId, { type: 'approval', toolUseId, input: request.input ?? {}, suggestions });
624
+ events.push({
625
+ type: 'approval.requested',
626
+ ...(background ? { background: true } : {}),
627
+ requestId,
628
+ ref: toolUseId,
629
+ toolName,
630
+ input: request.input ?? {},
631
+ description: str(request.description),
632
+ canAllowAlways: suggestions.length > 0,
633
+ ...(suggestions.length > 0 ? { allowAlways: { label: 'Allow with these rules', description: JSON.stringify(suggestions, null, 2) } } : {})
634
+ });
635
+ }
636
+ textRef(messageId, ordinal) {
637
+ return `${messageId}:t${ordinal}`;
638
+ }
639
+ thinkingRef(messageId, ordinal) {
640
+ return `${messageId}:k${ordinal}`;
641
+ }
642
+ }
@@ -0,0 +1,11 @@
1
+ export declare class ClaudeTitleReader {
2
+ private readonly progress;
3
+ private readonly found;
4
+ private readonly projectsDir;
5
+ private readonly chunkBytes;
6
+ constructor(projectsDir?: string, chunkBytes?: number);
7
+ forSession(agentSessionId: string, projectsDir?: string): Promise<string | null>;
8
+ forTranscript(path: string): Promise<string | null>;
9
+ private take;
10
+ private locate;
11
+ }