cli-claw-kit 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +245 -0
  3. package/config/default-groups.json +1 -0
  4. package/config/global-agents-md.template.md +37 -0
  5. package/config/mount-allowlist.json +11 -0
  6. package/container/Dockerfile +160 -0
  7. package/container/agent-runner/dist/.tsbuildinfo +1 -0
  8. package/container/agent-runner/dist/agent-definitions.js +22 -0
  9. package/container/agent-runner/dist/channel-prefixes.js +16 -0
  10. package/container/agent-runner/dist/codex-config.js +29 -0
  11. package/container/agent-runner/dist/image-detector.js +96 -0
  12. package/container/agent-runner/dist/index.js +2587 -0
  13. package/container/agent-runner/dist/mcp-tools.js +1076 -0
  14. package/container/agent-runner/dist/stream-event.types.js +5 -0
  15. package/container/agent-runner/dist/stream-processor.js +867 -0
  16. package/container/agent-runner/dist/types.js +6 -0
  17. package/container/agent-runner/dist/utils.js +115 -0
  18. package/container/agent-runner/package.json +36 -0
  19. package/container/agent-runner/prompts/security-rules.md +31 -0
  20. package/container/agent-runner/src/agent-definitions.ts +27 -0
  21. package/container/agent-runner/src/channel-prefixes.ts +16 -0
  22. package/container/agent-runner/src/codex-config.ts +40 -0
  23. package/container/agent-runner/src/image-detector.ts +116 -0
  24. package/container/agent-runner/src/index.ts +3107 -0
  25. package/container/agent-runner/src/mcp-tools.ts +1295 -0
  26. package/container/agent-runner/src/stream-event.types.ts +10 -0
  27. package/container/agent-runner/src/stream-processor.ts +932 -0
  28. package/container/agent-runner/src/types.ts +75 -0
  29. package/container/agent-runner/src/utils.ts +114 -0
  30. package/container/agent-runner/tsconfig.json +17 -0
  31. package/container/build.sh +28 -0
  32. package/container/entrypoint.sh +64 -0
  33. package/container/skills/agent-browser/SKILL.md +159 -0
  34. package/container/skills/install-skill/SKILL.md +64 -0
  35. package/container/skills/post-test-cleanup/SKILL.md +121 -0
  36. package/dist/.tsbuildinfo +1 -0
  37. package/dist/agent-output-parser.js +459 -0
  38. package/dist/app-root.js +52 -0
  39. package/dist/assistant-meta-footer.js +1 -0
  40. package/dist/auth.js +91 -0
  41. package/dist/billing.js +694 -0
  42. package/dist/channel-prefixes.js +16 -0
  43. package/dist/cli.js +86 -0
  44. package/dist/commands.js +79 -0
  45. package/dist/config.js +120 -0
  46. package/dist/container-runner.js +981 -0
  47. package/dist/daily-summary.js +210 -0
  48. package/dist/db.js +3683 -0
  49. package/dist/dingtalk.js +1347 -0
  50. package/dist/feishu-markdown-style.js +97 -0
  51. package/dist/feishu-streaming-card.js +1875 -0
  52. package/dist/feishu.js +1628 -0
  53. package/dist/file-manager.js +270 -0
  54. package/dist/group-queue.js +1070 -0
  55. package/dist/group-runtime.js +35 -0
  56. package/dist/host-workspace-cwd.js +85 -0
  57. package/dist/im-channel.js +384 -0
  58. package/dist/im-command-utils.js +142 -0
  59. package/dist/im-downloader.js +45 -0
  60. package/dist/im-manager.js +527 -0
  61. package/dist/im-utils.js +53 -0
  62. package/dist/image-detector.js +96 -0
  63. package/dist/index.js +5828 -0
  64. package/dist/logger.js +22 -0
  65. package/dist/mcp-utils.js +66 -0
  66. package/dist/message-attachments.js +69 -0
  67. package/dist/message-notifier.js +36 -0
  68. package/dist/middleware/auth.js +85 -0
  69. package/dist/mount-security.js +315 -0
  70. package/dist/permissions.js +67 -0
  71. package/dist/project-memory.js +6 -0
  72. package/dist/provider-pool.js +189 -0
  73. package/dist/qq.js +826 -0
  74. package/dist/reset-admin.js +42 -0
  75. package/dist/routes/admin.js +543 -0
  76. package/dist/routes/agent-definitions.js +241 -0
  77. package/dist/routes/agents.js +533 -0
  78. package/dist/routes/auth.js +675 -0
  79. package/dist/routes/billing.js +490 -0
  80. package/dist/routes/browse.js +210 -0
  81. package/dist/routes/bug-report.js +387 -0
  82. package/dist/routes/config.js +1868 -0
  83. package/dist/routes/files.js +671 -0
  84. package/dist/routes/groups.js +1367 -0
  85. package/dist/routes/mcp-servers.js +320 -0
  86. package/dist/routes/memory.js +523 -0
  87. package/dist/routes/monitor.js +307 -0
  88. package/dist/routes/skills.js +777 -0
  89. package/dist/routes/tasks.js +509 -0
  90. package/dist/routes/usage.js +64 -0
  91. package/dist/routes/workspace-config.js +458 -0
  92. package/dist/runtime-build.js +112 -0
  93. package/dist/runtime-command-handler.js +189 -0
  94. package/dist/runtime-command-registry.js +1 -0
  95. package/dist/runtime-config.js +1777 -0
  96. package/dist/runtime-identity.js +52 -0
  97. package/dist/schemas.js +590 -0
  98. package/dist/script-runner.js +64 -0
  99. package/dist/sdk-query.js +82 -0
  100. package/dist/skill-utils.js +145 -0
  101. package/dist/sqlite-compat.js +19 -0
  102. package/dist/stream-event.types.js +5 -0
  103. package/dist/streaming-runtime-meta.js +29 -0
  104. package/dist/task-scheduler.js +695 -0
  105. package/dist/task-utils.js +13 -0
  106. package/dist/telegram-pairing.js +59 -0
  107. package/dist/telegram.js +897 -0
  108. package/dist/terminal-manager.js +307 -0
  109. package/dist/tool-step-display.js +1 -0
  110. package/dist/types.js +1 -0
  111. package/dist/utils.js +85 -0
  112. package/dist/web-context.js +161 -0
  113. package/dist/web.js +1377 -0
  114. package/dist/wechat-crypto.js +182 -0
  115. package/dist/wechat.js +589 -0
  116. package/dist/workspace-runtime-reset.js +35 -0
  117. package/package.json +107 -0
  118. package/shared/assistant-meta-footer.ts +127 -0
  119. package/shared/channel-prefixes.ts +16 -0
  120. package/shared/dist/assistant-meta-footer.d.ts +29 -0
  121. package/shared/dist/assistant-meta-footer.js +85 -0
  122. package/shared/dist/channel-prefixes.d.ts +4 -0
  123. package/shared/dist/channel-prefixes.js +16 -0
  124. package/shared/dist/image-detector.d.ts +20 -0
  125. package/shared/dist/image-detector.js +96 -0
  126. package/shared/dist/runtime-command-registry.d.ts +38 -0
  127. package/shared/dist/runtime-command-registry.js +185 -0
  128. package/shared/dist/stream-event.d.ts +65 -0
  129. package/shared/dist/stream-event.js +8 -0
  130. package/shared/dist/tool-step-display.d.ts +4 -0
  131. package/shared/dist/tool-step-display.js +11 -0
  132. package/shared/image-detector.ts +116 -0
  133. package/shared/runtime-command-registry.ts +252 -0
  134. package/shared/stream-event.ts +67 -0
  135. package/shared/tool-step-display.ts +21 -0
  136. package/shared/tsconfig.json +24 -0
  137. package/web/dist/assets/BillingPage-B1wBR_o-.js +52 -0
  138. package/web/dist/assets/ChatPage-6GBZ9nXN.css +32 -0
  139. package/web/dist/assets/ChatPage-BOJcXtaj.js +161 -0
  140. package/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  141. package/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  142. package/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  143. package/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  144. package/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  145. package/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  146. package/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  147. package/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  148. package/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  149. package/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  150. package/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  151. package/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  152. package/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  153. package/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  154. package/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  155. package/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  156. package/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  157. package/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  158. package/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  159. package/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  160. package/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  161. package/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  162. package/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  163. package/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  164. package/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  165. package/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  166. package/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  167. package/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  168. package/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  169. package/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  170. package/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  171. package/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  172. package/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  173. package/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  174. package/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  175. package/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  176. package/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  177. package/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  178. package/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  179. package/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  180. package/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  181. package/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  182. package/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  183. package/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  184. package/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  185. package/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  186. package/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  187. package/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  188. package/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  189. package/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  190. package/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  191. package/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  192. package/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  193. package/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  194. package/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  195. package/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  196. package/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  197. package/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  198. package/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  199. package/web/dist/assets/SettingsPage-DoY7FoZ_.js +153 -0
  200. package/web/dist/assets/ShareImageDialog-C1ga8b7l.js +22 -0
  201. package/web/dist/assets/TasksPage-CRivnNsx.js +14 -0
  202. package/web/dist/assets/_basePickBy-Bf-bSoS9.js +1 -0
  203. package/web/dist/assets/_baseUniq-zAOaCuKw.js +1 -0
  204. package/web/dist/assets/arc-Dm9mVQ9U.js +1 -0
  205. package/web/dist/assets/architectureDiagram-2XIMDMQ5-BLmzX1wr.js +36 -0
  206. package/web/dist/assets/band-CquvqAHh.js +1 -0
  207. package/web/dist/assets/blockDiagram-WCTKOSBZ-B9pcqm3j.js +132 -0
  208. package/web/dist/assets/c4Diagram-IC4MRINW-Cytx1q3b.js +10 -0
  209. package/web/dist/assets/channel-BOVj73LR.js +1 -0
  210. package/web/dist/assets/channel-meta-CQD0Pei-.js +41 -0
  211. package/web/dist/assets/chunk-4BX2VUAB-0ToDr6RE.js +1 -0
  212. package/web/dist/assets/chunk-55IACEB6-DQDjnXfS.js +1 -0
  213. package/web/dist/assets/chunk-FMBD7UC4-Di8ABm6c.js +15 -0
  214. package/web/dist/assets/chunk-JSJVCQXG-BZQN6rnX.js +1 -0
  215. package/web/dist/assets/chunk-KX2RTZJC-zBbcpaN_.js +1 -0
  216. package/web/dist/assets/chunk-NQ4KR5QH-BCrLoU88.js +220 -0
  217. package/web/dist/assets/chunk-QZHKN3VN-Bqk8juan.js +1 -0
  218. package/web/dist/assets/chunk-WL4C6EOR-D2YX-MHY.js +189 -0
  219. package/web/dist/assets/classDiagram-VBA2DB6C-DUUoMyaK.js +1 -0
  220. package/web/dist/assets/classDiagram-v2-RAHNMMFH-DUUoMyaK.js +1 -0
  221. package/web/dist/assets/clone-BmaCesfa.js +1 -0
  222. package/web/dist/assets/cose-bilkent-S5V4N54A-CTsv6qQA.js +1 -0
  223. package/web/dist/assets/cytoscape.esm-BQaXIfA_.js +331 -0
  224. package/web/dist/assets/dagre-KLK3FWXG-Ci4Jh9nu.js +4 -0
  225. package/web/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  226. package/web/dist/assets/diagram-E7M64L7V-BFRnfTI2.js +24 -0
  227. package/web/dist/assets/diagram-IFDJBPK2-B7Zhnp0b.js +43 -0
  228. package/web/dist/assets/diagram-P4PSJMXO-BVyP7nwq.js +24 -0
  229. package/web/dist/assets/erDiagram-INFDFZHY-NorKdTOF.js +70 -0
  230. package/web/dist/assets/error-CGD5mp5f.js +1 -0
  231. package/web/dist/assets/flowDiagram-PKNHOUZH-Ch97nABF.js +162 -0
  232. package/web/dist/assets/ganttDiagram-A5KZAMGK-BQ2pLWsy.js +292 -0
  233. package/web/dist/assets/gitGraphDiagram-K3NZZRJ6-bcvnBsD2.js +65 -0
  234. package/web/dist/assets/graph-CeAEckur.js +1 -0
  235. package/web/dist/assets/index-CPnL1_qC.js +768 -0
  236. package/web/dist/assets/index-DVevCbcO.css +10 -0
  237. package/web/dist/assets/infoDiagram-LFFYTUFH-CcsrFdj-.js +2 -0
  238. package/web/dist/assets/init-Dmth1JHB.js +1 -0
  239. package/web/dist/assets/ishikawaDiagram-PHBUUO56-1upyMfHN.js +70 -0
  240. package/web/dist/assets/journeyDiagram-4ABVD52K-CKUi-V0c.js +139 -0
  241. package/web/dist/assets/kanban-definition-K7BYSVSG-DOnQwXfL.js +89 -0
  242. package/web/dist/assets/layout-BmMMqTnJ.js +1 -0
  243. package/web/dist/assets/linear-DiaJloY5.js +1 -0
  244. package/web/dist/assets/mermaid.core-BWLV1B2v.js +254 -0
  245. package/web/dist/assets/mindmap-definition-YRQLILUH-BeAKHVWP.js +68 -0
  246. package/web/dist/assets/ordinal-DILIJJjt.js +1 -0
  247. package/web/dist/assets/pieDiagram-SKSYHLDU-DfiMSfWo.js +30 -0
  248. package/web/dist/assets/quadrantDiagram-337W2JSQ-wZxZOJxd.js +7 -0
  249. package/web/dist/assets/requirementDiagram-Z7DCOOCP-BK4HHm17.js +73 -0
  250. package/web/dist/assets/sankeyDiagram-WA2Y5GQK-BX6t2avX.js +10 -0
  251. package/web/dist/assets/sequenceDiagram-2WXFIKYE-BPQlkbAa.js +145 -0
  252. package/web/dist/assets/sheet-rI0FfB1g.js +6 -0
  253. package/web/dist/assets/sliders-horizontal-CuijWFNK.js +6 -0
  254. package/web/dist/assets/sparkles-BsMYXJoT.js +11 -0
  255. package/web/dist/assets/square-0CqMX1Q3.js +11 -0
  256. package/web/dist/assets/stateDiagram-RAJIS63D-DxkV0Vwd.js +1 -0
  257. package/web/dist/assets/stateDiagram-v2-FVOUBMTO-qLYoiOPe.js +1 -0
  258. package/web/dist/assets/step-D51IIHGA.js +1 -0
  259. package/web/dist/assets/tasks-D8JjBTwx.js +1 -0
  260. package/web/dist/assets/time-O8zIGux3.js +1 -0
  261. package/web/dist/assets/timeline-definition-YZTLITO2-kNp1DyFc.js +61 -0
  262. package/web/dist/assets/treemap-KZPCXAKY-CkrClVhk.js +162 -0
  263. package/web/dist/assets/utils-KGAn0XTg.js +11 -0
  264. package/web/dist/assets/vennDiagram-LZ73GAT5-CgdzEZz4.js +34 -0
  265. package/web/dist/assets/xychartDiagram-JWTSCODW-DfYGPfNB.js +7 -0
  266. package/web/dist/assets/zap-_hKJYy7J.js +6 -0
  267. package/web/dist/favicon.svg +332 -0
  268. package/web/dist/fonts/AlibabaPuHuiTi-3-55-Regular.woff2 +0 -0
  269. package/web/dist/fonts/AlibabaPuHuiTi-3-65-Medium.woff2 +0 -0
  270. package/web/dist/fonts/AlibabaPuHuiTi-3-75-SemiBold.woff2 +0 -0
  271. package/web/dist/fonts/DMSans-latin-ext.woff2 +0 -0
  272. package/web/dist/fonts/DMSans-latin.woff2 +0 -0
  273. package/web/dist/icons/README.md +20 -0
  274. package/web/dist/icons/apple-touch-icon-180.png +0 -0
  275. package/web/dist/icons/icon-128.png +0 -0
  276. package/web/dist/icons/icon-144.png +0 -0
  277. package/web/dist/icons/icon-152.png +0 -0
  278. package/web/dist/icons/icon-192.png +0 -0
  279. package/web/dist/icons/icon-192.svg +332 -0
  280. package/web/dist/icons/icon-384.png +0 -0
  281. package/web/dist/icons/icon-48.png +0 -0
  282. package/web/dist/icons/icon-512-maskable.png +0 -0
  283. package/web/dist/icons/icon-512.png +0 -0
  284. package/web/dist/icons/icon-512.svg +332 -0
  285. package/web/dist/icons/icon-72.png +0 -0
  286. package/web/dist/icons/icon-96.png +0 -0
  287. package/web/dist/icons/loading-logo.svg +332 -0
  288. package/web/dist/icons/logo-1024.png +0 -0
  289. package/web/dist/icons/logo-icon.svg +332 -0
  290. package/web/dist/icons/logo-text.svg +332 -0
  291. package/web/dist/index.html +30 -0
  292. package/web/dist/manifest.webmanifest +1 -0
  293. package/web/dist/registerSW.js +1 -0
  294. package/web/dist/sw.js +1 -0
  295. package/web/dist/workbox-08d6266a.js +1 -0
