@swarmclawai/swarmclaw 0.8.4 → 0.8.7

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 (394) hide show
  1. package/README.md +9 -9
  2. package/bin/swarmclaw.js +5 -1
  3. package/bin/worker-cmd.js +73 -0
  4. package/package.json +2 -1
  5. package/src/app/api/agents/[id]/route.ts +17 -7
  6. package/src/app/api/agents/route.ts +21 -8
  7. package/src/app/api/approvals/route.test.ts +6 -6
  8. package/src/app/api/approvals/route.ts +2 -1
  9. package/src/app/api/auth/route.ts +2 -3
  10. package/src/app/api/chatrooms/[id]/chat/route.test.ts +299 -0
  11. package/src/app/api/chatrooms/[id]/chat/route.ts +3 -2
  12. package/src/app/api/chatrooms/[id]/route.ts +7 -6
  13. package/src/app/api/chats/[id]/chat/route.test.ts +496 -0
  14. package/src/app/api/chats/[id]/chat/route.ts +7 -3
  15. package/src/app/api/chats/[id]/clear/route.ts +9 -9
  16. package/src/app/api/chats/[id]/devserver/route.ts +2 -1
  17. package/src/app/api/chats/[id]/edit-resend/route.ts +3 -4
  18. package/src/app/api/chats/[id]/fork/route.ts +3 -5
  19. package/src/app/api/chats/[id]/restore/route.ts +6 -7
  20. package/src/app/api/chats/[id]/retry/route.ts +3 -4
  21. package/src/app/api/chats/[id]/route.ts +61 -62
  22. package/src/app/api/chats/route.ts +7 -1
  23. package/src/app/api/connectors/[id]/route.ts +7 -8
  24. package/src/app/api/connectors/route.ts +5 -4
  25. package/src/app/api/eval/run/route.ts +2 -1
  26. package/src/app/api/eval/suite/route.ts +2 -1
  27. package/src/app/api/external-agents/route.test.ts +1 -1
  28. package/src/app/api/external-agents/route.ts +2 -2
  29. package/src/app/api/files/serve/route.ts +1 -1
  30. package/src/app/api/gateways/[id]/route.ts +7 -5
  31. package/src/app/api/gateways/route.ts +1 -1
  32. package/src/app/api/knowledge/upload/route.ts +1 -1
  33. package/src/app/api/logs/route.ts +5 -7
  34. package/src/app/api/memory-images/[filename]/route.ts +2 -3
  35. package/src/app/api/openclaw/agent-files/route.ts +4 -3
  36. package/src/app/api/openclaw/approvals/route.ts +3 -4
  37. package/src/app/api/openclaw/config-sync/route.ts +3 -2
  38. package/src/app/api/openclaw/cron/route.ts +3 -2
  39. package/src/app/api/openclaw/dotenv-keys/route.ts +2 -1
  40. package/src/app/api/openclaw/exec-config/route.ts +3 -2
  41. package/src/app/api/openclaw/gateway/route.ts +5 -4
  42. package/src/app/api/openclaw/history/route.ts +3 -2
  43. package/src/app/api/openclaw/media/route.ts +2 -1
  44. package/src/app/api/openclaw/permissions/route.ts +3 -2
  45. package/src/app/api/openclaw/sandbox-env/route.ts +3 -2
  46. package/src/app/api/openclaw/skills/install/route.ts +2 -1
  47. package/src/app/api/openclaw/skills/remove/route.ts +2 -1
  48. package/src/app/api/openclaw/skills/route.ts +3 -2
  49. package/src/app/api/orchestrator/run/route.ts +5 -14
  50. package/src/app/api/perf/route.ts +43 -0
  51. package/src/app/api/plugins/dependencies/route.ts +2 -1
  52. package/src/app/api/plugins/install/route.ts +2 -1
  53. package/src/app/api/plugins/marketplace/route.ts +3 -2
  54. package/src/app/api/plugins/settings/route.ts +2 -1
  55. package/src/app/api/preview-server/route.ts +11 -10
  56. package/src/app/api/projects/[id]/route.ts +1 -1
  57. package/src/app/api/schedules/[id]/route.test.ts +128 -0
  58. package/src/app/api/schedules/[id]/route.ts +43 -43
  59. package/src/app/api/schedules/[id]/run/route.ts +11 -62
  60. package/src/app/api/schedules/route.ts +21 -87
  61. package/src/app/api/settings/route.ts +2 -0
  62. package/src/app/api/setup/doctor/route.ts +9 -8
  63. package/src/app/api/tasks/[id]/approve/route.ts +33 -30
  64. package/src/app/api/tasks/[id]/route.ts +12 -35
  65. package/src/app/api/tasks/import/github/route.ts +2 -1
  66. package/src/app/api/tasks/route.ts +79 -91
  67. package/src/app/api/wallets/[id]/approve/route.ts +2 -1
  68. package/src/app/api/wallets/[id]/route.ts +13 -19
  69. package/src/app/api/wallets/[id]/send/route.ts +2 -1
  70. package/src/app/api/wallets/route.ts +2 -1
  71. package/src/app/api/webhooks/[id]/route.ts +2 -1
  72. package/src/app/api/webhooks/route.test.ts +3 -1
  73. package/src/app/page.tsx +23 -331
  74. package/src/cli/index.js +19 -0
  75. package/src/cli/index.ts +38 -7
  76. package/src/cli/spec.js +9 -0
  77. package/src/components/activity/activity-feed.tsx +7 -4
  78. package/src/components/agents/agent-card.tsx +32 -6
  79. package/src/components/agents/agent-chat-list.tsx +55 -22
  80. package/src/components/agents/agent-files-editor.tsx +3 -2
  81. package/src/components/agents/agent-sheet.tsx +123 -22
  82. package/src/components/agents/inspector-panel.tsx +1 -1
  83. package/src/components/agents/openclaw-skills-panel.tsx +2 -1
  84. package/src/components/agents/trash-list.tsx +1 -1
  85. package/src/components/auth/access-key-gate.tsx +8 -2
  86. package/src/components/auth/setup-wizard.tsx +10 -9
  87. package/src/components/auth/user-picker.tsx +3 -2
  88. package/src/components/chat/chat-area.tsx +20 -1
  89. package/src/components/chat/chat-card.tsx +18 -3
  90. package/src/components/chat/chat-header.tsx +24 -4
  91. package/src/components/chat/chat-list.tsx +2 -11
  92. package/src/components/chat/heartbeat-history-panel.tsx +2 -1
  93. package/src/components/chat/message-bubble.tsx +45 -6
  94. package/src/components/chat/message-list.tsx +280 -145
  95. package/src/components/chat/streaming-bubble.tsx +217 -60
  96. package/src/components/chat/swarm-panel.test.ts +274 -0
  97. package/src/components/chat/swarm-panel.tsx +410 -0
  98. package/src/components/chat/swarm-status-card.tsx +346 -0
  99. package/src/components/chat/tool-call-bubble.tsx +48 -23
  100. package/src/components/chatrooms/chatroom-list.tsx +8 -5
  101. package/src/components/chatrooms/chatroom-message.tsx +10 -7
  102. package/src/components/chatrooms/chatroom-view.tsx +12 -9
  103. package/src/components/connectors/connector-health.tsx +6 -4
  104. package/src/components/connectors/connector-list.tsx +16 -11
  105. package/src/components/connectors/connector-sheet.tsx +12 -6
  106. package/src/components/home/home-view.tsx +38 -24
  107. package/src/components/input/chat-input.tsx +10 -1
  108. package/src/components/layout/app-layout.tsx +2 -38
  109. package/src/components/layout/sheet-layer.tsx +50 -0
  110. package/src/components/mcp-servers/mcp-server-list.tsx +37 -5
  111. package/src/components/mcp-servers/mcp-server-sheet.tsx +12 -2
  112. package/src/components/plugins/plugin-list.tsx +8 -4
  113. package/src/components/plugins/plugin-sheet.tsx +2 -1
  114. package/src/components/providers/provider-list.tsx +3 -2
  115. package/src/components/providers/provider-sheet.tsx +2 -1
  116. package/src/components/runs/run-list.tsx +11 -7
  117. package/src/components/schedules/schedule-card.tsx +5 -3
  118. package/src/components/shared/agent-switch-dialog.tsx +1 -1
  119. package/src/components/shared/attachment-chip.tsx +19 -3
  120. package/src/components/shared/notification-center.tsx +6 -3
  121. package/src/components/shared/settings/plugin-manager.tsx +3 -2
  122. package/src/components/shared/settings/section-embedding.tsx +2 -1
  123. package/src/components/shared/settings/section-orchestrator.tsx +2 -1
  124. package/src/components/shared/settings/section-user-preferences.tsx +107 -0
  125. package/src/components/shared/settings/settings-page.tsx +13 -9
  126. package/src/components/skills/clawhub-browser.tsx +15 -4
  127. package/src/components/skills/skill-list.tsx +15 -4
  128. package/src/components/tasks/approvals-panel.tsx +2 -1
  129. package/src/components/tasks/task-board.tsx +35 -37
  130. package/src/components/tasks/task-sheet.tsx +4 -3
  131. package/src/components/ui/full-screen-loader.tsx +164 -0
  132. package/src/components/wallets/wallet-approval-dialog.tsx +2 -1
  133. package/src/components/wallets/wallet-panel.tsx +6 -5
  134. package/src/components/wallets/wallet-section.tsx +3 -2
  135. package/src/components/webhooks/webhook-list.tsx +4 -5
  136. package/src/components/webhooks/webhook-sheet.tsx +6 -6
  137. package/src/hooks/use-app-bootstrap.ts +202 -0
  138. package/src/hooks/use-mounted-ref.ts +14 -0
  139. package/src/hooks/use-now.ts +31 -0
  140. package/src/hooks/use-openclaw-gateway.ts +2 -1
  141. package/src/instrumentation.ts +20 -8
  142. package/src/lib/agent-default-tools.test.ts +52 -0
  143. package/src/lib/agent-default-tools.ts +40 -0
  144. package/src/lib/api-client.test.ts +21 -0
  145. package/src/lib/api-client.ts +6 -11
  146. package/src/lib/canvas-content.test.ts +360 -0
  147. package/src/lib/chat-streaming-state.test.ts +49 -2
  148. package/src/lib/chat-streaming-state.ts +26 -10
  149. package/src/lib/fetch-timeout.test.ts +54 -0
  150. package/src/lib/fetch-timeout.ts +60 -3
  151. package/src/lib/live-tool-events.test.ts +77 -0
  152. package/src/lib/live-tool-events.ts +73 -0
  153. package/src/lib/local-observability.test.ts +2 -2
  154. package/src/lib/openclaw-endpoint.test.ts +1 -1
  155. package/src/lib/providers/anthropic.ts +12 -16
  156. package/src/lib/providers/index.ts +4 -2
  157. package/src/lib/providers/ollama.ts +9 -6
  158. package/src/lib/providers/openai.ts +11 -14
  159. package/src/lib/runtime-env.test.ts +8 -8
  160. package/src/lib/schedule-dedupe-advanced.test.ts +2 -2
  161. package/src/lib/schedule-dedupe.test.ts +1 -1
  162. package/src/lib/schedule-dedupe.ts +3 -2
  163. package/src/lib/server/agent-thread-session.test.ts +6 -6
  164. package/src/lib/server/agent-thread-session.ts +6 -9
  165. package/src/lib/server/alert-dispatch.ts +2 -1
  166. package/src/lib/server/api-routes.test.ts +6 -6
  167. package/src/lib/server/approval-connector-notify.test.ts +4 -4
  168. package/src/lib/server/approvals-auto-approve.test.ts +29 -29
  169. package/src/lib/server/approvals.test.ts +317 -0
  170. package/src/lib/server/approvals.ts +5 -4
  171. package/src/lib/server/autonomy-runtime.test.ts +11 -11
  172. package/src/lib/server/browser-state.ts +2 -2
  173. package/src/lib/server/capability-router.test.ts +1 -1
  174. package/src/lib/server/capability-router.ts +3 -2
  175. package/src/lib/server/chat-execution-advanced.test.ts +15 -2
  176. package/src/lib/server/chat-execution-connector-delivery.ts +67 -0
  177. package/src/lib/server/chat-execution-disabled.test.ts +3 -3
  178. package/src/lib/server/chat-execution-eval-history.test.ts +3 -3
  179. package/src/lib/server/chat-execution-heartbeat.test.ts +42 -1
  180. package/src/lib/server/chat-execution-session-sync.test.ts +119 -0
  181. package/src/lib/server/chat-execution-tool-events.ts +116 -0
  182. package/src/lib/server/chat-execution-utils.test.ts +479 -0
  183. package/src/lib/server/chat-execution-utils.ts +533 -0
  184. package/src/lib/server/chat-execution.ts +153 -748
  185. package/src/lib/server/chat-streaming-utils.ts +174 -0
  186. package/src/lib/server/chat-turn-tool-routing.ts +310 -0
  187. package/src/lib/server/chatroom-session-persistence.test.ts +2 -2
  188. package/src/lib/server/clawhub-client.ts +2 -1
  189. package/src/lib/server/collection-helpers.test.ts +92 -0
  190. package/src/lib/server/collection-helpers.ts +25 -3
  191. package/src/lib/server/connectors/access.ts +146 -0
  192. package/src/lib/server/connectors/bluebubbles.test.ts +1 -1
  193. package/src/lib/server/connectors/bluebubbles.ts +4 -4
  194. package/src/lib/server/connectors/commands.ts +367 -0
  195. package/src/lib/server/connectors/connector-routing.test.ts +4 -4
  196. package/src/lib/server/connectors/delivery.ts +142 -0
  197. package/src/lib/server/connectors/discord.ts +37 -40
  198. package/src/lib/server/connectors/email.ts +11 -10
  199. package/src/lib/server/connectors/googlechat.ts +4 -4
  200. package/src/lib/server/connectors/inbound-audio-transcription.ts +2 -1
  201. package/src/lib/server/connectors/ingress-delivery.ts +23 -0
  202. package/src/lib/server/connectors/manager-roundtrip.test.ts +300 -0
  203. package/src/lib/server/connectors/manager.test.ts +352 -77
  204. package/src/lib/server/connectors/manager.ts +134 -673
  205. package/src/lib/server/connectors/matrix.ts +4 -4
  206. package/src/lib/server/connectors/message-sentinel.ts +7 -0
  207. package/src/lib/server/connectors/openclaw.test.ts +1 -1
  208. package/src/lib/server/connectors/openclaw.ts +8 -10
  209. package/src/lib/server/connectors/outbox.test.ts +192 -0
  210. package/src/lib/server/connectors/outbox.ts +369 -0
  211. package/src/lib/server/connectors/pairing.test.ts +18 -1
  212. package/src/lib/server/connectors/pairing.ts +49 -4
  213. package/src/lib/server/connectors/policy.ts +9 -3
  214. package/src/lib/server/connectors/reconnect-state.ts +71 -0
  215. package/src/lib/server/connectors/response-media.ts +256 -0
  216. package/src/lib/server/connectors/runtime-state.ts +67 -0
  217. package/src/lib/server/connectors/session.test.ts +357 -0
  218. package/src/lib/server/connectors/session.ts +422 -0
  219. package/src/lib/server/connectors/signal.ts +7 -7
  220. package/src/lib/server/connectors/slack.ts +43 -43
  221. package/src/lib/server/connectors/teams.ts +4 -4
  222. package/src/lib/server/connectors/telegram.ts +37 -43
  223. package/src/lib/server/connectors/types.ts +31 -1
  224. package/src/lib/server/connectors/whatsapp.test.ts +108 -0
  225. package/src/lib/server/connectors/whatsapp.ts +106 -34
  226. package/src/lib/server/context-manager.test.ts +409 -0
  227. package/src/lib/server/cost.test.ts +1 -1
  228. package/src/lib/server/daemon-policy.ts +78 -0
  229. package/src/lib/server/daemon-state-connectors.test.ts +167 -0
  230. package/src/lib/server/daemon-state.test.ts +283 -55
  231. package/src/lib/server/daemon-state.ts +106 -109
  232. package/src/lib/server/data-dir.test.ts +5 -5
  233. package/src/lib/server/data-dir.ts +4 -0
  234. package/src/lib/server/delegation-jobs-advanced.test.ts +1 -1
  235. package/src/lib/server/delegation-jobs.test.ts +87 -0
  236. package/src/lib/server/delegation-jobs.ts +42 -48
  237. package/src/lib/server/devserver-launch.ts +1 -1
  238. package/src/lib/server/document-utils.ts +7 -9
  239. package/src/lib/server/elevenlabs.ts +2 -1
  240. package/src/lib/server/embeddings.test.ts +105 -0
  241. package/src/lib/server/ethereum.ts +3 -2
  242. package/src/lib/server/eval/agent-regression.ts +3 -2
  243. package/src/lib/server/eval/runner.ts +2 -1
  244. package/src/lib/server/eval/scorer.ts +2 -1
  245. package/src/lib/server/evm-swap.ts +2 -1
  246. package/src/lib/server/gateway/protocol.test.ts +1 -1
  247. package/src/lib/server/guardian.ts +2 -1
  248. package/src/lib/server/heartbeat-blocked-suppression.test.ts +151 -0
  249. package/src/lib/server/heartbeat-service-timer.test.ts +6 -6
  250. package/src/lib/server/heartbeat-service.test.ts +406 -0
  251. package/src/lib/server/heartbeat-service.ts +54 -7
  252. package/src/lib/server/heartbeat-wake.test.ts +19 -0
  253. package/src/lib/server/heartbeat-wake.ts +17 -16
  254. package/src/lib/server/integrity-monitor.test.ts +149 -0
  255. package/src/lib/server/json-utils.ts +22 -0
  256. package/src/lib/server/knowledge-db.test.ts +13 -13
  257. package/src/lib/server/link-understanding.ts +2 -1
  258. package/src/lib/server/llm-response-cache.test.ts +1 -1
  259. package/src/lib/server/main-agent-loop-advanced.test.ts +65 -3
  260. package/src/lib/server/main-agent-loop.test.ts +6 -6
  261. package/src/lib/server/main-agent-loop.ts +21 -7
  262. package/src/lib/server/mcp-client.test.ts +1 -1
  263. package/src/lib/server/mcp-conformance.test.ts +1 -1
  264. package/src/lib/server/mcp-conformance.ts +3 -2
  265. package/src/lib/server/memory-consolidation.ts +2 -1
  266. package/src/lib/server/memory-db.test.ts +485 -0
  267. package/src/lib/server/memory-db.ts +39 -26
  268. package/src/lib/server/memory-graph.test.ts +2 -2
  269. package/src/lib/server/memory-policy.test.ts +7 -7
  270. package/src/lib/server/memory-retrieval.test.ts +1 -1
  271. package/src/lib/server/openclaw-config-sync.ts +2 -1
  272. package/src/lib/server/openclaw-deploy.test.ts +1 -1
  273. package/src/lib/server/openclaw-deploy.ts +8 -12
  274. package/src/lib/server/openclaw-exec-config.ts +2 -1
  275. package/src/lib/server/openclaw-gateway.ts +6 -7
  276. package/src/lib/server/openclaw-skills-normalize.ts +2 -1
  277. package/src/lib/server/openclaw-sync.ts +7 -5
  278. package/src/lib/server/orchestrator-lg-structure.test.ts +17 -0
  279. package/src/lib/server/orchestrator-lg.ts +199 -327
  280. package/src/lib/server/path-utils.ts +31 -0
  281. package/src/lib/server/perf.ts +161 -0
  282. package/src/lib/server/plugins-approval-guidance.ts +115 -0
  283. package/src/lib/server/plugins.test.ts +1 -1
  284. package/src/lib/server/plugins.ts +22 -132
  285. package/src/lib/server/process-manager.ts +5 -8
  286. package/src/lib/server/provider-health.test.ts +137 -0
  287. package/src/lib/server/provider-health.ts +3 -3
  288. package/src/lib/server/provider-model-discovery.ts +3 -12
  289. package/src/lib/server/queue-followups.test.ts +9 -9
  290. package/src/lib/server/queue-reconcile.test.ts +2 -2
  291. package/src/lib/server/queue-recovery.test.ts +269 -0
  292. package/src/lib/server/queue.test.ts +570 -0
  293. package/src/lib/server/queue.ts +62 -455
  294. package/src/lib/server/resolve-image.ts +30 -0
  295. package/src/lib/server/runtime-settings.test.ts +4 -4
  296. package/src/lib/server/runtime-storage-write-paths.test.ts +60 -0
  297. package/src/lib/server/schedule-normalization.test.ts +279 -0
  298. package/src/lib/server/schedule-service.ts +263 -0
  299. package/src/lib/server/scheduler.ts +17 -74
  300. package/src/lib/server/session-mailbox.test.ts +191 -0
  301. package/src/lib/server/session-run-manager.test.ts +640 -0
  302. package/src/lib/server/session-run-manager.ts +59 -15
  303. package/src/lib/server/session-tools/autonomy-tools.test.ts +20 -20
  304. package/src/lib/server/session-tools/calendar.ts +2 -1
  305. package/src/lib/server/session-tools/canvas.ts +2 -1
  306. package/src/lib/server/session-tools/chatroom.ts +2 -1
  307. package/src/lib/server/session-tools/connector.ts +26 -28
  308. package/src/lib/server/session-tools/context-mgmt.ts +3 -2
  309. package/src/lib/server/session-tools/crawl.ts +4 -3
  310. package/src/lib/server/session-tools/crud.ts +105 -324
  311. package/src/lib/server/session-tools/delegate-fallback.test.ts +9 -9
  312. package/src/lib/server/session-tools/delegate.ts +6 -8
  313. package/src/lib/server/session-tools/discovery-approvals.test.ts +15 -15
  314. package/src/lib/server/session-tools/discovery.ts +4 -3
  315. package/src/lib/server/session-tools/document.ts +2 -1
  316. package/src/lib/server/session-tools/email.ts +2 -1
  317. package/src/lib/server/session-tools/extract.ts +2 -1
  318. package/src/lib/server/session-tools/file.ts +4 -3
  319. package/src/lib/server/session-tools/http.ts +2 -1
  320. package/src/lib/server/session-tools/human-loop.ts +2 -1
  321. package/src/lib/server/session-tools/image-gen.ts +4 -3
  322. package/src/lib/server/session-tools/index.ts +26 -30
  323. package/src/lib/server/session-tools/mailbox.ts +2 -1
  324. package/src/lib/server/session-tools/manage-connectors.test.ts +4 -4
  325. package/src/lib/server/session-tools/manage-schedules.test.ts +12 -12
  326. package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +5 -5
  327. package/src/lib/server/session-tools/manage-tasks.test.ts +2 -2
  328. package/src/lib/server/session-tools/monitor.ts +2 -1
  329. package/src/lib/server/session-tools/platform.ts +2 -1
  330. package/src/lib/server/session-tools/plugin-creator.ts +2 -1
  331. package/src/lib/server/session-tools/replicate.ts +3 -2
  332. package/src/lib/server/session-tools/session-tools-wiring.test.ts +6 -6
  333. package/src/lib/server/session-tools/shell.ts +4 -9
  334. package/src/lib/server/session-tools/subagent.ts +322 -170
  335. package/src/lib/server/session-tools/table.ts +6 -5
  336. package/src/lib/server/session-tools/wallet-tool.test.ts +3 -3
  337. package/src/lib/server/session-tools/wallet.ts +7 -6
  338. package/src/lib/server/session-tools/web-browser-config.test.ts +1 -0
  339. package/src/lib/server/session-tools/web-utils.ts +317 -0
  340. package/src/lib/server/session-tools/web.ts +62 -328
  341. package/src/lib/server/skill-prompt-budget.test.ts +1 -1
  342. package/src/lib/server/skills-normalize.ts +2 -1
  343. package/src/lib/server/storage-item-access.test.ts +302 -0
  344. package/src/lib/server/storage.ts +366 -314
  345. package/src/lib/server/stream-agent-chat.test.ts +82 -3
  346. package/src/lib/server/stream-agent-chat.ts +146 -510
  347. package/src/lib/server/stream-continuation.ts +412 -0
  348. package/src/lib/server/subagent-lineage.test.ts +647 -0
  349. package/src/lib/server/subagent-lineage.ts +435 -0
  350. package/src/lib/server/subagent-runtime.test.ts +484 -0
  351. package/src/lib/server/subagent-runtime.ts +419 -0
  352. package/src/lib/server/subagent-swarm.test.ts +391 -0
  353. package/src/lib/server/subagent-swarm.ts +564 -0
  354. package/src/lib/server/system-events.ts +3 -3
  355. package/src/lib/server/task-followups.test.ts +491 -0
  356. package/src/lib/server/task-followups.ts +391 -0
  357. package/src/lib/server/task-lifecycle.test.ts +205 -0
  358. package/src/lib/server/task-lifecycle.ts +200 -0
  359. package/src/lib/server/task-quality-gate.test.ts +1 -1
  360. package/src/lib/server/task-resume.ts +208 -0
  361. package/src/lib/server/task-service.test.ts +108 -0
  362. package/src/lib/server/task-service.ts +264 -0
  363. package/src/lib/server/task-validation.test.ts +1 -1
  364. package/src/lib/server/test-utils/run-with-temp-data-dir.ts +42 -0
  365. package/src/lib/server/tool-capability-policy.test.ts +2 -2
  366. package/src/lib/server/tool-capability-policy.ts +3 -2
  367. package/src/lib/server/tool-planning.ts +2 -1
  368. package/src/lib/server/tool-retry.ts +2 -3
  369. package/src/lib/server/wake-dispatcher.test.ts +303 -0
  370. package/src/lib/server/wake-dispatcher.ts +318 -0
  371. package/src/lib/server/wake-mode.test.ts +161 -0
  372. package/src/lib/server/wake-mode.ts +174 -0
  373. package/src/lib/server/wallet-service.ts +8 -9
  374. package/src/lib/server/watch-jobs.ts +2 -1
  375. package/src/lib/server/workspace-context.ts +2 -2
  376. package/src/lib/shared-utils.test.ts +142 -0
  377. package/src/lib/shared-utils.ts +62 -0
  378. package/src/lib/tool-event-summary.ts +2 -1
  379. package/src/lib/view-routes.test.ts +100 -0
  380. package/src/lib/wallet.test.ts +322 -6
  381. package/src/proxy.test.ts +4 -4
  382. package/src/proxy.ts +2 -3
  383. package/src/stores/set-if-changed.ts +40 -0
  384. package/src/stores/slices/agent-slice.ts +111 -0
  385. package/src/stores/slices/auth-slice.ts +25 -0
  386. package/src/stores/slices/data-slice.ts +301 -0
  387. package/src/stores/slices/index.ts +7 -0
  388. package/src/stores/slices/session-slice.ts +112 -0
  389. package/src/stores/slices/task-slice.ts +63 -0
  390. package/src/stores/slices/ui-slice.ts +192 -0
  391. package/src/stores/use-app-store.ts +17 -822
  392. package/src/stores/use-approval-store.ts +2 -1
  393. package/src/stores/use-chat-store.ts +8 -1
  394. package/src/types/index.ts +10 -0
