@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,200 @@
1
+ import { genId } from '@/lib/id'
2
+ import type { BoardTask, BoardTaskStatus, Schedule, TaskComment } from '@/types'
3
+
4
+ import { ensureTaskCompletionReport, type TaskReportArtifact } from './task-reports'
5
+ import {
6
+ formatValidationFailure,
7
+ validateTaskCompletion,
8
+ type TaskCompletionValidation,
9
+ } from './task-validation'
10
+
11
+ export interface BuildBoardTaskInput {
12
+ id?: string
13
+ title: string
14
+ description?: string | null
15
+ agentId: string
16
+ now: number
17
+ status?: BoardTaskStatus
18
+ seed?: Record<string, unknown>
19
+ }
20
+
21
+ export function buildBoardTask(input: BuildBoardTaskInput): BoardTask {
22
+ const id = input.id || genId()
23
+ const seed = input.seed ? { ...input.seed } : {}
24
+ const seedStatus = typeof seed.status === 'string' ? seed.status as BoardTaskStatus : undefined
25
+ const task = {
26
+ sessionId: null,
27
+ result: null,
28
+ error: null,
29
+ createdAt: input.now,
30
+ updatedAt: input.now,
31
+ queuedAt: null,
32
+ startedAt: null,
33
+ completedAt: null,
34
+ ...seed,
35
+ id,
36
+ title: input.title,
37
+ description: input.description ?? '',
38
+ status: input.status ?? seedStatus ?? 'backlog',
39
+ agentId: input.agentId,
40
+ } as BoardTask
41
+ return task
42
+ }
43
+
44
+ export interface ResetTaskForRerunOptions {
45
+ title: string
46
+ now: number
47
+ runNumber?: number | null
48
+ }
49
+
50
+ export function resetTaskForRerun(task: BoardTask, options: ResetTaskForRerunOptions): BoardTask {
51
+ const stats = task as unknown as Record<string, unknown>
52
+ stats.totalRuns = ((stats.totalRuns as number) || 0) + 1
53
+ if (task.status === 'completed') stats.totalCompleted = ((stats.totalCompleted as number) || 0) + 1
54
+ if (task.status === 'failed') stats.totalFailed = ((stats.totalFailed as number) || 0) + 1
55
+
56
+ task.status = 'backlog'
57
+ task.title = options.title
58
+ task.result = null
59
+ task.error = null
60
+ task.outputFiles = []
61
+ task.artifacts = []
62
+ task.sessionId = null
63
+ task.completionReportPath = null
64
+ task.updatedAt = options.now
65
+ task.queuedAt = null
66
+ task.startedAt = null
67
+ task.completedAt = null
68
+ task.archivedAt = null
69
+ task.attempts = 0
70
+ task.retryScheduledAt = null
71
+ task.deadLetteredAt = null
72
+ task.validation = null
73
+ if (options.runNumber !== undefined) stats.runNumber = options.runNumber
74
+ return task
75
+ }
76
+
77
+ export interface PrepareScheduledTaskRunOptions {
78
+ schedule: Pick<
79
+ Schedule,
80
+ | 'id'
81
+ | 'name'
82
+ | 'agentId'
83
+ | 'taskPrompt'
84
+ | 'linkedTaskId'
85
+ | 'runNumber'
86
+ | 'createdInSessionId'
87
+ | 'createdByAgentId'
88
+ | 'followupConnectorId'
89
+ | 'followupChannelId'
90
+ | 'followupThreadId'
91
+ | 'followupSenderId'
92
+ | 'followupSenderName'
93
+ >
94
+ tasks: Record<string, BoardTask>
95
+ now: number
96
+ scheduleSignature?: string | null
97
+ }
98
+
99
+ export function prepareScheduledTaskRun(params: PrepareScheduledTaskRunOptions): { taskId: string; task: BoardTask } {
100
+ const { schedule, tasks, now, scheduleSignature } = params
101
+ const title = `[Sched] ${schedule.name} (run #${schedule.runNumber})`
102
+ const existingTaskId = typeof schedule.linkedTaskId === 'string' ? schedule.linkedTaskId : ''
103
+ const existingTask = existingTaskId ? tasks[existingTaskId] : null
104
+
105
+ if (existingTask && existingTask.status !== 'queued' && existingTask.status !== 'running') {
106
+ return {
107
+ taskId: existingTaskId,
108
+ task: resetTaskForRerun(existingTask, {
109
+ title,
110
+ now,
111
+ runNumber: schedule.runNumber,
112
+ }),
113
+ }
114
+ }
115
+
116
+ const task = buildBoardTask({
117
+ title,
118
+ description: schedule.taskPrompt || '',
119
+ agentId: schedule.agentId,
120
+ now,
121
+ seed: {
122
+ sourceType: 'schedule',
123
+ sourceScheduleId: schedule.id,
124
+ sourceScheduleName: schedule.name,
125
+ sourceScheduleKey: scheduleSignature || null,
126
+ createdInSessionId: schedule.createdInSessionId || null,
127
+ createdByAgentId: schedule.createdByAgentId || null,
128
+ followupConnectorId: schedule.followupConnectorId || null,
129
+ followupChannelId: schedule.followupChannelId || null,
130
+ followupThreadId: schedule.followupThreadId || null,
131
+ followupSenderId: schedule.followupSenderId || null,
132
+ followupSenderName: schedule.followupSenderName || null,
133
+ runNumber: schedule.runNumber,
134
+ },
135
+ })
136
+ tasks[task.id] = task
137
+ schedule.linkedTaskId = task.id
138
+ return { taskId: task.id, task }
139
+ }
140
+
141
+ function sameValidationReasons(a?: string[] | null, b?: string[] | null): boolean {
142
+ const av = Array.isArray(a) ? a : []
143
+ const bv = Array.isArray(b) ? b : []
144
+ if (av.length !== bv.length) return false
145
+ for (let i = 0; i < av.length; i++) {
146
+ if (av[i] !== bv[i]) return false
147
+ }
148
+ return true
149
+ }
150
+
151
+ export function didTaskValidationChange(
152
+ previous: TaskCompletionValidation | null | undefined,
153
+ next: TaskCompletionValidation,
154
+ ): boolean {
155
+ return !previous
156
+ || previous.ok !== next.ok
157
+ || !sameValidationReasons(previous.reasons, next.reasons)
158
+ }
159
+
160
+ export function refreshTaskCompletionValidation(
161
+ task: BoardTask,
162
+ settings?: Record<string, unknown> | null,
163
+ ): { report: TaskReportArtifact | null; validation: TaskCompletionValidation } {
164
+ const report = ensureTaskCompletionReport(task)
165
+ if (report?.relativePath) task.completionReportPath = report.relativePath
166
+ const validation = validateTaskCompletion(task, { report, settings: settings || null })
167
+ task.validation = validation
168
+ return { report, validation }
169
+ }
170
+
171
+ export function markValidatedTaskCompleted(
172
+ task: BoardTask,
173
+ options: { now: number; preserveCompletedAt?: boolean } ,
174
+ ): BoardTask {
175
+ task.status = 'completed'
176
+ task.completedAt = options.preserveCompletedAt ? (task.completedAt || options.now) : options.now
177
+ task.updatedAt = options.now
178
+ task.error = null
179
+ return task
180
+ }
181
+
182
+ export function markInvalidCompletedTaskFailed(
183
+ task: BoardTask,
184
+ validation: TaskCompletionValidation,
185
+ options: { now: number; comment?: Omit<TaskComment, 'id' | 'createdAt'> & { text: string } } ,
186
+ ): BoardTask {
187
+ task.status = 'failed'
188
+ task.completedAt = null
189
+ task.updatedAt = options.now
190
+ task.error = formatValidationFailure(validation.reasons).slice(0, 500)
191
+ if (options.comment) {
192
+ if (!task.comments) task.comments = []
193
+ task.comments.push({
194
+ id: genId(),
195
+ createdAt: options.now,
196
+ ...options.comment,
197
+ })
198
+ }
199
+ return task
200
+ }
@@ -1,6 +1,6 @@
1
1
  import assert from 'node:assert/strict'
