@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
@@ -0,0 +1,484 @@
1
+ import assert from 'node:assert/strict'
2
+ import fs from 'node:fs'
3
+ import os from 'node:os'
4
+ import path from 'node:path'
5
+ import { after, before, describe, it } from 'node:test'
6
+
7
+ const originalEnv = {
8
+ DATA_DIR: process.env.DATA_DIR,
9
+ WORKSPACE_DIR: process.env.WORKSPACE_DIR,
10
+ SWARMCLAW_BUILD_MODE: process.env.SWARMCLAW_BUILD_MODE,
11
+ }
12
+
13
+ let tempDir = ''
14
+ let runtime: typeof import('./subagent-runtime')
15
+ let lineage: typeof import('./subagent-lineage')
16
+ let delegationJobs: typeof import('./delegation-jobs')
17
+ let storage: typeof import('./storage')
18
+
19
+ before(async () => {
20
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-subagent-runtime-'))
21
+ process.env.DATA_DIR = path.join(tempDir, 'data')
22
+ process.env.WORKSPACE_DIR = path.join(tempDir, 'workspace')
23
+ process.env.SWARMCLAW_BUILD_MODE = '1'
24
+
25
+ storage = await import('./storage')
26
+ delegationJobs = await import('./delegation-jobs')
27
+ lineage = await import('./subagent-lineage')
28
+ runtime = await import('./subagent-runtime')
29
+ })
30
+
31
+ after(() => {
32
+ if (originalEnv.DATA_DIR === undefined) delete process.env.DATA_DIR
33
+ else process.env.DATA_DIR = originalEnv.DATA_DIR
34
+ if (originalEnv.WORKSPACE_DIR === undefined) delete process.env.WORKSPACE_DIR
35
+ else process.env.WORKSPACE_DIR = originalEnv.WORKSPACE_DIR
36
+ if (originalEnv.SWARMCLAW_BUILD_MODE === undefined) delete process.env.SWARMCLAW_BUILD_MODE
37
+ else process.env.SWARMCLAW_BUILD_MODE = originalEnv.SWARMCLAW_BUILD_MODE
38
+ fs.rmSync(tempDir, { recursive: true, force: true })
39
+ })
40
+
41
+ function seedAgent(id: string, name: string, plugins: string[] = []) {
42
+ const agents = storage.loadAgents()
43
+ agents[id] = {
44
+ id,
45
+ name,
46
+ provider: 'anthropic',
47
+ model: 'claude-sonnet-4-20250514',
48
+ systemPrompt: 'Test agent',
49
+ plugins,
50
+ }
51
+ storage.saveAgents(agents)
52
+ }
53
+
54
+ describe('subagent-runtime', () => {
55
+ before(() => {
56
+ lineage._clearLineage()
57
+ })
58
+
59
+ describe('spawnSubagent', () => {
60
+ it('throws for unknown agent', () => {
61
+ lineage._clearLineage()
62
+ assert.throws(
63
+ () => runtime.spawnSubagent(
64
+ { agentId: 'nonexistent', message: 'hello' },
65
+ { cwd: tempDir },
66
+ ),
67
+ /not found/,
68
+ )
69
+ })
70
+
71
+ it('throws when max depth is exceeded', () => {
72
+ lineage._clearLineage()
73
+ seedAgent('depth-agent', 'Depth Agent')
74
+
75
+ // Create a chain of sessions to simulate depth
76
+ const sessions = storage.loadSessions()
77
+ sessions['depth-s0'] = { id: 'depth-s0', parentSessionId: null, cwd: tempDir }
78
+ sessions['depth-s1'] = { id: 'depth-s1', parentSessionId: 'depth-s0', cwd: tempDir }
79
+ sessions['depth-s2'] = { id: 'depth-s2', parentSessionId: 'depth-s1', cwd: tempDir }
80
+ sessions['depth-s3'] = { id: 'depth-s3', parentSessionId: 'depth-s2', cwd: tempDir }
81
+ storage.saveSessions(sessions)
82
+
83
+ assert.throws(
84
+ () => runtime.spawnSubagent(
85
+ { agentId: 'depth-agent', message: 'too deep' },
86
+ { sessionId: 'depth-s3', cwd: tempDir },
87
+ ),
88
+ /Max subagent depth/,
89
+ )
90
+ })
91
+
92
+ it('creates session, lineage node, and delegation job', () => {
93
+ lineage._clearLineage()
94
+ seedAgent('spawn-agent', 'Spawn Agent')
95
+
96
+ let handle: ReturnType<typeof runtime.spawnSubagent> | null = null
97
+ try {
98
+ handle = runtime.spawnSubagent(
99
+ { agentId: 'spawn-agent', message: 'test task', waitForCompletion: false },
100
+ { sessionId: undefined, cwd: tempDir },
101
+ )
102
+ } catch {
103
+ // May throw if enqueueSessionRun fails synchronously
104
+ }
105
+
106
+ if (handle) {
107
+ // Verify delegation job was created
108
+ const job = delegationJobs.getDelegationJob(handle.jobId)
109
+ assert.ok(job, 'Delegation job should exist')
110
+ assert.equal(job.kind, 'subagent')
111
+ assert.equal(job.agentId, 'spawn-agent')
112
+
113
+ // Verify lineage node was created with lifecycle state
114
+ const node = lineage.getLineageNodeBySession(handle.sessionId)
115
+ assert.ok(node, 'Lineage node should exist')
116
+ assert.equal(node.agentId, 'spawn-agent')
117
+ assert.equal(node.agentName, 'Spawn Agent')
118
+ assert.equal(node.depth, 0)
119
+ assert.equal(node.task, 'test task')
120
+ assert.equal(node.status, 'running') // initializing → ready → running
121
+
122
+ // Verify handle is registered for promise-based waiting
123
+ const retrieved = runtime.getHandle(handle.jobId)
124
+ assert.ok(retrieved, 'Handle should be registered')
125
+ assert.equal(retrieved.jobId, handle.jobId)
126
+
127
+ // Verify session was created
128
+ const sessions = storage.loadSessions()
129
+ assert.ok(sessions[handle.sessionId], 'Session should exist')
130
+ assert.equal(sessions[handle.sessionId].agentId, 'spawn-agent')
131
+ assert.ok(sessions[handle.sessionId].createdAt > 0)
132
+ }
133
+ })
134
+
135
+ it('tracks parent-child lineage correctly', () => {
136
+ lineage._clearLineage()
137
+ seedAgent('parent-agent', 'Parent')
138
+ seedAgent('child-agent', 'Child')
139
+
140
+ // Create a parent session
141
+ const sessions = storage.loadSessions()
142
+ sessions['parent-session'] = {
143
+ id: 'parent-session',
144
+ cwd: tempDir,
145
+ parentSessionId: null,
146
+ agentId: 'parent-agent',
147
+ }
148
+ storage.saveSessions(sessions)
149
+
150
+ // Create parent lineage node
151
+ lineage.createLineageNode({
152
+ sessionId: 'parent-session',
153
+ agentId: 'parent-agent',
154
+ agentName: 'Parent',
155
+ task: 'Parent task',
156
+ })
157
+
158
+ let handle: ReturnType<typeof runtime.spawnSubagent> | null = null
159
+ try {
160
+ handle = runtime.spawnSubagent(
161
+ { agentId: 'child-agent', message: 'child task', waitForCompletion: false },
162
+ { sessionId: 'parent-session', cwd: tempDir },
163
+ )
164
+ } catch {
165
+ // May throw from async execution
166
+ }
167
+
168
+ if (handle) {
169
+ const childNode = lineage.getLineageNodeBySession(handle.sessionId)
170
+ assert.ok(childNode)
171
+ assert.equal(childNode.depth, 1)
172
+ assert.equal(childNode.parentSessionId, 'parent-session')
173
+ assert.equal(childNode.agentName, 'Child')
174
+
175
+ // Verify parent can see child
176
+ const parentNode = lineage.getLineageNodeBySession('parent-session')!
177
+ const children = lineage.getChildren(parentNode.id)
178
+ assert.equal(children.length, 1)
179
+ assert.equal(children[0].sessionId, handle.sessionId)
180
+
181
+ // Verify ancestry
182
+ const ancestors = lineage.getAncestors(childNode.id)
183
+ assert.equal(ancestors.length, 1)
184
+ assert.equal(ancestors[0].sessionId, 'parent-session')
185
+ }
186
+ })
187
+ })
188
+
189
+ describe('mergePlugins', () => {
190
+ it('returns agent plugins when parent has none', () => {
191
+ const merged = runtime._mergePlugins(['shell', 'memory'], null)
192
+ assert.deepEqual(merged, ['shell', 'memory'])
193
+ })
194
+
195
+ it('returns parent plugins when agent has none', () => {
196
+ const merged = runtime._mergePlugins([], { plugins: ['browser', 'web'] })
197
+ assert.deepEqual(merged, ['browser', 'web'])
198
+ })
199
+
200
+ it('merges and deduplicates agent + parent plugins', () => {
201
+ const merged = runtime._mergePlugins(
202
+ ['shell', 'memory'],
203
+ { plugins: ['memory', 'browser', 'web'] },
204
+ )
205
+ // agent plugins first, then parent fills gaps; 'memory' not duplicated
206
+ assert.deepEqual(merged, ['shell', 'memory', 'browser', 'web'])
207
+ })
208
+
209
+ it('deduplicates case-insensitively', () => {
210
+ const merged = runtime._mergePlugins(['Shell'], { plugins: ['shell', 'web'] })
211
+ assert.equal(merged.length, 2)
212
+ assert.equal(merged[0], 'Shell') // preserves original case
213
+ assert.equal(merged[1], 'web')
214
+ })
215
+
216
+ it('falls back to parent tools when plugins is missing', () => {
217
+ const merged = runtime._mergePlugins(['shell'], { tools: ['browser'] })
218
+ assert.deepEqual(merged, ['shell', 'browser'])
219
+ })
220
+
221
+ it('ignores empty/whitespace strings', () => {
222
+ const merged = runtime._mergePlugins(['shell', ''], { plugins: [' ', 'web'] })
223
+ assert.deepEqual(merged, ['shell', 'web'])
224
+ })
225
+
226
+ it('returns empty array when both are empty', () => {
227
+ const merged = runtime._mergePlugins([], { plugins: [] })
228
+ assert.deepEqual(merged, [])
229
+ })
230
+ })
231
+
232
+ describe('plugin inheritance in spawnSubagent', () => {
233
+ it('child session inherits parent plugins merged with agent plugins', () => {
234
+ lineage._clearLineage()
235
+ seedAgent('inherit-agent', 'Inherit Agent', ['shell', 'memory'])
236
+
237
+ const sessions = storage.loadSessions()
238
+ sessions['inherit-parent'] = {
239
+ id: 'inherit-parent',
240
+ cwd: tempDir,
241
+ parentSessionId: null,
242
+ agentId: 'inherit-agent',
243
+ plugins: ['shell', 'browser', 'web', 'manage_connectors'],
244
+ }
245
+ storage.saveSessions(sessions)
246
+
247
+ let handle: ReturnType<typeof runtime.spawnSubagent> | null = null
248
+ try {
249
+ handle = runtime.spawnSubagent(
250
+ { agentId: 'inherit-agent', message: 'test inheritance', waitForCompletion: false },
251
+ { sessionId: 'inherit-parent', cwd: tempDir },
252
+ )
253
+ } catch { /* enqueueSessionRun may fail */ }
254
+
255
+ if (handle) {
256
+ const childSession = storage.loadSessions()[handle.sessionId]
257
+ assert.ok(childSession, 'Child session should exist')
258
+ const plugins = childSession.plugins as string[]
259
+ assert.ok(plugins.includes('shell'), 'should have shell from agent')
260
+ assert.ok(plugins.includes('memory'), 'should have memory from agent')
261
+ assert.ok(plugins.includes('browser'), 'should inherit browser from parent')
262
+ assert.ok(plugins.includes('web'), 'should inherit web from parent')
263
+ assert.ok(plugins.includes('manage_connectors'), 'should inherit manage_connectors from parent')
264
+ assert.equal(plugins.filter((p: string) => p.toLowerCase() === 'shell').length, 1, 'shell should not be duplicated')
265
+ }
266
+ })
267
+
268
+ it('child session does not inherit when inheritPlugins is false', () => {
269
+ lineage._clearLineage()
270
+ seedAgent('no-inherit-agent', 'No Inherit Agent', ['shell'])
271
+
272
+ const sessions = storage.loadSessions()
273
+ sessions['no-inherit-parent'] = {
274
+ id: 'no-inherit-parent',
275
+ cwd: tempDir,
276
+ parentSessionId: null,
277
+ plugins: ['shell', 'browser', 'web'],
278
+ }
279
+ storage.saveSessions(sessions)
280
+
281
+ let handle: ReturnType<typeof runtime.spawnSubagent> | null = null
282
+ try {
283
+ handle = runtime.spawnSubagent(
284
+ { agentId: 'no-inherit-agent', message: 'no inherit', inheritPlugins: false, waitForCompletion: false },
285
+ { sessionId: 'no-inherit-parent', cwd: tempDir },
286
+ )
287
+ } catch { /* enqueueSessionRun may fail */ }
288
+
289
+ if (handle) {
290
+ const childSession = storage.loadSessions()[handle.sessionId]
291
+ assert.ok(childSession, 'Child session should exist')
292
+ const plugins = childSession.plugins as string[]
293
+ assert.deepEqual(plugins, ['shell'], 'should only have agent plugins')
294
+ }
295
+ })
296
+ })
297
+
298
+ describe('cancelSubagentBySession', () => {
299
+ it('cancels a subagent and its lineage node', () => {
300
+ lineage._clearLineage()
301
+
302
+ const node = lineage.createLineageNode({
303
+ sessionId: 'cancel-session',
304
+ agentId: 'ag',
305
+ agentName: 'Cancel Agent',
306
+ task: 'Some task',
307
+ })
308
+ // Transition to running so it can be cancelled
309
+ lineage.transitionState(node.id, 'READY')
310
+ lineage.transitionState(node.id, 'START')
311
+
312
+ const result = runtime.cancelSubagentBySession('cancel-session')
313
+ assert.equal(result, true)
314
+ assert.equal(lineage.getLineageNode(node.id)?.status, 'cancelled')
315
+ })
316
+
317
+ it('returns false for unknown session', () => {
318
+ assert.equal(runtime.cancelSubagentBySession('unknown-session'), false)
319
+ })
320
+ })
321
+
322
+ describe('cleanupFinishedSubagents', () => {
323
+ it('removes old terminal lineage nodes', () => {
324
+ lineage._clearLineage()
325
+
326
+ const node = lineage.createLineageNode({
327
+ sessionId: 'cleanup-sess',
328
+ agentId: 'ag',
329
+ agentName: 'Cleanup Agent',
330
+ task: 'test',
331
+ })
332
+ lineage.transitionState(node.id, 'READY')
333
+ lineage.transitionState(node.id, 'START')
334
+ lineage.completeLineageNode(node.id, 'done')
335
+
336
+ // Use negative maxAge so everything qualifies as old
337
+ const cleaned = runtime.cleanupFinishedSubagents(-1)
338
+ assert.equal(cleaned, 1)
339
+ assert.equal(lineage.getLineageNode(node.id), null)
340
+ })
341
+
342
+ it('does not remove active lineage nodes', () => {
343
+ lineage._clearLineage()
344
+
345
+ const node = lineage.createLineageNode({
346
+ sessionId: 'active-sess',
347
+ agentId: 'ag',
348
+ agentName: 'Active Agent',
349
+ task: 'test',
350
+ })
351
+ lineage.transitionState(node.id, 'READY')
352
+ lineage.transitionState(node.id, 'START')
353
+ // Leave in 'running' state
354
+
355
+ const cleaned = runtime.cleanupFinishedSubagents(0)
356
+ assert.equal(cleaned, 0)
357
+ assert.ok(lineage.getLineageNode(node.id))
358
+ })
359
+ })
360
+
361
+ // ---------------------------------------------------------------------------
362
+ // Reliability fix: orphaned handle cleanup (#9)
363
+ // ---------------------------------------------------------------------------
364
+
365
+ describe('cleanupFinishedSubagents — orphaned handles', () => {
366
+ it('purges handles whose lineage node no longer exists', () => {
367
+ lineage._clearLineage()
368
+
369
+ // Create a lineage node and register a handle for it
370
+ const node = lineage.createLineageNode({
371
+ sessionId: 'orphan-sess',
372
+ agentId: 'ag',
373
+ agentName: 'Orphan Agent',
374
+ task: 'test orphan cleanup',
375
+ })
376
+ lineage.transitionState(node.id, 'READY')
377
+ lineage.transitionState(node.id, 'START')
378
+ lineage.completeLineageNode(node.id, 'done')
379
+
380
+ // Manually register a handle referencing this lineage node
381
+ const handleRegistry = (globalThis as any).__swarmclaw_subagent_handles__ as Map<string, any>
382
+ handleRegistry.set('orphan-job-1', {
383
+ jobId: 'orphan-job-1',
384
+ sessionId: 'orphan-sess',
385
+ lineageId: node.id,
386
+ agentId: 'ag',
387
+ agentName: 'Orphan Agent',
388
+ run: { runId: 'r', position: 0, promise: Promise.resolve(null), abort: () => {}, unsubscribe: () => {} },
389
+ promise: Promise.resolve(null),
390
+ })
391
+
392
+ // Also register a handle with a fake lineage ID that was never created
393
+ handleRegistry.set('orphan-job-2', {
394
+ jobId: 'orphan-job-2',
395
+ sessionId: 'never-existed',
396
+ lineageId: 'fake-lineage-id-xyz',
397
+ agentId: 'ag',
398
+ agentName: 'Ghost',
399
+ run: { runId: 'r2', position: 0, promise: Promise.resolve(null), abort: () => {}, unsubscribe: () => {} },
400
+ promise: Promise.resolve(null),
401
+ })
402
+
403
+ assert.equal(handleRegistry.has('orphan-job-1'), true)
404
+ assert.equal(handleRegistry.has('orphan-job-2'), true)
405
+
406
+ // Cleanup with negative maxAge so all terminal nodes are removed
407
+ const cleaned = runtime.cleanupFinishedSubagents(-1)
408
+ assert.equal(cleaned, 1) // one terminal node removed
409
+
410
+ // Both handles should be purged:
411
+ // orphan-job-1: lineage node was removed by cleanup
412
+ // orphan-job-2: lineage node never existed (orphaned handle)
413
+ assert.equal(handleRegistry.has('orphan-job-1'), false, 'Handle for cleaned lineage node should be purged')
414
+ assert.equal(handleRegistry.has('orphan-job-2'), false, 'Handle with non-existent lineage node should be purged')
415
+ })
416
+
417
+ it('preserves handles for active lineage nodes', () => {
418
+ lineage._clearLineage()
419
+
420
+ const node = lineage.createLineageNode({
421
+ sessionId: 'active-handle-sess',
422
+ agentId: 'ag',
423
+ agentName: 'Active',
424
+ task: 'still running',
425
+ })
426
+ lineage.transitionState(node.id, 'READY')
427
+ lineage.transitionState(node.id, 'START')
428
+ // Leave in running state — should NOT be cleaned up
429
+
430
+ const handleRegistry = (globalThis as any).__swarmclaw_subagent_handles__ as Map<string, any>
431
+ handleRegistry.set('active-job', {
432
+ jobId: 'active-job',
433
+ sessionId: 'active-handle-sess',
434
+ lineageId: node.id,
435
+ agentId: 'ag',
436
+ agentName: 'Active',
437
+ run: { runId: 'r', position: 0, promise: Promise.resolve(null), abort: () => {}, unsubscribe: () => {} },
438
+ promise: Promise.resolve(null),
439
+ })
440
+
441
+ runtime.cleanupFinishedSubagents(0)
442
+
443
+ assert.equal(handleRegistry.has('active-job'), true, 'Handle for active node should be preserved')
444
+ assert.ok(lineage.getLineageNode(node.id), 'Active lineage node should still exist')
445
+ })
446
+ })
447
+
448
+ describe('query helpers (re-exported)', () => {
449
+ it('getLineageNodeBySession works through runtime module', () => {
450
+ lineage._clearLineage()
451
+ lineage.createLineageNode({
452
+ sessionId: 'reexport-test',
453
+ agentId: 'ag',
454
+ agentName: 'Re-export',
455
+ task: 'Test',
456
+ })
457
+
458
+ const node = runtime.getLineageNodeBySession('reexport-test')
459
+ assert.ok(node)
460
+ assert.equal(node.agentName, 'Re-export')
461
+ })
462
+
463
+ it('buildLineageTree works through runtime module', () => {
464
+ lineage._clearLineage()
465
+ const root = lineage.createLineageNode({
466
+ sessionId: 'tree-root',
467
+ agentId: 'ag',
468
+ agentName: 'Root',
469
+ task: 'Root task',
470
+ })
471
+ lineage.createLineageNode({
472
+ sessionId: 'tree-child',
473
+ agentId: 'ag',
474
+ agentName: 'Child',
475
+ parentSessionId: 'tree-root',
476
+ task: 'Child task',
477
+ })
478
+
479
+ const tree = runtime.buildLineageTree(root.id)
480
+ assert.ok(tree)
481
+ assert.equal(tree.children.length, 1)
482
+ })
483
+ })
484
+ })