@@ -4,6 +4,7 @@ import type { Plugin, PluginHooks } from '@/types'
4
4
  import { getPluginManager } from '../plugins'
5
5
  import { normalizeToolInputArgs } from './normalize-tool-args'
6
6
  import type { ToolBuildContext } from './context'
7
+ import { errorMessage, sleep } from '@/lib/shared-utils'
7
8
 
8
9
  interface ReplicateConfig {
9
10
  apiToken: string
@@ -71,7 +72,7 @@ function formatPrediction(p: Record<string, unknown>): Record<string, unknown> {
71
72
  async function pollPrediction(token: string, predictionId: string, cfg: ReplicateConfig): Promise<Record<string, unknown>> {
72
73
  const deadline = Date.now() + cfg.timeoutMs
73
74
  while (Date.now() < deadline) {
74
- await new Promise((r) => setTimeout(r, cfg.pollingIntervalMs))
75
+ await sleep(cfg.pollingIntervalMs)
75
76
  const r = await replicateRequest('GET', `/predictions/${predictionId}`, token)
76
77
  if (!r.ok) return { status: 'failed', error: r.error }
77
78
  const prediction = r.data as Record<string, unknown>
@@ -206,7 +207,7 @@ async function executeReplicate(args: Record<string, unknown>): Promise<string>
206
207
  return `Error: Unknown action "${action}". Use: run, get, cancel, get_model, search, status.`
207
208
  }
208
209
  } catch (err: unknown) {
209
- return `Error: ${err instanceof Error ? err.message : String(err)}`
210
+ return `Error: ${errorMessage(err)}`
210
211
  }
211
212
  }
212
213
 
@@ -107,7 +107,7 @@ describe('memory tool knowledge actions (source verification)', () => {
107
107
  it('action enum in memory.ts includes the declared base actions', async () => {
108
108
  const fs = await import('fs')
109
109
  const src = fs.readFileSync(
110
- new URL('./memory.ts', import.meta.url).pathname,
110
+ new URL('./memory', import.meta.url).pathname,
111
111
  'utf-8',
112
112
  )
113
113
 
@@ -127,7 +127,7 @@ describe('memory tool knowledge actions (source verification)', () => {
127
127
  it('action enum does not advertise removed knowledge actions', async () => {
128
128
  const fs = await import('fs')
129
129
  const src = fs.readFileSync(
130
- new URL('./memory.ts', import.meta.url).pathname,
130
+ new URL('./memory', import.meta.url).pathname,
131
131
  'utf-8',
132
132
  )
133
133
 
@@ -142,7 +142,7 @@ describe('memory tool knowledge actions (source verification)', () => {
142
142
  it('declares the narrow OpenClaw-style memory tool names', async () => {
143
143
  const fs = await import('fs')
144
144
  const src = fs.readFileSync(
145
- new URL('./memory.ts', import.meta.url).pathname,
145
+ new URL('./memory', import.meta.url).pathname,
146
146
  'utf-8',
147
147
  )
148
148
 
@@ -176,7 +176,7 @@ describe('MCP tool block type wiring', () => {
176
176
  it('index.ts source has MCP tool block gated on mcpServerIds', async () => {
177
177
  const fs = await import('fs')
178
178
  const src = fs.readFileSync(
179
- new URL('./index.ts', import.meta.url).pathname,
179
+ new URL('./index', import.meta.url).pathname,
180
180
  'utf-8',
181
181
  )
182
182
  assert.ok(src.includes('mcpServerIds'), 'index.ts should reference mcpServerIds')
@@ -199,8 +199,8 @@ describe('context utility functions', () => {
199
199
 
200
200
  it('safePath allows valid paths', async () => {
201
201
  const { safePath } = await import('./context')
202
- const result = safePath('/home/user/project', 'src/index.ts')
203
- assert.equal(result, '/home/user/project/src/index.ts')
202
+ const result = safePath('/home/user/project', 'src/index')
203
+ assert.equal(result, '/home/user/project/src/index')
204
204
  })
205
205
 
206
206
  it('truncate respects max length', async () => {
@@ -15,7 +15,9 @@ import type { ToolBuildContext } from './context'
15
15
  import { safePath, truncate, coerceEnvMap, MAX_OUTPUT } from './context'
16
16
  import type { Plugin, PluginHooks } from '@/types'
17
17
  import { getPluginManager } from '../plugins'
18
+ import { safeJsonParseObject } from '../json-utils'
18
19
  import { normalizeToolInputArgs } from './normalize-tool-args'
20
+ import { errorMessage } from '@/lib/shared-utils'
19
21
 
20
22
  function resolveShellWorkdir(baseCwd: string, requestedWorkdir?: string): string {
21
23
  const raw = typeof requestedWorkdir === 'string' ? requestedWorkdir.trim() : ''
@@ -54,14 +56,7 @@ function asRecord(value: unknown): Record<string, unknown> | null {
54
56
  }
55
57
 
56
58
  function parseNestedInput(raw: unknown): Record<string, unknown> | null {
57
- if (typeof raw === 'string') {
58
- try {
59
- return asRecord(JSON.parse(raw))
60
- } catch {
61
- return null
62
- }
63
- }
64
- return asRecord(raw)
59
+ return typeof raw === 'string' ? safeJsonParseObject(raw) : asRecord(raw)
65
60
  }
66
61
 
67
62
  function pickString(...values: unknown[]): string | undefined {
@@ -171,7 +166,7 @@ async function executeShellAction(args: Record<string, unknown>, bctx: { cwd: st
171
166
  default: return `Error: Unknown action "${action}"`
172
167
  }
173
168
  } catch (err: unknown) {
174
- return `Error: ${err instanceof Error ? err.message : String(err)}`
169
+ return `Error: ${errorMessage(err)}`
175
170
  }
176
171
  }
177
172
 
@@ -1,45 +1,33 @@
1
1
  import { z } from 'zod'
2
2
  import { tool, type StructuredToolInterface } from '@langchain/core/tools'
3
- import { genId } from '@/lib/id'
4
- import { DEFAULT_DELEGATION_MAX_DEPTH } from '@/lib/runtime-loop'
5
- import { loadAgents, loadSessions, saveSessions } from '../storage'
6
- import { enqueueSessionRun } from '../session-run-manager'
7
- import { loadRuntimeSettings } from '../runtime-settings'
8
3
  import type { ToolBuildContext } from './context'
9
4
  import type { Plugin, PluginHooks } from '@/types'
10
5
  import { getPluginManager } from '../plugins'
11
6
  import { normalizeToolInputArgs } from './normalize-tool-args'
12
- import { applyResolvedRoute, resolvePrimaryAgentRoute } from '../agent-runtime-config'
7
+ import { errorMessage, sleep } from '@/lib/shared-utils'
13
8
  import {
14
- appendDelegationCheckpoint,
15
9
  cancelDelegationJob,
16
- completeDelegationJob,
17
- createDelegationJob,
18
- failDelegationJob,
19
10
  getDelegationJob,
20
11
  listDelegationJobs,
21
12
  recoverStaleDelegationJobs,
22
- registerDelegationRuntime,
23
- startDelegationJob,
24
13
  } from '../delegation-jobs'
25
-
26
- function getSessionDepth(sessionId: string | undefined, maxDepth: number): number {
27
- if (!sessionId) return 0
28
- const sessions = loadSessions()
29
- let depth = 0
30
- let current = sessionId
31
- while (current && depth < maxDepth + 1) {
32
- const session = sessions[current]
33
- if (!session?.parentSessionId) break
34
- current = session.parentSessionId as string
35
- depth++
36
- }
37
- return depth
38
- }
39
-
40
- function sleep(ms: number) {
41
- return new Promise((resolve) => setTimeout(resolve, ms))
42
- }
14
+ import {
15
+ spawnSubagent,
16
+ getHandle,
17
+ getLineageNodeBySession,
18
+ getAncestors,
19
+ getChildren,
20
+ buildLineageTree,
21
+ cancelSubagentBySession,
22
+ } from '../subagent-runtime'
23
+ import {
24
+ spawnSwarm,
25
+ getSwarm,
26
+ getSwarmSnapshot,
27
+ listSwarms,
28
+ aggregateResults,
29
+ waitForAll,
30
+ } from '../subagent-swarm'
43
31
 
44
32
  export function resolveSubagentBrowserProfileId(
45
33
  parentSession: Record<string, unknown> | null | undefined,
@@ -55,86 +43,44 @@ export function resolveSubagentBrowserProfileId(
55
43
  return inherited || childSessionId
56
44
  }
57
45
 
58
- async function startSubagentJob(jobId: string, args: {
59
- agentId: string
60
- message: string
61
- cwd?: string
62
- shareBrowserProfile?: boolean
63
- }, context: { sessionId?: string; cwd: string }) {
64
- const runtime = loadRuntimeSettings()
65
- const maxDepth = runtime.delegationMaxDepth || DEFAULT_DELEGATION_MAX_DEPTH
66
- const agents = loadAgents()
67
- const agent = agents[args.agentId]
68
- if (!agent) throw new Error(`Agent "${args.agentId}" not found.`)
69
-
70
- const depth = getSessionDepth(context.sessionId, maxDepth)
71
- if (depth >= maxDepth) throw new Error('Max subagent depth reached.')
72
-
73
- const sid = genId()
74
- const now = Date.now()
75
- const sessions = loadSessions()
76
- const parent = context.sessionId ? sessions[context.sessionId] : null
77
- const browserProfileId = resolveSubagentBrowserProfileId(parent, sid, args.shareBrowserProfile === true)
78
- const nextSession = {
79
- id: sid,
80
- name: `subagent-${agent.name}`,
81
- cwd: args.cwd || context.cwd,
82
- user: 'agent',
83
- provider: agent.provider,
84
- model: agent.model,
85
- credentialId: agent.credentialId || null,
86
- messages: [],
87
- createdAt: now,
88
- lastActiveAt: now,
89
- sessionType: 'orchestrated',
90
- agentId: agent.id,
91
- parentSessionId: context.sessionId || null,
92
- plugins: agent.plugins || agent.tools || [],
93
- browserProfileId,
94
- }
95
- sessions[sid] = applyResolvedRoute(nextSession, resolvePrimaryAgentRoute(agent))
96
- saveSessions(sessions)
97
-
98
- startDelegationJob(jobId, {
99
- childSessionId: sid,
100
- agentId: agent.id,
101
- agentName: agent.name,
102
- cwd: args.cwd || context.cwd,
103
- })
104
- appendDelegationCheckpoint(jobId, `Created child session ${sid}`, 'running')
105
-
106
- const run = enqueueSessionRun({
107
- sessionId: sid,
108
- message: args.message,
109
- internal: true,
110
- source: 'subagent',
111
- mode: 'followup',
112
- })
46
+ // ---------------------------------------------------------------------------
47
+ // Action context & helpers
48
+ // ---------------------------------------------------------------------------
113
49
 
114
- registerDelegationRuntime(jobId, {
115
- cancel: () => run.abort(),
116
- })
117
-
118
- run.promise
119
- .then((result) => {
120
- const latest = getDelegationJob(jobId)
121
- if (latest?.status === 'cancelled') return
122
- appendDelegationCheckpoint(jobId, 'Child session completed', 'completed')
123
- completeDelegationJob(jobId, result.text.slice(0, 32_000), { childSessionId: sid })
124
- })
125
- .catch((err: unknown) => {
126
- const message = err instanceof Error ? err.message : String(err)
127
- const latest = getDelegationJob(jobId)
128
- if (latest?.status === 'cancelled') return
129
- appendDelegationCheckpoint(jobId, `Child session failed: ${message}`, 'failed')
130
- failDelegationJob(jobId, message, { childSessionId: sid })
131
- })
50
+ interface ActionContext {
51
+ sessionId?: string
52
+ cwd: string
53
+ }
132
54
 
133
- return { run, sid, agent }
55
+ function requireString(args: Record<string, unknown>, key: string): string {
56
+ const val = typeof args[key] === 'string' ? (args[key] as string).trim() : ''
57
+ if (!val) throw new Error(`${key} is required.`)
58
+ return val
134
59
  }
135
60
 
136
- async function waitForSubagentJob(jobId: string, timeoutSec = 30): Promise<string> {
137
- const timeoutAt = Date.now() + Math.max(1, timeoutSec) * 1000
61
+ // ---------------------------------------------------------------------------
62
+ // Promise-based wait (no polling when handle exists)
63
+ // ---------------------------------------------------------------------------
64
+
65
+ async function waitForJob(jobId: string, timeoutSec = 30): Promise<string> {
66
+ const timeoutMs = Math.max(1, timeoutSec) * 1000
67
+
68
+ // Try handle-based wait first (instant if already resolved)
69
+ const handle = getHandle(jobId)
70
+ if (handle) {
71
+ const result = await Promise.race([
72
+ handle.promise,
73
+ sleep(timeoutMs).then(() => null),
74
+ ])
75
+ if (result) return JSON.stringify(result)
76
+ // Timed out — return current job state with explicit timeout indicator
77
+ const job = getDelegationJob(jobId)
78
+ if (job) return JSON.stringify({ ...job, _timedOut: true })
79
+ return `Error: delegation job "${jobId}" not found.`
80
+ }
81
+
82
+ // Legacy fallback: poll delegation job store
83
+ const timeoutAt = Date.now() + timeoutMs
138
84
  while (Date.now() < timeoutAt) {
139
85
  const job = getDelegationJob(jobId)
140
86
  if (!job) return `Error: delegation job "${jobId}" not found.`
@@ -147,76 +93,246 @@ async function waitForSubagentJob(jobId: string, timeoutSec = 30): Promise<strin
147
93
  return latest ? JSON.stringify(latest) : `Error: delegation job "${jobId}" not found.`
148
94
  }
149
95
 
150
- /**
151
- * Core Subagent Execution Logic
152
- */
153
- async function executeSubagentAction(args: any, context: { sessionId?: string; cwd: string }) {
154
- const normalized = normalizeToolInputArgs((args ?? {}) as Record<string, unknown>)
155
- const action = String(normalized.action || '').trim().toLowerCase()
156
- const agentId = (normalized.agentId ?? normalized.agent_id) as string | undefined
157
- const message = normalized.message as string | undefined
158
- const cwd = normalized.cwd as string | undefined
159
- const shareBrowserProfile = normalized.shareBrowserProfile === true || normalized.share_browser_profile === true
160
- const jobId = typeof normalized.jobId === 'string' ? normalized.jobId.trim() : ''
161
- const waitForCompletion = normalized.waitForCompletion !== false && normalized.background !== true
96
+ // ---------------------------------------------------------------------------
97
+ // Action handlers (dispatch map)
98
+ // ---------------------------------------------------------------------------
162
99
 
163
- recoverStaleDelegationJobs()
100
+ async function handleStatus(args: Record<string, unknown>): Promise<string> {
101
+ const jobId = requireString(args, 'jobId')
102
+ const job = getDelegationJob(jobId)
103
+ if (!job) return `Error: delegation job "${jobId}" not found.`
104
+ const lineage = job.childSessionId ? getLineageNodeBySession(job.childSessionId) : null
105
+ return JSON.stringify({
106
+ ...job,
107
+ lineage: lineage ? {
108
+ id: lineage.id,
109
+ depth: lineage.depth,
110
+ status: lineage.status,
111
+ parentSessionId: lineage.parentSessionId,
112
+ childCount: getChildren(lineage.id).length,
113
+ ancestors: getAncestors(lineage.id).map((a) => ({ id: a.id, agentName: a.agentName, depth: a.depth })),
114
+ } : null,
115
+ })
116
+ }
164
117
 
165
- try {
166
- if (action === 'status') {
167
- if (!jobId) return 'Error: jobId is required.'
168
- const job = getDelegationJob(jobId)
169
- return job ? JSON.stringify(job) : `Error: delegation job "${jobId}" not found.`
170
- }
171
- if (action === 'list') {
172
- return JSON.stringify(listDelegationJobs({ parentSessionId: context.sessionId || null }))
173
- }
174
- if (action === 'cancel') {
175
- if (!jobId) return 'Error: jobId is required.'
176
- const job = cancelDelegationJob(jobId)
177
- return job ? JSON.stringify(job) : `Error: delegation job "${jobId}" not found.`
178
- }
179
- if (action === 'wait') {
180
- if (!jobId) return 'Error: jobId is required.'
181
- const timeoutSec = typeof normalized.timeoutSec === 'number' ? normalized.timeoutSec : 30
182
- return waitForSubagentJob(jobId, timeoutSec)
183
- }
118
+ function handleList(_args: Record<string, unknown>, ctx: ActionContext): string {
119
+ return JSON.stringify(listDelegationJobs({ parentSessionId: ctx.sessionId || null }))
120
+ }
184
121
 
185
- if (!agentId) return 'Error: agentId is required.'
186
- if (!message) return 'Error: message is required.'
122
+ function handleCancel(args: Record<string, unknown>): string {
123
+ const jobId = requireString(args, 'jobId')
124
+ const job = getDelegationJob(jobId)
125
+ if (!job) return `Error: delegation job "${jobId}" not found.`
126
+ if (job.childSessionId) cancelSubagentBySession(job.childSessionId)
127
+ const cancelled = cancelDelegationJob(jobId)
128
+ return cancelled ? JSON.stringify(cancelled) : `Error: delegation job "${jobId}" not found.`
129
+ }
130
+
131
+ async function handleWait(args: Record<string, unknown>): Promise<string> {
132
+ const jobId = requireString(args, 'jobId')
133
+ const timeoutSec = typeof args.timeoutSec === 'number' ? args.timeoutSec : 30
134
+ return waitForJob(jobId, timeoutSec)
135
+ }
136
+
137
+ function handleLineage(args: Record<string, unknown>, ctx: ActionContext): string {
138
+ const targetSessionId = (args.sessionId as string) || ctx.sessionId
139
+ if (!targetSessionId) return 'Error: sessionId is required for lineage query.'
140
+ const node = getLineageNodeBySession(targetSessionId)
141
+ if (!node) return JSON.stringify({ lineage: null })
142
+ const tree = buildLineageTree(node.id)
143
+ return JSON.stringify({ lineage: tree })
144
+ }
187
145
 
188
- const job = createDelegationJob({
189
- kind: 'subagent',
190
- parentSessionId: context.sessionId || null,
191
- agentId,
192
- task: message,
193
- cwd: cwd || context.cwd,
146
+ async function handleBatch(args: Record<string, unknown>, ctx: ActionContext): Promise<string> {
147
+ const tasks = args.tasks as Array<{ agentId: string; message: string; cwd?: string; shareBrowserProfile?: boolean }> | undefined
148
+ if (!Array.isArray(tasks) || tasks.length === 0) return 'Error: tasks array is required for batch action.'
149
+ for (const t of tasks) {
150
+ if (!t.agentId || !t.message) return 'Error: each task requires agentId and message.'
151
+ }
152
+ const waitForCompletion = args.waitForCompletion !== false && args.background !== true
153
+
154
+ // Use spawnSwarm internally — batch is a simplified interface
155
+ const swarm = spawnSwarm({ tasks }, { sessionId: ctx.sessionId, cwd: ctx.cwd })
156
+ const jobIds = swarm.members
157
+ .filter((m) => !m.spawnError && m.handle)
158
+ .map((m) => m.handle.jobId)
159
+
160
+ if (!waitForCompletion) {
161
+ return JSON.stringify({
162
+ action: 'batch',
163
+ status: 'running',
164
+ jobIds,
165
+ taskCount: tasks.length,
194
166
  })
195
- appendDelegationCheckpoint(job.id, `Starting subagent ${agentId}`, 'queued')
196
- const started = await startSubagentJob(job.id, { agentId, message, cwd, shareBrowserProfile }, context)
197
-
198
- if (!waitForCompletion) {
199
- return JSON.stringify({
200
- jobId: job.id,
201
- status: 'running',
202
- agentId,
203
- agentName: started.agent.name,
204
- sessionId: started.sid,
205
- })
206
- }
167
+ }
168
+ const aggregate = await swarm.allSettled
169
+ return JSON.stringify({
170
+ action: 'batch',
171
+ status: 'completed',
172
+ jobIds,
173
+ completed: aggregate.totalCompleted,
174
+ failed: aggregate.totalFailed + aggregate.totalSpawnErrors,
175
+ cancelled: aggregate.totalCancelled,
176
+ timedOut: 0,
177
+ totalDurationMs: aggregate.durationMs,
178
+ results: aggregate.results.map((r) => ({
179
+ jobId: r.jobId,
180
+ agentName: r.agentName,
181
+ status: r.status === 'spawn_error' ? 'failed' : r.status,
182
+ response: r.response?.slice(0, 2000) || null,
183
+ error: r.error,
184
+ })),
185
+ })
186
+ }
207
187
 
208
- const result = await started.run.promise
209
- const completed = getDelegationJob(job.id)
188
+ async function handleAggregate(args: Record<string, unknown>): Promise<string> {
189
+ const jobIds = args.jobIds as string[] | undefined
190
+ if (!Array.isArray(jobIds) || jobIds.length === 0) return 'Error: jobIds array is required for aggregate action.'
191
+ return JSON.stringify(aggregateResults(jobIds))
192
+ }
193
+
194
+ async function handleWaitAll(args: Record<string, unknown>): Promise<string> {
195
+ const jobIds = args.jobIds as string[] | undefined
196
+ if (!Array.isArray(jobIds) || jobIds.length === 0) return 'Error: jobIds array is required for wait_all action.'
197
+ const timeoutSec = typeof args.timeoutSec === 'number' ? args.timeoutSec : 300
198
+ const agg = await waitForAll(jobIds, timeoutSec)
199
+ return JSON.stringify(agg)
200
+ }
201
+
202
+ async function handleSwarm(args: Record<string, unknown>, ctx: ActionContext): Promise<string> {
203
+ const tasks = args.tasks as Array<{ agentId: string; message: string; cwd?: string; shareBrowserProfile?: boolean }> | undefined
204
+ if (!Array.isArray(tasks) || tasks.length === 0) return 'Error: tasks array is required for swarm action.'
205
+ for (const t of tasks) {
206
+ if (!t.agentId || !t.message) return 'Error: each task requires agentId and message.'
207
+ }
208
+ const waitForCompletion = args.waitForCompletion !== false && args.background !== true
209
+
210
+ const swarm = spawnSwarm({ tasks }, { sessionId: ctx.sessionId, cwd: ctx.cwd })
211
+ if (!waitForCompletion) {
212
+ const snapshot = getSwarmSnapshot(swarm.swarmId)
210
213
  return JSON.stringify({
211
- jobId: job.id,
212
- status: completed?.status || 'completed',
213
- agentId,
214
- agentName: started.agent.name,
215
- sessionId: started.sid,
216
- response: result.text.slice(0, 32_000),
214
+ action: 'swarm',
215
+ status: 'running',
216
+ swarmId: swarm.swarmId,
217
+ memberCount: swarm.members.length,
218
+ snapshot,
217
219
  })
220
+ }
221
+ const aggregate = await swarm.allSettled
222
+ const snapshot = getSwarmSnapshot(swarm.swarmId)
223
+ return JSON.stringify({
224
+ action: 'swarm',
225
+ ...aggregate,
226
+ status: swarm.status,
227
+ snapshot,
228
+ })
229
+ }
230
+
231
+ function handleSwarmStatus(args: Record<string, unknown>): string {
232
+ const swarmId = requireString(args, 'swarmId')
233
+ const snapshot = getSwarmSnapshot(swarmId)
234
+ if (!snapshot) return `Error: swarm "${swarmId}" not found.`
235
+ return JSON.stringify(snapshot)
236
+ }
237
+
238
+ function handleSwarmList(_args: Record<string, unknown>, ctx: ActionContext): string {
239
+ const swarms = listSwarms(ctx.sessionId)
240
+ return JSON.stringify(swarms.map((s) => ({
241
+ swarmId: s.swarmId,
242
+ status: s.status,
243
+ memberCount: s.members.length,
244
+ createdAt: s.createdAt,
245
+ completedAt: s.completedAt,
246
+ })))
247
+ }
248
+
249
+ function handleSwarmCancel(args: Record<string, unknown>): string {
250
+ const swarmId = requireString(args, 'swarmId')
251
+ const swarm = getSwarm(swarmId)
252
+ if (!swarm) return `Error: swarm "${swarmId}" not found.`
253
+ swarm.cancelAll()
254
+ const snapshot = getSwarmSnapshot(swarmId)
255
+ return JSON.stringify({ cancelled: true, snapshot })
256
+ }
257
+
258
+ async function handleStart(args: Record<string, unknown>, ctx: ActionContext): Promise<string> {
259
+ const agentId = (args.agentId ?? args.agent_id) as string | undefined
260
+ const message = args.message as string | undefined
261
+ if (!agentId) return 'Error: agentId is required.'
262
+ if (!message) return 'Error: message is required.'
263
+
264
+ const cwd = args.cwd as string | undefined
265
+ const shareBrowserProfile = args.shareBrowserProfile === true || args.share_browser_profile === true
266
+ const waitForCompletion = args.waitForCompletion !== false && args.background !== true
267
+
268
+ const handle = spawnSubagent(
269
+ { agentId, message, cwd, shareBrowserProfile, waitForCompletion },
270
+ { sessionId: ctx.sessionId, cwd: ctx.cwd },
271
+ )
272
+
273
+ if (!waitForCompletion) {
274
+ return JSON.stringify({
275
+ jobId: handle.jobId,
276
+ status: 'running',
277
+ agentId: handle.agentId,
278
+ agentName: handle.agentName,
279
+ sessionId: handle.sessionId,
280
+ lineageId: handle.lineageId,
281
+ })
282
+ }
283
+
284
+ const result = await handle.promise
285
+ return JSON.stringify({
286
+ jobId: result.jobId,
287
+ status: result.status,
288
+ agentId: result.agentId,
289
+ agentName: result.agentName,
290
+ sessionId: result.sessionId,
291
+ lineageId: result.lineageId,
292
+ response: result.response,
293
+ depth: result.depth,
294
+ childCount: result.childCount,
295
+ durationMs: result.durationMs,
296
+ })
297
+ }
298
+
299
+ // ---------------------------------------------------------------------------
300
+ // Dispatch map
301
+ // ---------------------------------------------------------------------------
302
+
303
+ type ActionHandler = (args: Record<string, unknown>, ctx: ActionContext) => Promise<string> | string
304
+ const ACTIONS: Record<string, ActionHandler> = {
305
+ status: handleStatus,
306
+ list: handleList,
307
+ cancel: handleCancel,
308
+ wait: handleWait,
309
+ lineage: handleLineage,
310
+ batch: handleBatch,
311
+ aggregate: handleAggregate,
312
+ wait_all: handleWaitAll,
313
+ swarm: handleSwarm,
314
+ swarm_status: handleSwarmStatus,
315
+ swarm_list: handleSwarmList,
316
+ swarm_cancel: handleSwarmCancel,
317
+ }
318
+
319
+ /**
320
+ * Core Subagent Execution Logic — powered by native subagent runtime.
321
+ * Uses dispatch map instead of if-else chain for maintainability.
322
+ */
323
+ async function executeSubagentAction(args: unknown, context: ActionContext) {
324
+ const normalized = normalizeToolInputArgs((args ?? {}) as Record<string, unknown>)
325
+ const action = String(normalized.action || 'start').trim().toLowerCase()
326
+
327
+ recoverStaleDelegationJobs()
328
+
329
+ try {
330
+ const handler = ACTIONS[action]
331
+ if (handler) return handler(normalized, context)
332
+ // Default: single agent spawn
333
+ return handleStart(normalized, context)
218
334
  } catch (err: unknown) {
219
- return `Error: ${err instanceof Error ? err.message : String(err)}`
335
+ return `Error: ${errorMessage(err)}`
220
336
  }
221
337
  }
222
338
 
@@ -226,15 +342,31 @@ async function executeSubagentAction(args: any, context: { sessionId?: string; c
226
342
  const SubagentPlugin: Plugin = {
227
343
  name: 'Core Subagents',
228
344
  description: 'Delegate tasks to other specialized agents with resumable job handles.',
229
- hooks: {} as PluginHooks,
345
+ hooks: {
346
+ getCapabilityDescription: () =>
347
+ 'Delegate tasks to other agents (spawn_subagent). Single task: action "start". '
348
+ + 'Multiple independent tasks: action "batch" with a tasks array. '
349
+ + 'Event-driven parallel with status tracking: action "swarm" with a tasks array.',
350
+ getOperatingGuidance: () => [
351
+ 'SUBAGENT DISPATCH RULES:',
352
+ '- Single task → action "start" with agentId + message.',
353
+ '- 2+ independent tasks that can run in parallel → action "batch" with tasks array [{agentId, message}, ...].',
354
+ '- DO NOT call "start" in a loop when tasks are independent — use "batch" or "swarm" instead.',
355
+ '- Only use subagents when the task genuinely requires another agent\'s specialization or parallel execution.',
356
+ '- If you can answer directly from your own knowledge, do NOT spawn a subagent.',
357
+ ],
358
+ } as PluginHooks,
230
359
  tools: [
231
360
  {
232
361
  name: 'spawn_subagent',
233
- description: 'Delegate a task to another agent. Supports background jobs with action=status|list|wait|cancel and waitForCompletion=false.',
362
+ description: 'Delegate tasks to other agents. '
363
+ + 'Actions: start (single agent), batch (2+ parallel tasks via "tasks" array), swarm (parallel with status tracking via "tasks" array). '
364
+ + 'Management: status, list, wait, wait_all, cancel, lineage, aggregate, swarm_status, swarm_list, swarm_cancel. '
365
+ + 'For multiple independent tasks, prefer "batch" with tasks:[{agentId,message},...] over calling "start" repeatedly.',
234
366
  parameters: {
235
367
  type: 'object',
236
368
  properties: {
237
- action: { type: 'string', enum: ['start', 'status', 'list', 'wait', 'cancel'] },
369
+ action: { type: 'string', enum: ['start', 'status', 'list', 'wait', 'wait_all', 'cancel', 'lineage', 'batch', 'aggregate', 'swarm', 'swarm_status', 'swarm_list', 'swarm_cancel'] },
238
370
  agentId: { type: 'string' },
239
371
  message: { type: 'string' },
240
372
  cwd: { type: 'string' },
@@ -243,6 +375,26 @@ const SubagentPlugin: Plugin = {
243
375
  description: 'When true, inherit the parent session browser profile. Defaults to false so subagents get isolated browser state.',
244
376
  },
245
377
  jobId: { type: 'string' },
378
+ swarmId: { type: 'string', description: 'Swarm ID for swarm_status/swarm_cancel actions.' },
379
+ jobIds: {
380
+ type: 'array',
381
+ items: { type: 'string' },
382
+ description: 'Array of job IDs for aggregate/wait_all actions.',
383
+ },
384
+ tasks: {
385
+ type: 'array',
386
+ items: {
387
+ type: 'object',
388
+ properties: {
389
+ agentId: { type: 'string' },
390
+ message: { type: 'string' },
391
+ cwd: { type: 'string' },
392
+ shareBrowserProfile: { type: 'boolean' },
393
+ },
394
+ required: ['agentId', 'message'],
395
+ },
396
+ description: 'Array of tasks for batch/swarm action.',
397
+ },
246
398
  waitForCompletion: { type: 'boolean' },
247
399
  background: { type: 'boolean' },
248
400
  timeoutSec: { type: 'number' },