@@ -0,0 +1,867 @@
1
+ /**
2
+ * StreamEventProcessor — encapsulates all streaming event processing logic
3
+ * extracted from runQuery() in index.ts.
4
+ *
5
+ * Manages:
6
+ * - Text/thinking buffering and flushing
7
+ * - Tool use start/end tracking (top-level, nested, Skill, Task)
8
+ * - Sub-agent message conversion to StreamEvents
9
+ * - Cleanup of residual tool states
10
+ */
11
+ import { extractSkillName, summarizeToolInput } from './utils.js';
12
+ /** Tools with specialized input_json_delta handling — generic accumulation is skipped for these. */
13
+ const SPECIAL_TOOLS = ['Skill', 'Task', 'Agent', 'AskUserQuestion', 'TodoWrite'];
14
+ export class StreamEventProcessor {
15
+ emit;
16
+ log;
17
+ // Text aggregation buffers — keyed by parentToolUseId (BUF_MAIN for top-level)
18
+ BUF_MAIN = '__main__';
19
+ streamBufs = new Map();
20
+ flushTimer = null;
21
+ seenTextualResult = false;
22
+ FLUSH_MS = 100;
23
+ FLUSH_CHARS = 200;
24
+ // Full text accumulator — SDK's result.result only contains the last text block;
25
+ // this accumulates all text_delta to produce the complete response.
26
+ fullTextAccumulator = '';
27
+ // Top-level tool use tracking
28
+ activeTopLevelToolUseId = null;
29
+ // Active Skill tool ID: tools called inside Skill may lack parent_tool_use_id
30
+ activeSkillToolUseId = null;
31
+ // Accumulate Skill tool input_json_delta to extract skillName
32
+ // Keyed by content block index (event.index) to match deltas correctly
33
+ pendingSkillInput = new Map();
34
+ // Accumulate Task tool input_json_delta to extract description and team_name
35
+ pendingTaskInput = new Map();
36
+ // Accumulate AskUserQuestion tool input_json_delta to extract questions/options
37
+ pendingAskUserInput = new Map();
38
+ // Accumulate TodoWrite tool input_json_delta to extract todos
39
+ pendingTodoInput = new Map();
40
+ // Accumulate generic tool input_json_delta to extract toolInputSummary
41
+ pendingGenericInput = new Map();
42
+ // Confirmed teammate Tasks (detected via team_name)
43
+ teammateTaskToolUseIds = new Set();
44
+ // Task tool_use_ids — tool_use_end is only emitted via tool_use_summary,
45
+ // not prematurely when the next content block starts
46
+ taskToolUseIds = new Set();
47
+ // Track active nested tool per parent context (for synthetic tool_use_end)
48
+ activeNestedToolByParent = new Map();
49
+ // Background Task tool_use_ids (run_in_background: true)
50
+ backgroundTaskToolUseIds = new Set();
51
+ // SDK internal task_id → API tool_use_id mapping.
52
+ // Built from task_started/task_progress system messages so that
53
+ // task_notification (which carries SDK task_id) can be translated
54
+ // back to the tool_use_id used at creation time.
55
+ sdkTaskIdToToolUseId = new Map();
56
+ // Sub-agent active tools per parent task ID
57
+ activeSubAgentToolsByTask = new Map();
58
+ constructor(emit, log) {
59
+ this.emit = emit;
60
+ this.log = log;
61
+ }
62
+ /** Get or create a buffer for a given key. */
63
+ getBuf(key) {
64
+ let b = this.streamBufs.get(key);
65
+ if (!b) {
66
+ b = { text: '', think: '' };
67
+ this.streamBufs.set(key, b);
68
+ }
69
+ return b;
70
+ }
71
+ /** Flush all pending text/thinking buffers. */
72
+ flushBuffers() {
73
+ for (const [key, buf] of this.streamBufs) {
74
+ const pid = key === this.BUF_MAIN ? undefined : key;
75
+ if (buf.text) {
76
+ this.emit({ status: 'stream', result: null, streamEvent: { eventType: 'text_delta', text: buf.text, parentToolUseId: pid } });
77
+ buf.text = '';
78
+ }
79
+ if (buf.think) {
80
+ this.emit({ status: 'stream', result: null, streamEvent: { eventType: 'thinking_delta', text: buf.think, parentToolUseId: pid } });
81
+ buf.think = '';
82
+ }
83
+ }
84
+ this.flushTimer = null;
85
+ }
86
+ /** Schedule a flush, either immediately (if buffer is large enough) or after FLUSH_MS. */
87
+ scheduleFlush() {
88
+ let maxLen = 0;
89
+ for (const buf of this.streamBufs.values()) {
90
+ maxLen = Math.max(maxLen, buf.text.length, buf.think.length);
91
+ }
92
+ if (maxLen >= this.FLUSH_CHARS) {
93
+ if (this.flushTimer) {
94
+ clearTimeout(this.flushTimer);
95
+ this.flushTimer = null;
96
+ }
97
+ this.flushBuffers();
98
+ }
99
+ else if (!this.flushTimer) {
100
+ this.flushTimer = setTimeout(() => this.flushBuffers(), this.FLUSH_MS);
101
+ }
102
+ }
103
+ /** Clean up tools associated with a Task. */
104
+ cleanupTaskTools(taskId) {
105
+ const nested = this.activeNestedToolByParent.get(taskId);
106
+ if (nested) {
107
+ this.emit({ status: 'stream', result: null,
108
+ streamEvent: { eventType: 'tool_use_end', toolUseId: nested.toolUseId, parentToolUseId: taskId },
109
+ });
110
+ this.activeNestedToolByParent.delete(taskId);
111
+ }
112
+ const subTools = this.activeSubAgentToolsByTask.get(taskId);
113
+ if (subTools) {
114
+ for (const toolId of subTools) {
115
+ this.emit({ status: 'stream', result: null,
116
+ streamEvent: { eventType: 'tool_use_end', toolUseId: toolId, parentToolUseId: taskId },
117
+ });
118
+ }
119
+ this.activeSubAgentToolsByTask.delete(taskId);
120
+ }
121
+ }
122
+ /**
123
+ * Process a stream_event message from the SDK.
124
+ * Returns true if the message was handled (caller should continue to next message).
125
+ */
126
+ processStreamEvent(message) {
127
+ const parentToolUseId = message.parent_tool_use_id === undefined ? null : message.parent_tool_use_id;
128
+ const isNested = parentToolUseId !== null;
129
+ const event = message.event;
130
+ // Diagnostic log: print non-delta nested events
131
+ if (isNested && event.type !== 'content_block_delta') {
132
+ const evtType = event.type === 'content_block_start'
133
+ ? `block_start/${event.content_block?.type}${event.content_block?.name ? `:${event.content_block.name}` : ''}`
134
+ : event.type;
135
+ this.log(`[stream-nested] parent=${parentToolUseId} evt=${evtType} tasks=[${[...this.taskToolUseIds].map(id => id.slice(0, 12)).join(',')}]`);
136
+ }
137
+ if (event.type === 'content_block_start') {
138
+ const _b = event.content_block;
139
+ this.log(`[stream] parent=${parentToolUseId ?? 'null'} block=${_b?.type}${_b?.name ? ` name=${_b.name}` : ''}${_b?.id ? ` id=${_b.id.slice(0, 12)}` : ''}`);
140
+ const block = event.content_block;
141
+ if (block?.type === 'tool_use') {
142
+ this.handleToolUseStart(block, parentToolUseId, isNested, event.index);
143
+ }
144
+ else if (block?.type === 'text') {
145
+ this.handleTextBlockStart(parentToolUseId, isNested);
146
+ }
147
+ }
148
+ else if (event.type === 'content_block_delta') {
149
+ this.handleContentBlockDelta(event, parentToolUseId);
150
+ }
151
+ return true;
152
+ }
153
+ /** Handle tool_use content_block_start. */
154
+ handleToolUseStart(block, parentToolUseId, isNested, blockIndex) {
155
+ // Determine if this is inside a Skill: SDK may not set parent_tool_use_id
156
+ const isInsideSkill = !isNested && this.activeSkillToolUseId && block.name !== 'Skill';
157
+ const effectiveIsNested = isNested || !!isInsideSkill;
158
+ const effectiveParentToolUseId = isInsideSkill ? this.activeSkillToolUseId : parentToolUseId;
159
+ if (!effectiveIsNested && this.activeTopLevelToolUseId && this.activeTopLevelToolUseId !== block.id) {
160
+ // Task tool_use_end only via tool_use_summary (not premature)
161
+ if (!this.taskToolUseIds.has(this.activeTopLevelToolUseId)) {
162
+ this.emit({
163
+ status: 'stream', result: null,
164
+ streamEvent: { eventType: 'tool_use_end', toolUseId: this.activeTopLevelToolUseId },
165
+ });
166
+ }
167
+ if (this.activeTopLevelToolUseId === this.activeSkillToolUseId) {
168
+ this.activeSkillToolUseId = null;
169
+ }
170
+ }
171
+ if (!effectiveIsNested)
172
+ this.activeTopLevelToolUseId = block.id || null;
173
+ // Track nested tools: end previous active tool under same parent
174
+ if (effectiveIsNested && effectiveParentToolUseId) {
175
+ const prevNested = this.activeNestedToolByParent.get(effectiveParentToolUseId);
176
+ if (prevNested && prevNested.toolUseId !== block.id) {
177
+ this.emit({
178
+ status: 'stream', result: null,
179
+ streamEvent: { eventType: 'tool_use_end', toolUseId: prevNested.toolUseId, parentToolUseId: effectiveParentToolUseId },
180
+ });
181
+ }
182
+ this.activeNestedToolByParent.set(effectiveParentToolUseId, { toolUseId: block.id || '', toolName: block.name });
183
+ }
184
+ this.emit({
185
+ status: 'stream', result: null,
186
+ streamEvent: {
187
+ eventType: 'tool_use_start',
188
+ toolName: block.name,
189
+ toolUseId: block.id,
190
+ parentToolUseId: effectiveParentToolUseId,
191
+ isNested: effectiveIsNested,
192
+ skillName: extractSkillName(block.name, block.input),
193
+ toolInputSummary: summarizeToolInput(block.input),
194
+ },
195
+ });
196
+ // Track Skill tool_use block
197
+ if (block.name === 'Skill' && block.id) {
198
+ this.activeSkillToolUseId = block.id;
199
+ if (typeof blockIndex === 'number') {
200
+ this.pendingSkillInput.set(blockIndex, {
201
+ toolUseId: block.id, inputJson: '', resolved: false,
202
+ parentToolUseId, isNested,
203
+ });
204
+ }
205
+ }
206
+ // Track AskUserQuestion tool
207
+ if (block.name === 'AskUserQuestion' && block.id) {
208
+ if (typeof blockIndex === 'number') {
209
+ this.pendingAskUserInput.set(blockIndex, {
210
+ toolUseId: block.id, inputJson: '', resolved: false,
211
+ parentToolUseId, isNested,
212
+ });
213
+ }
214
+ }
215
+ // Track TodoWrite tool
216
+ if (block.name === 'TodoWrite' && block.id) {
217
+ if (typeof blockIndex === 'number') {
218
+ this.pendingTodoInput.set(blockIndex, {
219
+ toolUseId: block.id, inputJson: '', resolved: false,
220
+ parentToolUseId, isNested,
221
+ });
222
+ }
223
+ }
224
+ // Track generic tools for input_json_delta → toolInputSummary
225
+ if (block.name && !SPECIAL_TOOLS.includes(block.name) && typeof blockIndex === 'number') {
226
+ this.pendingGenericInput.set(blockIndex, {
227
+ toolUseId: block.id || '', inputJson: '', resolved: false,
228
+ parentToolUseId: effectiveParentToolUseId, isNested: effectiveIsNested,
229
+ toolName: block.name,
230
+ });
231
+ }
232
+ // Track Task / Agent tool (both spawn sub-agents whose messages need forwarding)
233
+ if ((block.name === 'Task' || block.name === 'Agent') && block.id) {
234
+ this.taskToolUseIds.add(block.id);
235
+ this.emit({
236
+ status: 'stream', result: null,
237
+ streamEvent: { eventType: 'task_start', toolUseId: block.id, toolName: block.name },
238
+ });
239
+ if (typeof blockIndex === 'number') {
240
+ this.pendingTaskInput.set(blockIndex, {
241
+ toolUseId: block.id, inputJson: '', resolved: false,
242
+ });
243
+ }
244
+ }
245
+ }
246
+ /** Handle text content_block_start. */
247
+ handleTextBlockStart(parentToolUseId, isNested) {
248
+ // New text block means top-level tool has finished executing (main agent only)
249
+ if (!isNested && this.activeTopLevelToolUseId) {
250
+ if (!this.taskToolUseIds.has(this.activeTopLevelToolUseId)) {
251
+ this.emit({
252
+ status: 'stream', result: null,
253
+ streamEvent: { eventType: 'tool_use_end', toolUseId: this.activeTopLevelToolUseId },
254
+ });
255
+ }
256
+ this.activeTopLevelToolUseId = null;
257
+ this.activeSkillToolUseId = null;
258
+ }
259
+ // Nested text block: end active nested tool under that parent
260
+ if (isNested && parentToolUseId) {
261
+ const prevNested = this.activeNestedToolByParent.get(parentToolUseId);
262
+ if (prevNested) {
263
+ this.emit({
264
+ status: 'stream', result: null,
265
+ streamEvent: { eventType: 'tool_use_end', toolUseId: prevNested.toolUseId, parentToolUseId },
266
+ });
267
+ this.activeNestedToolByParent.delete(parentToolUseId);
268
+ }
269
+ }
270
+ }
271
+ /** Handle content_block_delta events (text, thinking, input_json). */
272
+ handleContentBlockDelta(event, parentToolUseId) {
273
+ const delta = event.delta;
274
+ if (delta?.type === 'text_delta' && delta.text) {
275
+ const bufKey = parentToolUseId || this.BUF_MAIN;
276
+ this.getBuf(bufKey).text += delta.text;
277
+ if (bufKey === this.BUF_MAIN)
278
+ this.fullTextAccumulator += delta.text;
279
+ this.scheduleFlush();
280
+ }
281
+ else if (delta?.type === 'thinking_delta' && delta.thinking) {
282
+ const bufKey = parentToolUseId || this.BUF_MAIN;
283
+ this.getBuf(bufKey).think += delta.thinking;
284
+ this.scheduleFlush();
285
+ }
286
+ else if (delta?.type === 'input_json_delta' && delta.partial_json) {
287
+ const blockIndex = event.index;
288
+ if (typeof blockIndex === 'number') {
289
+ this.handleInputJsonDelta(blockIndex, delta.partial_json);
290
+ }
291
+ }
292
+ }
293
+ /** Handle input_json_delta for Skill and Task tools. */
294
+ handleInputJsonDelta(blockIndex, partialJson) {
295
+ // Accumulate Skill input JSON
296
+ const pending = this.pendingSkillInput.get(blockIndex);
297
+ if (pending && !pending.resolved) {
298
+ pending.inputJson += partialJson;
299
+ const skillMatch = pending.inputJson.match(/"skill"\s*:\s*"([^"]+)"/);
300
+ if (skillMatch) {
301
+ pending.resolved = true;
302
+ this.pendingSkillInput.delete(blockIndex);
303
+ this.emit({
304
+ status: 'stream', result: null,
305
+ streamEvent: {
306
+ eventType: 'tool_progress',
307
+ toolName: 'Skill',
308
+ toolUseId: pending.toolUseId,
309
+ parentToolUseId: pending.parentToolUseId,
310
+ isNested: pending.isNested,
311
+ skillName: skillMatch[1],
312
+ },
313
+ });
314
+ }
315
+ }
316
+ // Accumulate AskUserQuestion input JSON
317
+ const pendingAsk = this.pendingAskUserInput.get(blockIndex);
318
+ if (pendingAsk && !pendingAsk.resolved) {
319
+ pendingAsk.inputJson += partialJson;
320
+ // Try to parse once we see "questions" field
321
+ if (pendingAsk.inputJson.includes('"question')) {
322
+ try {
323
+ const parsed = JSON.parse(pendingAsk.inputJson);
324
+ if (parsed.question || parsed.questions) {
325
+ pendingAsk.resolved = true;
326
+ this.pendingAskUserInput.delete(blockIndex);
327
+ this.emit({
328
+ status: 'stream', result: null,
329
+ streamEvent: {
330
+ eventType: 'tool_progress',
331
+ toolName: 'AskUserQuestion',
332
+ toolUseId: pendingAsk.toolUseId,
333
+ parentToolUseId: pendingAsk.parentToolUseId,
334
+ isNested: pendingAsk.isNested,
335
+ toolInput: parsed,
336
+ },
337
+ });
338
+ }
339
+ }
340
+ catch {
341
+ // JSON not complete yet, continue accumulating
342
+ }
343
+ }
344
+ }
345
+ // Accumulate TodoWrite input JSON
346
+ const pendingTodo = this.pendingTodoInput.get(blockIndex);
347
+ if (pendingTodo && !pendingTodo.resolved) {
348
+ pendingTodo.inputJson += partialJson;
349
+ if (pendingTodo.inputJson.includes('"todos"')) {
350
+ try {
351
+ const parsed = JSON.parse(pendingTodo.inputJson);
352
+ if (Array.isArray(parsed.todos)) {
353
+ pendingTodo.resolved = true;
354
+ this.pendingTodoInput.delete(blockIndex);
355
+ this.emit({
356
+ status: 'stream', result: null,
357
+ streamEvent: {
358
+ eventType: 'todo_update',
359
+ todos: parsed.todos,
360
+ },
361
+ });
362
+ }
363
+ }
364
+ catch {
365
+ // JSON not complete yet, continue accumulating
366
+ }
367
+ }
368
+ }
369
+ // Accumulate Task input JSON
370
+ const pendingTask = this.pendingTaskInput.get(blockIndex);
371
+ if (pendingTask && !pendingTask.resolved) {
372
+ pendingTask.inputJson += partialJson;
373
+ // Detect team_name
374
+ if (!pendingTask.isTeammate) {
375
+ const teamMatch = pendingTask.inputJson.match(/"team_name"\s*:\s*"((?:[^"\\]|\\.)*)"/);
376
+ if (teamMatch) {
377
+ pendingTask.isTeammate = true;
378
+ this.teammateTaskToolUseIds.add(pendingTask.toolUseId);
379
+ }
380
+ }
381
+ const descMatch = pendingTask.inputJson.match(/"description"\s*:\s*"((?:[^"\\]|\\.)*)"/);
382
+ if (descMatch) {
383
+ pendingTask.resolved = true;
384
+ this.pendingTaskInput.delete(blockIndex);
385
+ const isTeammate = pendingTask.isTeammate || false;
386
+ if (isTeammate)
387
+ this.teammateTaskToolUseIds.add(pendingTask.toolUseId);
388
+ this.emit({
389
+ status: 'stream', result: null,
390
+ streamEvent: {
391
+ eventType: 'task_start',
392
+ toolUseId: pendingTask.toolUseId,
393
+ toolName: 'Task',
394
+ taskDescription: descMatch[1].replace(/\\"/g, '"').slice(0, 200),
395
+ ...(isTeammate ? { isTeammate: true } : {}),
396
+ },
397
+ });
398
+ }
399
+ }
400
+ // Accumulate generic tool input JSON for toolInputSummary.
401
+ // Only attempt JSON.parse when the accumulated string looks complete (ends with '}')
402
+ // to avoid O(n^2) repeated parse failures on large tool inputs.
403
+ // Cap at 10KB to avoid unbounded memory growth on tools with large inputs (Write, Edit).
404
+ const GENERIC_INPUT_MAX = 10_240;
405
+ const pendingGeneric = this.pendingGenericInput.get(blockIndex);
406
+ if (pendingGeneric && !pendingGeneric.resolved) {
407
+ if (pendingGeneric.inputJson.length >= GENERIC_INPUT_MAX) {
408
+ pendingGeneric.resolved = true;
409
+ this.pendingGenericInput.delete(blockIndex);
410
+ return;
411
+ }
412
+ pendingGeneric.inputJson += partialJson;
413
+ const trimmed = pendingGeneric.inputJson.trimEnd();
414
+ const summary = trimmed.endsWith('}') ? summarizeToolInput((() => {
415
+ try {
416
+ return JSON.parse(pendingGeneric.inputJson);
417
+ }
418
+ catch {
419
+ return null;
420
+ }
421
+ })()) : undefined;
422
+ if (summary) {
423
+ pendingGeneric.resolved = true;
424
+ this.pendingGenericInput.delete(blockIndex);
425
+ this.emit({
426
+ status: 'stream', result: null,
427
+ streamEvent: {
428
+ eventType: 'tool_progress',
429
+ toolName: pendingGeneric.toolName,
430
+ toolUseId: pendingGeneric.toolUseId,
431
+ parentToolUseId: pendingGeneric.parentToolUseId,
432
+ isNested: pendingGeneric.isNested,
433
+ toolInputSummary: summary,
434
+ },
435
+ });
436
+ }
437
+ }
438
+ }
439
+ /**
440
+ * Process a tool_progress message.
441
+ */
442
+ processToolProgress(message) {
443
+ const parentToolUseId = message.parent_tool_use_id === undefined ? null : message.parent_tool_use_id;
444
+ this.emit({
445
+ status: 'stream', result: null,
446
+ streamEvent: {
447
+ eventType: 'tool_progress',
448
+ toolName: message.tool_name,
449
+ toolUseId: message.tool_use_id,
450
+ parentToolUseId,
451
+ isNested: parentToolUseId !== null,
452
+ elapsedSeconds: message.elapsed_time_seconds,
453
+ },
454
+ });
455
+ }
456
+ /**
457
+ * Process a tool_use_summary message.
458
+ */
459
+ processToolUseSummary(message) {
460
+ const ids = Array.isArray(message.preceding_tool_use_ids)
461
+ ? message.preceding_tool_use_ids.filter((id) => typeof id === 'string')
462
+ : [];
463
+ this.log(`[tool_use_summary] ids=[${ids.map((id) => id.slice(0, 12)).join(',')}] taskToolUseIds=[${[...this.taskToolUseIds].map(id => id.slice(0, 12)).join(',')}] bgTasks=[${[...this.backgroundTaskToolUseIds].map(id => id.slice(0, 12)).join(',')}]`);
464
+ for (const id of ids) {
465
+ // Foreground Task completion: synthesize task_notification
466
+ if (this.taskToolUseIds.has(id) && !this.backgroundTaskToolUseIds.has(id)) {
467
+ this.log(`Synthesizing task_notification for foreground Task ${id.slice(0, 12)}`);
468
+ this.cleanupTaskTools(id);
469
+ this.emit({
470
+ status: 'stream', result: null,
471
+ streamEvent: {
472
+ eventType: 'task_notification',
473
+ taskId: id,
474
+ taskStatus: 'completed',
475
+ taskSummary: '',
476
+ },
477
+ });
478
+ }
479
+ this.taskToolUseIds.delete(id);
480
+ this.backgroundTaskToolUseIds.delete(id);
481
+ this.emit({
482
+ status: 'stream', result: null,
483
+ streamEvent: { eventType: 'tool_use_end', toolUseId: id },
484
+ });
485
+ if (this.activeTopLevelToolUseId === id) {
486
+ this.activeTopLevelToolUseId = null;
487
+ }
488
+ }
489
+ }
490
+ /**
491
+ * Process system messages (status, hook_started, hook_progress, hook_response).
492
+ * Returns true if the message was handled.
493
+ */
494
+ processSystemMessage(message) {
495
+ if (message.subtype === 'status') {
496
+ const statusText = message.status?.type || null;
497
+ this.emit({ status: 'stream', result: null, streamEvent: { eventType: 'status', statusText } });
498
+ return true;
499
+ }
500
+ if (message.subtype === 'hook_started') {
501
+ this.emit({
502
+ status: 'stream', result: null,
503
+ streamEvent: { eventType: 'hook_started', hookName: message.hook_name, hookEvent: message.hook_event },
504
+ });
505
+ return true;
506
+ }
507
+ if (message.subtype === 'hook_progress') {
508
+ this.emit({
509
+ status: 'stream', result: null,
510
+ streamEvent: { eventType: 'hook_progress', hookName: message.hook_name, hookEvent: message.hook_event },
511
+ });
512
+ return true;
513
+ }
514
+ if (message.subtype === 'hook_response') {
515
+ this.emit({
516
+ status: 'stream', result: null,
517
+ streamEvent: { eventType: 'hook_response', hookName: message.hook_name, hookEvent: message.hook_event, hookOutcome: message.outcome },
518
+ });
519
+ return true;
520
+ }
521
+ // API retry — emit status so user sees retry progress and activity stays alive
522
+ if (message.subtype === 'api_retry') {
523
+ const attempt = message.attempt ?? '?';
524
+ const max = message.max_retries ?? '?';
525
+ const delayMs = message.retry_delay_ms ?? 0;
526
+ const delaySec = Math.round(delayMs / 1000);
527
+ this.emit({
528
+ status: 'stream', result: null,
529
+ streamEvent: { eventType: 'status', statusText: `API 重试中 (${attempt}/${max}),${delaySec}s 后重试` },
530
+ });
531
+ return true;
532
+ }
533
+ // task_started / task_progress — emit a status event to keep stdout activity alive.
534
+ // Without this, long-running tasks produce no stdout output, and the host's
535
+ // stuck-runner detector may kill the process after 6 minutes of silence.
536
+ // Also build sdkTaskId → toolUseId mapping for task_notification translation.
537
+ if (message.subtype === 'task_started' || message.subtype === 'task_progress') {
538
+ if (message.task_id && message.tool_use_id) {
539
+ this.sdkTaskIdToToolUseId.set(message.task_id, message.tool_use_id);
540
+ }
541
+ const desc = message.description || message.summary || '';
542
+ const toolName = message.last_tool_name || '';
543
+ const statusText = message.subtype === 'task_started'
544
+ ? `Task 启动: ${desc.slice(0, 80)}`
545
+ : `Task 进度${toolName ? ` [${toolName}]` : ''}: ${desc.slice(0, 80)}`;
546
+ this.emit({
547
+ status: 'stream', result: null,
548
+ streamEvent: { eventType: 'status', statusText },
549
+ });
550
+ return true;
551
+ }
552
+ return false;
553
+ }
554
+ /**
555
+ * Convenience: emit a status StreamEvent.
556
+ */
557
+ emitStatus(statusText) {
558
+ this.emit({ status: 'stream', result: null, streamEvent: { eventType: 'status', statusText } });
559
+ }
560
+ /**
561
+ * Process sub-agent messages (assistant/user with parent_tool_use_id that matches a Task).
562
+ * Returns true if the message was handled as a sub-agent message.
563
+ */
564
+ processSubAgentMessage(message) {
565
+ const msgParentToolUseId = message.parent_tool_use_id ?? null;
566
+ if (!msgParentToolUseId || !this.taskToolUseIds.has(msgParentToolUseId)) {
567
+ if (msgParentToolUseId && (message.type === 'assistant' || message.type === 'user')) {
568
+ this.log(`[WARN] Sub-agent message dropped: parent=${msgParentToolUseId.slice(0, 12)} not in taskToolUseIds=[${[...this.taskToolUseIds].map(id => id.slice(0, 12)).join(',')}]`);
569
+ }
570
+ return false;
571
+ }
572
+ if (message.type === 'assistant') {
573
+ const subContent = message.message?.content;
574
+ if (Array.isArray(subContent)) {
575
+ // End previous sub-agent active tools
576
+ const prevTools = this.activeSubAgentToolsByTask.get(msgParentToolUseId);
577
+ if (prevTools && prevTools.size > 0) {
578
+ for (const toolId of prevTools) {
579
+ this.emit({ status: 'stream', result: null,
580
+ streamEvent: { eventType: 'tool_use_end', toolUseId: toolId, parentToolUseId: msgParentToolUseId },
581
+ });
582
+ }
583
+ prevTools.clear();
584
+ }
585
+ for (const block of subContent) {
586
+ if (block.type === 'thinking' && block.thinking) {
587
+ this.emit({ status: 'stream', result: null,
588
+ streamEvent: { eventType: 'thinking_delta', text: block.thinking, parentToolUseId: msgParentToolUseId },
589
+ });
590
+ }
591
+ if (block.type === 'text' && block.text) {
592
+ this.emit({ status: 'stream', result: null,
593
+ streamEvent: { eventType: 'text_delta', text: block.text, parentToolUseId: msgParentToolUseId },
594
+ });
595
+ }
596
+ if (block.type === 'tool_use' && block.id) {
597
+ this.emit({ status: 'stream', result: null,
598
+ streamEvent: {
599
+ eventType: 'tool_use_start',
600
+ toolName: block.name || 'unknown',
601
+ toolUseId: block.id,
602
+ parentToolUseId: msgParentToolUseId,
603
+ isNested: true,
604
+ toolInputSummary: summarizeToolInput(block.input),
605
+ },
606
+ });
607
+ if (!this.activeSubAgentToolsByTask.has(msgParentToolUseId)) {
608
+ this.activeSubAgentToolsByTask.set(msgParentToolUseId, new Set());
609
+ }
610
+ this.activeSubAgentToolsByTask.get(msgParentToolUseId).add(block.id);
611
+ }
612
+ }
613
+ this.log(`[sub-agent] parent=${msgParentToolUseId.slice(0, 12)} blocks=${subContent.length} types=[${subContent.map(b => b.type).join(',')}]`);
614
+ }
615
+ }
616
+ if (message.type === 'user') {
617
+ const rawContent = message.message?.content;
618
+ if (typeof rawContent === 'string' && rawContent) {
619
+ this.emit({ status: 'stream', result: null,
620
+ streamEvent: { eventType: 'text_delta', text: rawContent, parentToolUseId: msgParentToolUseId },
621
+ });
622
+ }
623
+ else if (Array.isArray(rawContent)) {
624
+ const activeSub = this.activeSubAgentToolsByTask.get(msgParentToolUseId);
625
+ for (const block of rawContent) {
626
+ if (block.type === 'text' && block.text) {
627
+ this.emit({ status: 'stream', result: null,
628
+ streamEvent: { eventType: 'text_delta', text: block.text, parentToolUseId: msgParentToolUseId },
629
+ });
630
+ }
631
+ if (block.type === 'thinking' && block.thinking) {
632
+ this.emit({ status: 'stream', result: null,
633
+ streamEvent: { eventType: 'thinking_delta', text: block.thinking, parentToolUseId: msgParentToolUseId },
634
+ });
635
+ }
636
+ if (block.type === 'tool_result' && block.tool_use_id) {
637
+ this.emit({ status: 'stream', result: null,
638
+ streamEvent: { eventType: 'tool_use_end', toolUseId: block.tool_use_id, parentToolUseId: msgParentToolUseId },
639
+ });
640
+ activeSub?.delete(block.tool_use_id);
641
+ }
642
+ }
643
+ }
644
+ }
645
+ return true;
646
+ }
647
+ /** Check if a tool_use was already resolved by the streaming accumulator. */
648
+ isPendingResolved(pendingMap, toolUseId) {
649
+ for (const pending of pendingMap.values()) {
650
+ if (pending.toolUseId === toolUseId && pending.resolved)
651
+ return true;
652
+ }
653
+ return false;
654
+ }
655
+ /**
656
+ * Process an assistant message for Skill/Task fallback extraction and pending tracker cleanup.
657
+ */
658
+ processAssistantMessage(message) {
659
+ const content = message.message?.content;
660
+ if (!Array.isArray(content))
661
+ return;
662
+ // Fallback: extract skill name from complete assistant message
663
+ for (const block of content) {
664
+ if (block.type === 'tool_use' && block.name === 'Skill' && block.id && block.input) {
665
+ const skillName = extractSkillName(block.name, block.input);
666
+ if (skillName && !this.isPendingResolved(this.pendingSkillInput, block.id)) {
667
+ this.emit({
668
+ status: 'stream', result: null,
669
+ streamEvent: { eventType: 'tool_progress', toolName: 'Skill', toolUseId: block.id, skillName },
670
+ });
671
+ }
672
+ }
673
+ }
674
+ // Fallback: identify background Tasks and Teammate Tasks from complete input
675
+ for (const block of content) {
676
+ if (block.type === 'tool_use' && (block.name === 'Task' || block.name === 'Agent') && block.id && block.input) {
677
+ const taskInput = block.input;
678
+ if (taskInput.run_in_background === true) {
679
+ this.backgroundTaskToolUseIds.add(block.id);
680
+ this.log(`Task ${block.id.slice(0, 12)} marked as background`);
681
+ }
682
+ if (taskInput.team_name && !this.teammateTaskToolUseIds.has(block.id)) {
683
+ this.teammateTaskToolUseIds.add(block.id);
684
+ this.log(`Task ${block.id.slice(0, 12)} marked as teammate (team=${taskInput.team_name})`);
685
+ this.emit({
686
+ status: 'stream', result: null,
687
+ streamEvent: { eventType: 'task_start', toolUseId: block.id, toolName: 'Task', isTeammate: true },
688
+ });
689
+ }
690
+ }
691
+ }
692
+ // Fallback: extract AskUserQuestion input from complete assistant message
693
+ for (const block of content) {
694
+ if (block.type === 'tool_use' && block.name === 'AskUserQuestion' && block.id && block.input) {
695
+ if (!this.isPendingResolved(this.pendingAskUserInput, block.id)) {
696
+ this.emit({
697
+ status: 'stream', result: null,
698
+ streamEvent: {
699
+ eventType: 'tool_progress',
700
+ toolName: 'AskUserQuestion',
701
+ toolUseId: block.id,
702
+ toolInput: block.input,
703
+ },
704
+ });
705
+ }
706
+ }
707
+ }
708
+ // Fallback: extract TodoWrite todos from complete assistant message
709
+ for (const block of content) {
710
+ if (block.type === 'tool_use' && block.name === 'TodoWrite' && block.id && block.input) {
711
+ if (!this.isPendingResolved(this.pendingTodoInput, block.id)) {
712
+ const todoInput = block.input;
713
+ if (Array.isArray(todoInput.todos)) {
714
+ this.emit({
715
+ status: 'stream', result: null,
716
+ streamEvent: {
717
+ eventType: 'todo_update',
718
+ todos: todoInput.todos,
719
+ },
720
+ });
721
+ }
722
+ }
723
+ }
724
+ }
725
+ // Clear pending trackers to avoid memory leaks
726
+ this.pendingSkillInput.clear();
727
+ this.pendingTaskInput.clear();
728
+ this.pendingAskUserInput.clear();
729
+ this.pendingTodoInput.clear();
730
+ this.pendingGenericInput.clear();
731
+ this.sdkTaskIdToToolUseId.clear();
732
+ }
733
+ /**
734
+ * Process a task_notification system message.
735
+ * The SDK's task_id differs from the API's tool_use_id used at task creation.
736
+ * We resolve the effective toolUseId via: message.tool_use_id → sdkTaskId map → raw task_id.
737
+ */
738
+ processTaskNotification(message) {
739
+ const effectiveToolUseId = message.tool_use_id
740
+ || this.sdkTaskIdToToolUseId.get(message.task_id)
741
+ || message.task_id;
742
+ if (effectiveToolUseId !== message.task_id) {
743
+ this.log(`Task notification: sdkTaskId=${message.task_id} → toolUseId=${effectiveToolUseId} status=${message.status}`);
744
+ }
745
+ else {
746
+ this.log(`Task notification: task=${message.task_id} status=${message.status} summary=${message.summary}`);
747
+ }
748
+ this.emit({
749
+ status: 'stream', result: null,
750
+ streamEvent: {
751
+ eventType: 'task_notification',
752
+ taskId: effectiveToolUseId,
753
+ taskStatus: message.status,
754
+ taskSummary: message.summary,
755
+ isBackground: true,
756
+ },
757
+ });
758
+ this.cleanupTaskTools(effectiveToolUseId);
759
+ this.backgroundTaskToolUseIds.delete(effectiveToolUseId);
760
+ if (this.taskToolUseIds.has(effectiveToolUseId)) {
761
+ this.taskToolUseIds.delete(effectiveToolUseId);
762
+ this.emit({
763
+ status: 'stream', result: null,
764
+ streamEvent: { eventType: 'tool_use_end', toolUseId: effectiveToolUseId },
765
+ });
766
+ if (this.activeTopLevelToolUseId === effectiveToolUseId) {
767
+ this.activeTopLevelToolUseId = null;
768
+ }
769
+ }
770
+ // Clean up the mapping entry
771
+ this.sdkTaskIdToToolUseId.delete(message.task_id);
772
+ }
773
+ /**
774
+ * Process a result message. Handles flushing and returns the effective result text.
775
+ * Returns null if there's no textual result.
776
+ */
777
+ processResult(textResult) {
778
+ if (textResult) {
779
+ if (this.flushTimer) {
780
+ clearTimeout(this.flushTimer);
781
+ this.flushTimer = null;
782
+ }
783
+ this.flushBuffers();
784
+ this.seenTextualResult = true;
785
+ }
786
+ // Use fullTextAccumulator if it's more complete than SDK's result
787
+ const effectiveResult = this.fullTextAccumulator.length > (textResult?.length || 0)
788
+ ? this.fullTextAccumulator
789
+ : (textResult || null);
790
+ // Reset accumulator for next query loop
791
+ this.fullTextAccumulator = '';
792
+ return { effectiveResult, seenTextual: !!textResult };
793
+ }
794
+ /** Reset the full text accumulator (e.g., on context overflow). */
795
+ resetFullTextAccumulator() {
796
+ this.fullTextAccumulator = '';
797
+ }
798
+ /**
799
+ * Cleanup all residual state after the query loop ends.
800
+ * Must be called after the for-await loop completes or on error.
801
+ */
802
+ cleanup() {
803
+ // Cancel pending timer, then flush or clear remaining buffers
804
+ if (this.flushTimer) {
805
+ clearTimeout(this.flushTimer);
806
+ this.flushTimer = null;
807
+ }
808
+ if (this.seenTextualResult) {
809
+ // Textual result already emitted. Drop buffered tail to avoid stale residue.
810
+ this.streamBufs.clear();
811
+ }
812
+ else {
813
+ this.flushBuffers();
814
+ }
815
+ // Emit tool_use_end for active top-level tool (except Task tools)
816
+ if (this.activeTopLevelToolUseId) {
817
+ if (!this.taskToolUseIds.has(this.activeTopLevelToolUseId)) {
818
+ this.emit({
819
+ status: 'stream', result: null,
820
+ streamEvent: { eventType: 'tool_use_end', toolUseId: this.activeTopLevelToolUseId },
821
+ });
822
+ }
823
+ this.activeTopLevelToolUseId = null;
824
+ this.activeSkillToolUseId = null;
825
+ }
826
+ // Safety net: emit completion signals for pending Task tools
827
+ if (this.taskToolUseIds.size > 0) {
828
+ this.log(`[safety-net] ${this.taskToolUseIds.size} Task tools still pending: [${[...this.taskToolUseIds].map(id => id.slice(0, 12)).join(',')}]`);
829
+ }
830
+ for (const id of this.taskToolUseIds) {
831
+ if (!this.backgroundTaskToolUseIds.has(id)) {
832
+ this.log(`[safety-net] Synthesizing task_notification for Task ${id.slice(0, 12)}`);
833
+ this.cleanupTaskTools(id);
834
+ this.emit({
835
+ status: 'stream', result: null,
836
+ streamEvent: { eventType: 'task_notification', taskId: id, taskStatus: 'completed', taskSummary: '' },
837
+ });
838
+ }
839
+ this.emit({
840
+ status: 'stream', result: null,
841
+ streamEvent: { eventType: 'tool_use_end', toolUseId: id },
842
+ });
843
+ }
844
+ this.taskToolUseIds.clear();
845
+ // Clean up residual nested tool tracking
846
+ for (const [parentId, nested] of this.activeNestedToolByParent) {
847
+ this.emit({
848
+ status: 'stream', result: null,
849
+ streamEvent: { eventType: 'tool_use_end', toolUseId: nested.toolUseId, parentToolUseId: parentId },
850
+ });
851
+ }
852
+ this.activeNestedToolByParent.clear();
853
+ // Clean up residual sub-agent active tools
854
+ for (const [taskId, subTools] of this.activeSubAgentToolsByTask) {
855
+ for (const toolId of subTools) {
856
+ this.emit({ status: 'stream', result: null,
857
+ streamEvent: { eventType: 'tool_use_end', toolUseId: toolId, parentToolUseId: taskId },
858
+ });
859
+ }
860
+ }
861
+ this.activeSubAgentToolsByTask.clear();
862
+ }
863
+ /** Get the accumulated full text (for result comparison). */
864
+ getFullText() {
865
+ return this.fullTextAccumulator;
866
+ }
867
+ }