2
2
  import { test } from 'node:test'
3
- import { normalizeTaskQualityGate } from './task-quality-gate.ts'
3
+ import { normalizeTaskQualityGate } from './task-quality-gate'
4
4
 
5
5
  test('normalizeTaskQualityGate uses defaults when unset', () => {
6
6
  const gate = normalizeTaskQualityGate(undefined, undefined)
@@ -0,0 +1,208 @@
1
+ import type { BoardTask, Session } from '@/types'
2
+ import type { SessionLike } from './task-followups'
3
+
4
+ export interface TaskResumeState {
5
+ claudeSessionId: string | null
6
+ codexThreadId: string | null
7
+ opencodeSessionId: string | null
8
+ delegateResumeIds: NonNullable<Session['delegateResumeIds']>
9
+ }
10
+
11
+ export interface TaskResumeContext {
12
+ source: 'self' | 'delegated_from_task' | 'blocked_by'
13
+ sourceTaskId: string
14
+ sourceTaskTitle: string
15
+ sourceSessionId: string | null
16
+ resume: TaskResumeState
17
+ }
18
+
19
+ function normalizeResumeHandle(value: unknown): string | null {
20
+ return typeof value === 'string' && value.trim() ? value.trim() : null
21
+ }
22
+
23
+ function buildEmptyDelegateResumeIds(): NonNullable<Session['delegateResumeIds']> {
24
+ return {
25
+ claudeCode: null,
26
+ codex: null,
27
+ opencode: null,
28
+ gemini: null,
29
+ }
30
+ }
31
+
32
+ function normalizeCliProvider(value: unknown): string | null {
33
+ return typeof value === 'string' && value.trim() ? value.trim().toLowerCase() : null
34
+ }
35
+
36
+ function hasResumeState(state: TaskResumeState | null | undefined): state is TaskResumeState {
37
+ if (!state) return false
38
+ return Boolean(
39
+ state.claudeSessionId
40
+ || state.codexThreadId
41
+ || state.opencodeSessionId
42
+ || state.delegateResumeIds.claudeCode
43
+ || state.delegateResumeIds.codex
44
+ || state.delegateResumeIds.opencode
45
+ || state.delegateResumeIds.gemini
46
+ )
47
+ }
48
+
49
+ export function extractTaskResumeState(task: Partial<BoardTask> | null | undefined): TaskResumeState | null {
50
+ if (!task) return null
51
+
52
+ const legacyResumeId = normalizeResumeHandle(task.cliResumeId)
53
+ const legacyProvider = normalizeCliProvider(task.cliProvider)
54
+ const claudeSessionId = normalizeResumeHandle(task.claudeResumeId)
55
+ || (legacyProvider === 'claude-cli' ? legacyResumeId : null)
56
+ const codexThreadId = normalizeResumeHandle(task.codexResumeId)
57
+ || (legacyProvider === 'codex-cli' ? legacyResumeId : null)
58
+ const opencodeSessionId = normalizeResumeHandle(task.opencodeResumeId)
59
+ || (legacyProvider === 'opencode-cli' ? legacyResumeId : null)
60
+ const geminiSessionId = normalizeResumeHandle(task.geminiResumeId)
61
+ || (legacyProvider === 'gemini-cli' ? legacyResumeId : null)
62
+
63
+ const resume = {
64
+ claudeSessionId,
65
+ codexThreadId,
66
+ opencodeSessionId,
67
+ delegateResumeIds: {
68
+ claudeCode: claudeSessionId,
69
+ codex: codexThreadId,
70
+ opencode: opencodeSessionId,
71
+ gemini: geminiSessionId,
72
+ },
73
+ } satisfies TaskResumeState
74
+
75
+ return hasResumeState(resume) ? resume : null
76
+ }
77
+
78
+ export function extractSessionResumeState(session: Partial<Session> | null | undefined): TaskResumeState | null {
79
+ if (!session) return null
80
+
81
+ const claudeSessionId = normalizeResumeHandle(session.claudeSessionId)
82
+ const codexThreadId = normalizeResumeHandle(session.codexThreadId)
83
+ const opencodeSessionId = normalizeResumeHandle(session.opencodeSessionId)
84
+ const delegateResumeIds = session.delegateResumeIds && typeof session.delegateResumeIds === 'object'
85
+ ? { ...buildEmptyDelegateResumeIds(), ...session.delegateResumeIds }
86
+ : buildEmptyDelegateResumeIds()
87
+
88
+ const resume = {
89
+ claudeSessionId,
90
+ codexThreadId,
91
+ opencodeSessionId,
92
+ delegateResumeIds: {
93
+ claudeCode: normalizeResumeHandle(delegateResumeIds.claudeCode) || claudeSessionId,
94
+ codex: normalizeResumeHandle(delegateResumeIds.codex) || codexThreadId,
95
+ opencode: normalizeResumeHandle(delegateResumeIds.opencode) || opencodeSessionId,
96
+ gemini: normalizeResumeHandle(delegateResumeIds.gemini),
97
+ },
98
+ } satisfies TaskResumeState
99
+
100
+ return hasResumeState(resume) ? resume : null
101
+ }
102
+
103
+ export function resolveTaskResumeContext(
104
+ task: BoardTask,
105
+ tasksById: Record<string, BoardTask>,
106
+ sessionsById?: Record<string, SessionLike | Session>,
107
+ ): TaskResumeContext | null {
108
+ const candidates: Array<{ source: TaskResumeContext['source']; taskId: string | null | undefined }> = [
109
+ { source: 'self', taskId: task.id },
110
+ { source: 'delegated_from_task', taskId: task.delegatedFromTaskId },
111
+ ...((Array.isArray(task.blockedBy) ? task.blockedBy : []).map((taskId) => ({ source: 'blocked_by' as const, taskId }))),
112
+ ]
113
+ const seen = new Set<string>()
114
+
115
+ for (const candidate of candidates) {
116
+ const taskId = typeof candidate.taskId === 'string' ? candidate.taskId.trim() : ''
117
+ if (!taskId || seen.has(taskId)) continue
118
+ seen.add(taskId)
119
+ const sourceTask = taskId === task.id ? task : tasksById[taskId]
120
+ if (!sourceTask) continue
121
+ const sourceSessionId = normalizeResumeHandle(sourceTask.checkpoint?.lastSessionId) || normalizeResumeHandle(sourceTask.sessionId)
122
+ const resume = extractTaskResumeState(sourceTask)
123
+ || (sourceSessionId && sessionsById?.[sourceSessionId]
124
+ ? extractSessionResumeState(sessionsById[sourceSessionId] as Session)
125
+ : null)
126
+ if (!resume) continue
127
+ return {
128
+ source: candidate.source,
129
+ sourceTaskId: sourceTask.id,
130
+ sourceTaskTitle: sourceTask.title,
131
+ sourceSessionId,
132
+ resume,
133
+ }
134
+ }
135
+
136
+ return null
137
+ }
138
+
139
+ export function applyTaskResumeStateToSession(session: Session, resume: TaskResumeState | null | undefined): boolean {
140
+ if (!hasResumeState(resume)) return false
141
+
142
+ let changed = false
143
+ const directFields: Array<['claudeSessionId' | 'codexThreadId' | 'opencodeSessionId', string | null]> = [
144
+ ['claudeSessionId', resume.claudeSessionId],
145
+ ['codexThreadId', resume.codexThreadId],
146
+ ['opencodeSessionId', resume.opencodeSessionId],
147
+ ]
148
+ for (const [key, value] of directFields) {
149
+ if (!value || session[key] === value) continue
150
+ session[key] = value
151
+ changed = true
152
+ }
153
+
154
+ const currentDelegateResume = session.delegateResumeIds && typeof session.delegateResumeIds === 'object'
155
+ ? { ...buildEmptyDelegateResumeIds(), ...session.delegateResumeIds }
156
+ : buildEmptyDelegateResumeIds()
157
+ for (const [key, value] of Object.entries(resume.delegateResumeIds) as Array<[keyof NonNullable<Session['delegateResumeIds']>, string | null]>) {
158
+ if (!value || currentDelegateResume[key] === value) continue
159
+ currentDelegateResume[key] = value
160
+ changed = true
161
+ }
162
+ if (changed) session.delegateResumeIds = currentDelegateResume
163
+ return changed
164
+ }
165
+
166
+ export function resolveReusableTaskSessionId(
167
+ task: BoardTask,
168
+ tasks: Record<string, BoardTask>,
169
+ sessions: Record<string, SessionLike>,
170
+ ): string {
171
+ const candidateTaskIds = [
172
+ task.id,
173
+ typeof task.delegatedFromTaskId === 'string' ? task.delegatedFromTaskId : '',
174
+ ...(Array.isArray(task.blockedBy) ? task.blockedBy : []),
175
+ ]
176
+ const seen = new Set<string>()
177
+ for (const candidateTaskId of candidateTaskIds) {
178
+ const taskId = typeof candidateTaskId === 'string' ? candidateTaskId.trim() : ''
179
+ if (!taskId || seen.has(taskId)) continue
180
+ seen.add(taskId)
181
+ const sourceTask = taskId === task.id ? task : tasks[taskId]
182
+ if (!sourceTask) continue
183
+ const candidates = [
184
+ normalizeResumeHandle(sourceTask.checkpoint?.lastSessionId),
185
+ normalizeResumeHandle(sourceTask.sessionId),
186
+ ]
187
+ for (const candidate of candidates) {
188
+ if (candidate && sessions[candidate]) return candidate
189
+ }
190
+ }
191
+ return ''
192
+ }
193
+
194
+ export function buildTaskContinuationNote(
195
+ reusedExistingSession: boolean,
196
+ resumeContext: TaskResumeContext | null,
197
+ ): string {
198
+ const notes: string[] = []
199
+ if (reusedExistingSession) {
200
+ notes.push('Reusing the previous execution session for this task.')
201
+ }
202
+ if (resumeContext?.source === 'delegated_from_task' || resumeContext?.source === 'blocked_by') {
203
+ notes.push(`Stored CLI context is available from related task "${resumeContext.sourceTaskTitle}".`)
204
+ } else if (resumeContext?.source === 'self' && !reusedExistingSession) {
205
+ notes.push('Stored CLI resume handles are available for continuation.')
206
+ }
207
+ return notes.length ? `\n\n${notes.join(' ')}` : ''
208
+ }
@@ -0,0 +1,108 @@
1
+ import assert from 'node:assert/strict'
2
+ import { describe, it } from 'node:test'
3
+
4
+ import { computeTaskFingerprint } from '@/lib/task-dedupe'
5
+ import type { BoardTask } from '@/types'
6
+
7
+ import { applyTaskPatch, prepareTaskCreation } from './task-service'
8
+
9
+ function makeTask(overrides: Partial<BoardTask> = {}): BoardTask {
10
+ return {
11
+ id: 'task-1',
12
+ title: 'Example Task',
13
+ description: 'Do the work',
14
+ status: 'backlog',
15
+ agentId: 'agent-1',
16
+ sessionId: null,
17
+ result: null,
18
+ error: null,
19
+ createdAt: 1,
20
+ updatedAt: 1,
21
+ queuedAt: null,
22
+ startedAt: null,
23
+ completedAt: null,
24
+ ...overrides,
25
+ }
26
+ }
27
+
28
+ describe('task service helpers', () => {
29
+ it('prepareTaskCreation derives a title and normalizes running to queued', () => {
30
+ const prepared = prepareTaskCreation({
31
+ id: 'task-create',
32
+ input: {
33
+ description: 'Please create a new login page for the app.',
34
+ status: 'running',
35
+ agentId: 'agent-1',
36
+ },
37
+ tasks: {},
38
+ now: 50,
39
+ deriveTitleFromDescription: true,
40
+ requireMeaningfulTitle: true,
41
+ })
42
+
43
+ assert.equal(prepared.ok, true)
44
+ if (!prepared.ok) return
45
+ assert.equal(prepared.duplicate, null)
46
+ assert.equal(prepared.task.title, 'a new login page for the app.')
47
+ assert.equal(prepared.task.status, 'queued')
48
+ assert.equal((prepared.task.fingerprint || '').length, 16)
49
+ })
50
+
51
+ it('prepareTaskCreation returns an existing duplicate task instead of a new one', () => {
52
+ const existing = makeTask({
53
+ id: 'existing-task',
54
+ title: 'Unique dedupe title',
55
+ agentId: 'agent-1',
56
+ fingerprint: computeTaskFingerprint('Unique dedupe title', 'agent-1'),
57
+ })
58
+ const prepared = prepareTaskCreation({
59
+ id: 'task-create',
60
+ input: {
61
+ title: 'Unique dedupe title',
62
+ description: 'Duplicate task',
63
+ agentId: 'agent-1',
64
+ },
65
+ tasks: { 'existing-task': existing },
66
+ now: 60,
67
+ })
68
+
69
+ assert.equal(prepared.ok, true)
70
+ if (!prepared.ok) return
71
+ assert.equal(prepared.duplicate?.id, 'existing-task')
72
+ })
73
+
74
+ it('applyTaskPatch strips invalid statuses and clears nullable project ids', () => {
75
+ const task = makeTask({
76
+ status: 'backlog',
77
+ projectId: 'project-1',
78
+ })
79
+
80
+ applyTaskPatch({
81
+ task,
82
+ patch: {
83
+ status: 'bananas',
84
+ projectId: null,
85
+ },
86
+ now: 75,
87
+ clearProjectIdWhenNull: true,
88
+ })
89
+
90
+ assert.equal(task.status, 'backlog')
91
+ assert.equal('projectId' in task, false)
92
+ assert.equal(task.updatedAt, 75)
93
+ })
94
+
95
+ it('applyTaskPatch normalizes running updates to queued', () => {
96
+ const task = makeTask({
97
+ status: 'backlog',
98
+ })
99
+
100
+ applyTaskPatch({
101
+ task,
102
+ patch: { status: 'running' },
103
+ now: 90,
104
+ })
105
+
106
+ assert.equal(task.status, 'queued')
107
+ })
108
+ })