@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
@@ -21,8 +21,7 @@ import {
21
21
  decryptKey,
22
22
  } from '../storage'
23
23
  import { resolveScheduleName } from '@/lib/schedule-name'
24
- import { findDuplicateSchedule, findEquivalentSchedules, type ScheduleLike } from '@/lib/schedule-dedupe'
25
- import { computeTaskFingerprint, findDuplicateTask } from '@/lib/task-dedupe'
24
+ import type { ScheduleLike } from '@/lib/schedule-dedupe'
26
25
  import {
27
26
  hasManagedAgentAssignmentInput,
28
27
  isDelegationTaskPayload,
@@ -30,13 +29,23 @@ import {
30
29
  resolveManagedAgentAssignment,
31
30
  validateManagedAgentAssignment,
32
31
  } from '@/lib/server/agent-assignment'
33
- import { normalizeTaskQualityGate } from '@/lib/server/task-quality-gate'
34
- import { normalizeSchedulePayload } from '@/lib/server/schedule-normalization'
35
32
  import { buildProjectSnapshot, ensureProjectWorkspace, normalizeProjectCreateInput, normalizeProjectPatchInput } from '@/lib/server/project-utils'
33
+ import {
34
+ getScheduleClusterIds,
35
+ prepareScheduleCreate,
36
+ prepareScheduleUpdate,
37
+ } from '@/lib/server/schedule-service'
38
+ import {
39
+ applyTaskContinuationDefaults,
40
+ applyTaskPatch,
41
+ deriveTaskTitle,
42
+ prepareTaskCreation,
43
+ } from '@/lib/server/task-service'
36
44
  import type { ToolBuildContext } from './context'
37
45
  import { safePath, findBinaryOnPath } from './context'
38
46
  import { normalizeToolInputArgs } from './normalize-tool-args'
39
47
  import type { BoardTask } from '@/types'
48
+ import { dedup } from '@/lib/shared-utils'
40
49
 
41
50
  // ---------------------------------------------------------------------------
42
51
  // Document helpers
@@ -67,7 +76,7 @@ function extractDocumentText(filePath: string): { text: string; method: string }
67
76
  return { text: out.stdout || '', method: 'pdftotext' }
68
77
  }
69
78
 
70
- if (['.txt', '.md', '.markdown', '.json', '.csv', '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.py', '.go', '.rs', '.java', '.yaml', '.yml'].includes(ext)) {
79
+ if (['.txt', '.md', '.markdown', '.json', '.csv', '', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.py', '.go', '.rs', '.java', '.yaml', '.yml'].includes(ext)) {
71
80
  return { text: readUtf8Text(), method: 'utf8' }
72
81
  }
73
82
 
@@ -100,24 +109,6 @@ function trimDocumentContent(text: string): string {
100
109
  return normalized.slice(0, MAX_DOCUMENT_TEXT_CHARS)
101
110
  }
102
111
 
103
- function deriveTaskTitle(input: { title?: unknown; description?: unknown }): string {
104
- const explicit = typeof input.title === 'string' ? input.title.replace(/\s+/g, ' ').trim() : ''
105
- if (explicit && !/^untitled task$/i.test(explicit)) return explicit.slice(0, 120)
106
-
107
- const description = typeof input.description === 'string'
108
- ? input.description.replace(/\s+/g, ' ').trim()
109
- : ''
110
- if (!description) return ''
111
-
112
- const firstSentence = description.split(/[.!?]\s+/)[0] || description
113
- const compact = firstSentence
114
- .replace(/^please\s+/i, '')
115
- .replace(/^(create|make|build|implement|write)\s+/i, '')
116
- .trim()
117
- if (!compact) return ''
118
- return compact.slice(0, 120)
119
- }
120
-
121
112
  function validateAgentSoulPayload(value: unknown): string | null {
122
113
  if (value === undefined) return null
123
114
  if (typeof value === 'string') return null
@@ -235,60 +226,6 @@ function sanitizeConnectorCrudPayload(
235
226
  return out
236
227
  }
237
228
 
238
- const TASK_STATUS_VALUES = new Set([
239
- 'backlog',
240
- 'queued',
241
- 'running',
242
- 'completed',
243
- 'failed',
244
- 'archived',
245
- ])
246
-
247
- function normalizeTaskStatusInput(status: unknown, prevStatus?: string): string | null {
248
- if (typeof status !== 'string') return null
249
- const normalized = status.trim().toLowerCase()
250
- if (!TASK_STATUS_VALUES.has(normalized)) return null
251
- if (normalized === 'running' && prevStatus !== 'running') return 'queued'
252
- return normalized
253
- }
254
-
255
- function normalizeTaskIdList(value: unknown): string[] {
256
- const rawValues = Array.isArray(value)
257
- ? value
258
- : typeof value === 'string'
259
- ? value.split(',')
260
- : []
261
- const seen = new Set<string>()
262
- const out: string[] = []
263
- for (const entry of rawValues) {
264
- const normalized = typeof entry === 'string' ? entry.trim() : ''
265
- if (!normalized || seen.has(normalized)) continue
266
- seen.add(normalized)
267
- out.push(normalized)
268
- }
269
- return out
270
- }
271
-
272
- function pickFirstTaskId(value: unknown): string | null {
273
- const ids = normalizeTaskIdList(value)
274
- return ids[0] || null
275
- }
276
-
277
- function buildScheduleCreatorScope(schedule: Record<string, unknown> | null | undefined): {
278
- agentId?: string | null
279
- sessionId?: string | null
280
- } | null {
281
- if (!schedule || typeof schedule !== 'object') return null
282
- const agentId = typeof schedule.createdByAgentId === 'string' && schedule.createdByAgentId.trim()
283
- ? schedule.createdByAgentId.trim()
284
- : null
285
- const sessionId = typeof schedule.createdInSessionId === 'string' && schedule.createdInSessionId.trim()
286
- ? schedule.createdInSessionId.trim()
287
- : null
288
- if (!agentId && !sessionId) return null
289
- return { agentId, sessionId }
290
- }
291
-
292
229
  function deriveScheduleFollowupTarget(sessionId: string | null | undefined): {
293
230
  followupConnectorId?: string | null
294
231
  followupChannelId?: string | null
@@ -336,103 +273,6 @@ function deriveScheduleFollowupTarget(sessionId: string | null | undefined): {
336
273
  return {}
337
274
  }
338
275
 
339
- function findRelatedScheduleIds(
340
- schedules: Record<string, ScheduleLike>,
341
- schedule: Record<string, unknown> | null | undefined,
342
- opts: { ignoreId?: string | null } = {},
343
- ): string[] {
344
- if (!schedule || typeof schedule !== 'object') return []
345
- const scope = buildScheduleCreatorScope(schedule)
346
- if (!scope?.sessionId) return []
347
- const matches = findEquivalentSchedules(schedules, {
348
- id: typeof schedule.id === 'string' ? schedule.id : null,
349
- agentId: typeof schedule.agentId === 'string' ? schedule.agentId : null,
350
- taskPrompt: typeof schedule.taskPrompt === 'string' ? schedule.taskPrompt : null,
351
- scheduleType: typeof schedule.scheduleType === 'string' ? schedule.scheduleType : null,
352
- cron: typeof schedule.cron === 'string' ? schedule.cron : null,
353
- intervalMs: typeof schedule.intervalMs === 'number' ? schedule.intervalMs : null,
354
- runAt: typeof schedule.runAt === 'number' ? schedule.runAt : null,
355
- createdByAgentId: scope.agentId,
356
- createdInSessionId: scope.sessionId,
357
- }, {
358
- ignoreId: opts.ignoreId || (typeof schedule.id === 'string' ? schedule.id : null),
359
- creatorScope: scope,
360
- })
361
- return Array.from(new Set(matches
362
- .map((entry) => (typeof entry.id === 'string' ? entry.id : ''))
363
- .filter(Boolean)))
364
- }
365
-
366
- function applyTaskContinuationDefaults(
367
- parsed: Record<string, unknown>,
368
- tasks: Record<string, BoardTask>,
369
- explicitInput?: Record<string, unknown>,
370
- ): string | null {
371
- const explicit = explicitInput || parsed
372
- const continuationTaskId = pickFirstTaskId(parsed.continueFromTaskId)
373
- || pickFirstTaskId(parsed.followUpToTaskId)
374
- || pickFirstTaskId(parsed.resumeFromTaskId)
375
- const blockedBy = [
376
- ...normalizeTaskIdList(parsed.blockedBy),
377
- ...normalizeTaskIdList(parsed.dependsOn),
378
- ...normalizeTaskIdList(parsed.dependsOnTaskIds),
379
- ...normalizeTaskIdList(parsed.prerequisiteTaskIds),
380
- ]
381
- if (continuationTaskId && !blockedBy.includes(continuationTaskId)) {
382
- blockedBy.unshift(continuationTaskId)
383
- }
384
- if (blockedBy.length > 0) parsed.blockedBy = blockedBy
385
-
386
- if (continuationTaskId) {
387
- const sourceTask = tasks[continuationTaskId]
388
- if (!sourceTask) return `Error: source task "${continuationTaskId}" not found.`
389
-
390
- if (!Object.prototype.hasOwnProperty.call(explicit, 'projectId') && typeof sourceTask.projectId === 'string' && sourceTask.projectId.trim()) {
391
- parsed.projectId = sourceTask.projectId.trim()
392
- }
393
- if (
394
- !Object.prototype.hasOwnProperty.call(explicit, 'agentId')
395
- && !hasManagedAgentAssignmentInput(explicit)
396
- && typeof sourceTask.agentId === 'string'
397
- && sourceTask.agentId.trim()
398
- ) {
399
- parsed.agentId = sourceTask.agentId.trim()
400
- }
401
- if (!Object.prototype.hasOwnProperty.call(explicit, 'cwd') && typeof sourceTask.cwd === 'string' && sourceTask.cwd.trim()) {
402
- parsed.cwd = sourceTask.cwd.trim()
403
- }
404
- const sourceSessionId = typeof sourceTask.checkpoint?.lastSessionId === 'string' && sourceTask.checkpoint.lastSessionId.trim()
405
- ? sourceTask.checkpoint.lastSessionId.trim()
406
- : typeof sourceTask.sessionId === 'string' && sourceTask.sessionId.trim()
407
- ? sourceTask.sessionId.trim()
408
- : ''
409
- if (!Object.prototype.hasOwnProperty.call(explicit, 'sessionId') && sourceSessionId) {
410
- parsed.sessionId = sourceSessionId
411
- }
412
-
413
- const resumeFieldMap: Array<[keyof BoardTask, string]> = [
414
- ['cliResumeId', 'cliResumeId'],
415
- ['cliProvider', 'cliProvider'],
416
- ['claudeResumeId', 'claudeResumeId'],
417
- ['codexResumeId', 'codexResumeId'],
418
- ['opencodeResumeId', 'opencodeResumeId'],
419
- ['geminiResumeId', 'geminiResumeId'],
420
- ]
421
- for (const [sourceKey, targetKey] of resumeFieldMap) {
422
- const value = sourceTask[sourceKey]
423
- if (Object.prototype.hasOwnProperty.call(explicit, targetKey)) continue
424
- if (typeof value === 'string' && value.trim()) {
425
- parsed[targetKey] = value.trim()
426
- }
427
- }
428
- }
429
-
430
- for (const aliasKey of ['continueFromTaskId', 'followUpToTaskId', 'resumeFromTaskId', 'dependsOn', 'dependsOnTaskIds', 'prerequisiteTaskIds']) {
431
- delete parsed[aliasKey]
432
- }
433
- return null
434
- }
435
-
436
276
  // ---------------------------------------------------------------------------
437
277
  // RESOURCE_DEFAULTS
438
278
  // ---------------------------------------------------------------------------
@@ -722,68 +562,55 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
722
562
  if (assignmentError) return assignmentError
723
563
  parsed.agentId = resolution.agentId
724
564
  }
565
+ let preparedManagedTask: BoardTask | null = null
566
+ let preparedManagedSchedule: any = null
725
567
  if (toolKey === 'manage_schedules') {
726
- const normalizedSchedule = normalizeSchedulePayload(parsed as Record<string, unknown>, {
727
- cwd,
568
+ const prepared = prepareScheduleCreate({
569
+ input: parsed as Record<string, unknown>,
570
+ schedules: all as Record<string, ScheduleLike>,
728
571
  now,
729
- })
730
- if (!normalizedSchedule.ok) return normalizedSchedule.error
731
- Object.assign(parsed, normalizedSchedule.value)
732
- const duplicate = findDuplicateSchedule(all as Record<string, ScheduleLike>, {
733
- agentId: parsed.agentId || null,
734
- taskPrompt: parsed.taskPrompt || '',
735
- scheduleType: parsed.scheduleType || 'interval',
736
- cron: parsed.cron,
737
- intervalMs: parsed.intervalMs,
738
- runAt: parsed.runAt,
739
- createdByAgentId: ctx?.agentId || null,
740
- createdInSessionId: ctx?.sessionId || null,
741
- }, {
572
+ cwd,
742
573
  creatorScope: {
743
574
  agentId: ctx?.agentId || null,
744
575
  sessionId: ctx?.sessionId || null,
745
576
  },
577
+ dedupeCreatorScope: {
578
+ agentId: ctx?.agentId || null,
579
+ sessionId: ctx?.sessionId || null,
580
+ },
581
+ followupTarget: deriveScheduleFollowupTarget(ctx?.sessionId || null),
746
582
  })
747
- if (duplicate) {
748
- let changed = false
749
- const duplicateId = typeof duplicate.id === 'string' ? duplicate.id : ''
750
- const nextName = resolveScheduleName({
751
- name: parsed.name ?? duplicate.name,
752
- taskPrompt: parsed.taskPrompt ?? duplicate.taskPrompt,
753
- })
754
- if (nextName && nextName !== duplicate.name) {
755
- duplicate.name = nextName
756
- changed = true
757
- }
758
- const normalizedStatus = typeof parsed.status === 'string' ? parsed.status.trim().toLowerCase() : ''
759
- if ((normalizedStatus === 'active' || normalizedStatus === 'paused') && duplicate.status !== normalizedStatus) {
760
- duplicate.status = normalizedStatus
761
- changed = true
762
- }
763
- if (changed) {
764
- duplicate.updatedAt = now
765
- if (duplicateId) all[duplicateId] = duplicate
766
- res.save(all)
583
+ if (!prepared.ok) return prepared.error
584
+ if (prepared.kind === 'duplicate') {
585
+ for (const [duplicateId, schedule] of prepared.entries) {
586
+ all[duplicateId] = schedule
767
587
  }
588
+ if (prepared.entries.length > 0) res.save(all)
768
589
  return JSON.stringify({
769
- ...duplicate,
590
+ ...prepared.schedule,
770
591
  deduplicated: true,
771
592
  })
772
593
  }
594
+ preparedManagedSchedule = prepared.schedule
773
595
  }
774
596
  if (toolKey === 'manage_tasks') {
775
- parsed.title = deriveTaskTitle(parsed)
776
- if (!parsed.title || /^untitled task$/i.test(parsed.title)) {
777
- return 'Error: manage_tasks create requires a specific title or a meaningful description.'
778
- }
779
- parsed.status = normalizeTaskStatusInput(parsed.status) || 'backlog'
780
- if (!parsed.cwd && cwd) parsed.cwd = cwd
781
- if (Object.prototype.hasOwnProperty.call(parsed, 'qualityGate')) {
782
- const settings = loadSettings()
783
- parsed.qualityGate = parsed.qualityGate
784
- ? normalizeTaskQualityGate(parsed.qualityGate, settings)
785
- : null
597
+ const prepared = prepareTaskCreation({
598
+ id: genId(),
599
+ input: parsed as Record<string, unknown>,
600
+ tasks: all as Record<string, BoardTask>,
601
+ now,
602
+ settings: loadSettings(),
603
+ fallbackAgentId: ctx?.agentId || null,
604
+ defaultCwd: cwd,
605
+ deriveTitleFromDescription: true,
606
+ requireMeaningfulTitle: true,
607
+ seed: parsed as Record<string, unknown>,
608
+ })
609
+ if (!prepared.ok) return prepared.error
610
+ if (prepared.duplicate) {
611
+ return JSON.stringify({ ...prepared.duplicate, deduplicated: true })
786
612
  }
613
+ preparedManagedTask = prepared.task
787
614
  }
788
615
  if (toolKey === 'manage_agents' && Object.prototype.hasOwnProperty.call(parsed, 'soul')) {
789
616
  const soulError = validateAgentSoulPayload((parsed as Record<string, unknown>).soul)
@@ -795,38 +622,33 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
795
622
  return JSON.stringify({ ...duplicateAgent, deduplicated: true })
796
623
  }
797
624
  }
798
- // Task dedup
799
- if (toolKey === 'manage_tasks') {
800
- const fp = computeTaskFingerprint(parsed.title || 'Untitled Task', parsed.agentId || ctx?.agentId || '')
801
- parsed.fingerprint = fp
802
- const dupe = findDuplicateTask(all as Record<string, import('@/types').BoardTask>, { fingerprint: fp })
803
- if (dupe) {
804
- return JSON.stringify({ ...dupe, deduplicated: true })
805
- }
806
- }
807
- const newId = genId()
808
- const scheduleFollowupTarget = toolKey === 'manage_schedules'
809
- ? deriveScheduleFollowupTarget(ctx?.sessionId || null)
810
- : {}
811
- const entry = {
812
- id: newId,
813
- ...parsed,
814
- createdByAgentId: ctx?.agentId || null,
815
- createdInSessionId: ctx?.sessionId || null,
816
- ...scheduleFollowupTarget,
817
- createdAt: now,
818
- updatedAt: now,
819
- }
625
+ const newId = preparedManagedTask?.id || preparedManagedSchedule?.id || genId()
626
+ const entry = toolKey === 'manage_tasks' && preparedManagedTask
627
+ ? {
628
+ ...preparedManagedTask,
629
+ createdByAgentId: ctx?.agentId || null,
630
+ createdInSessionId: ctx?.sessionId || null,
631
+ }
632
+ : toolKey === 'manage_schedules' && preparedManagedSchedule
633
+ ? preparedManagedSchedule
634
+ : {
635
+ id: newId,
636
+ ...parsed,
637
+ createdByAgentId: ctx?.agentId || null,
638
+ createdInSessionId: ctx?.sessionId || null,
639
+ createdAt: now,
640
+ updatedAt: now,
641
+ }
820
642
  let responseEntry: unknown = entry
821
643
  if (toolKey === 'manage_secrets') {
822
644
  const secretValue = typeof parsed.value === 'string' ? parsed.value : null
823
645
  if (!secretValue) return 'Error: data.value is required to create a secret.'
824
646
  const normalizedScope = parsed.scope === 'agent' ? 'agent' : 'global'
825
647
  const normalizedAgentIds = normalizedScope === 'agent'
826
- ? Array.from(new Set([
827
- ...(Array.isArray(parsed.agentIds) ? parsed.agentIds.filter((x: any) => typeof x === 'string') : []),
648
+ ? dedup([
649
+ ...(Array.isArray(parsed.agentIds) ? parsed.agentIds.filter((x: unknown) => typeof x === 'string') as string[] : []),
828
650
  ...(ctx?.agentId ? [ctx.agentId] : []),
829
- ]))
651
+ ])
830
652
  : []
831
653
  const stored = {
832
654
  ...entry,
@@ -847,21 +669,6 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
847
669
  all[newId] = entry
848
670
  }
849
671
 
850
- if (toolKey === 'manage_tasks' && entry.status === 'completed') {
851
- const { formatValidationFailure, validateTaskCompletion } = await import('../task-validation')
852
- const { ensureTaskCompletionReport } = await import('../task-reports')
853
- const settings = loadSettings()
854
- const report = ensureTaskCompletionReport(entry as any)
855
- if (report?.relativePath) (entry as any).completionReportPath = report.relativePath
856
- const validation = validateTaskCompletion(entry as any, { report, settings })
857
- ;(entry as any).validation = validation
858
- if (!validation.ok) {
859
- entry.status = 'failed'
860
- ;(entry as any).completedAt = null
861
- ;(entry as any).error = formatValidationFailure(validation.reasons).slice(0, 500)
862
- }
863
- }
864
-
865
672
  res.save(all)
866
673
  if (toolKey === 'manage_tasks' && entry.status === 'queued') {
867
674
  const { enqueueTask } = await import('../queue')
@@ -898,32 +705,23 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
898
705
  if (continuationError) return continuationError
899
706
  }
900
707
  const prevStatus = all[effectiveId]?.status
901
- if (toolKey === 'manage_tasks' && Object.prototype.hasOwnProperty.call(parsedRecord, 'status')) {
902
- const normalized = normalizeTaskStatusInput(parsedRecord.status, prevStatus)
903
- if (normalized) parsedRecord.status = normalized
904
- else delete parsedRecord.status
905
- }
906
- if (toolKey === 'manage_tasks' && Object.prototype.hasOwnProperty.call(parsedRecord, 'qualityGate')) {
907
- const settings = loadSettings()
908
- parsedRecord.qualityGate = parsedRecord.qualityGate
909
- ? normalizeTaskQualityGate(parsedRecord.qualityGate, settings)
910
- : null
911
- }
912
- if (toolKey === 'manage_tasks' || toolKey === 'manage_schedules') {
913
- const agents = loadAgents()
708
+ const managedAgents = toolKey === 'manage_tasks' || toolKey === 'manage_schedules'
709
+ ? loadAgents()
710
+ : null
711
+ if (managedAgents) {
914
712
  const requestedClear = Object.prototype.hasOwnProperty.call(parsedRecord, 'agentId') && parsedRecord.agentId == null
915
713
  const shouldResolveAssignment = requestedClear
916
714
  || hasManagedAgentAssignmentInput(parsedRecord)
917
715
  if (shouldResolveAssignment) {
918
716
  const resolution = resolveManagedAgentAssignment(
919
717
  parsedRecord,
920
- agents,
718
+ managedAgents,
921
719
  null,
922
720
  { allowDescription: false },
923
721
  )
924
722
  const assignmentError = validateManagedAgentAssignment({
925
723
  resourceLabel: res.label,
926
- agents,
724
+ agents: managedAgents,
927
725
  assignScope,
928
726
  currentAgentId: ctx?.agentId || null,
929
727
  targetAgentId: requestedClear ? null : resolution.agentId,
@@ -939,40 +737,40 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
939
737
  ? resolveDelegatorAgentId({
940
738
  ...all[effectiveId],
941
739
  ...parsedRecord,
942
- }, agents, ctx?.agentId || null)
740
+ }, managedAgents, ctx?.agentId || null)
943
741
  : null,
944
742
  })
945
743
  if (assignmentError) return assignmentError
946
744
  if (!requestedClear) parsedRecord.agentId = resolution.agentId
947
745
  }
948
746
  }
949
- all[effectiveId] = { ...all[effectiveId], ...parsed, updatedAt: Date.now() }
950
747
  if (toolKey === 'manage_schedules') {
951
- const normalizedSchedule = normalizeSchedulePayload(all[effectiveId] as Record<string, unknown>, {
952
- cwd,
748
+ const prepared = prepareScheduleUpdate({
749
+ id: effectiveId,
750
+ current: all[effectiveId] as Record<string, unknown>,
751
+ patch: parsedRecord,
752
+ schedules: all as Record<string, ScheduleLike>,
953
753
  now: Date.now(),
754
+ cwd,
755
+ agentExists: (agentId) => Boolean(managedAgents?.[agentId]),
756
+ propagateEquivalentStatuses: true,
757
+ propagationSource: previousEntry as Record<string, unknown>,
954
758
  })
955
- if (!normalizedSchedule.ok) return normalizedSchedule.error
956
- all[effectiveId] = {
957
- ...all[effectiveId],
958
- ...normalizedSchedule.value,
959
- updatedAt: Date.now(),
960
- }
961
- const nextStatus = typeof all[effectiveId].status === 'string' ? all[effectiveId].status.trim().toLowerCase() : ''
962
- if (nextStatus === 'paused' || nextStatus === 'completed' || nextStatus === 'failed') {
963
- const relatedIds = findRelatedScheduleIds(all as Record<string, ScheduleLike>, previousEntry, {
964
- ignoreId: effectiveId,
965
- })
966
- for (const relatedId of relatedIds) {
967
- if (!all[relatedId]) continue
968
- all[relatedId] = {
969
- ...all[relatedId],
970
- status: nextStatus,
971
- updatedAt: Date.now(),
972
- }
973
- }
974
- affectedScheduleIds = [effectiveId, ...relatedIds]
759
+ if (!prepared.ok) return prepared.error
760
+ for (const [scheduleId, schedule] of prepared.entries) {
761
+ all[scheduleId] = schedule
975
762
  }
763
+ affectedScheduleIds = prepared.affectedScheduleIds.length > 1 ? prepared.affectedScheduleIds : null
764
+ } else if (toolKey === 'manage_tasks') {
765
+ applyTaskPatch({
766
+ task: all[effectiveId] as BoardTask,
767
+ patch: parsedRecord,
768
+ now: Date.now(),
769
+ settings: loadSettings(),
770
+ preserveCompletedAt: true,
771
+ })
772
+ } else {
773
+ all[effectiveId] = { ...all[effectiveId], ...parsed, updatedAt: Date.now() }
976
774
  }
977
775
  if (toolKey === 'manage_secrets') {
978
776
  if (!canAccessSecret(all[effectiveId])) return 'Error: you do not have access to this secret.'
@@ -987,10 +785,10 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
987
785
  : Array.isArray(all[effectiveId].agentIds)
988
786
  ? all[effectiveId].agentIds
989
787
  : []
990
- all[effectiveId].agentIds = Array.from(new Set([
788
+ all[effectiveId].agentIds = dedup([
991
789
  ...incomingIds,
992
790
  ...(ctx?.agentId ? [ctx.agentId] : []),
993
- ]))
791
+ ])
994
792
  } else {
995
793
  all[effectiveId].agentIds = []
996
794
  }
@@ -1006,23 +804,6 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
1006
804
  delete all[effectiveId].value
1007
805
  }
1008
806
 
1009
- if (toolKey === 'manage_tasks' && all[effectiveId].status === 'completed') {
1010
- const { formatValidationFailure, validateTaskCompletion } = await import('../task-validation')
1011
- const { ensureTaskCompletionReport } = await import('../task-reports')
1012
- const settings = loadSettings()
1013
- const report = ensureTaskCompletionReport(all[effectiveId] as any)
1014
- if (report?.relativePath) (all[effectiveId] as any).completionReportPath = report.relativePath
1015
- const validation = validateTaskCompletion(all[effectiveId] as any, { report, settings })
1016
- ;(all[effectiveId] as any).validation = validation
1017
- if (!validation.ok) {
1018
- all[effectiveId].status = 'failed'
1019
- ;(all[effectiveId] as any).completedAt = null
1020
- ;(all[effectiveId] as any).error = formatValidationFailure(validation.reasons).slice(0, 500)
1021
- } else if ((all[effectiveId] as any).completedAt == null) {
1022
- ;(all[effectiveId] as any).completedAt = Date.now()
1023
- }
1024
- }
1025
-
1026
807
  res.save(all)
1027
808
  if (toolKey === 'manage_projects') {
1028
809
  ensureProjectWorkspace(effectiveId, all[effectiveId].name)
@@ -1063,7 +844,7 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
1063
844
  return 'Error: you do not have access to this secret.'
1064
845
  }
1065
846
  const deletedIds = toolKey === 'manage_schedules'
1066
- ? [effectiveId, ...findRelatedScheduleIds(all as Record<string, ScheduleLike>, all[effectiveId], { ignoreId: effectiveId })]
847
+ ? getScheduleClusterIds(all as Record<string, ScheduleLike>, all[effectiveId])
1067
848
  : [effectiveId]
1068
849
  for (const deleteId of deletedIds) {
1069
850
  delete all[deleteId]
@@ -1082,10 +863,10 @@ export function buildCrudTools(bctx: ToolBuildContext): StructuredToolInterface[
1082
863
  if (changed) save(items)
1083
864
  }
1084
865
  clearProjectId(loadAgents, saveAgents)
1085
- clearProjectId(loadTasks, saveTasks)
1086
- clearProjectId(loadSchedules, saveSchedules)
1087
- clearProjectId(loadSkills, saveSkills)
1088
- clearProjectId(loadSecrets, saveSecrets)
866
+ clearProjectId(loadTasks, saveTasks as any)
867
+ clearProjectId(loadSchedules, saveSchedules as any)
868
+ clearProjectId(loadSkills, saveSkills as any)
869
+ clearProjectId(loadSecrets, saveSecrets as any)
1089
870
  }
1090
871
  return JSON.stringify({
1091
872
  deleted: effectiveId,
@@ -63,7 +63,7 @@ exit 2
63
63
  describe('delegate fallback', () => {
64
64
  it('falls back to another backend when Claude Code is unavailable', () => {
65
65
  const output = runWithFakeDelegates(`
66
- const mod = await import('./src/lib/server/session-tools/delegate.ts')
66
+ const mod = await import('./src/lib/server/session-tools/delegate')
67
67
  const { buildDelegateTools } = mod.default || mod['module.exports'] || mod
68
68
 
69
69
  const tools = buildDelegateTools({
@@ -93,7 +93,7 @@ describe('delegate fallback', () => {
93
93
 
94
94
  it('accepts wrapped function-call payloads with tool_name aliases', () => {
95
95
  const output = runWithFakeDelegates(`
96
- const mod = await import('./src/lib/server/session-tools/delegate.ts')
96
+ const mod = await import('./src/lib/server/session-tools/delegate')
97
97
  const { buildDelegateTools } = mod.default || mod['module.exports'] || mod
98
98
 
99
99
  const tools = buildDelegateTools({
@@ -133,7 +133,7 @@ describe('delegate fallback', () => {
133
133
 
134
134
  it('rejects delegating a locally available tool call', () => {
135
135
  const output = runWithFakeDelegates(`
136
- const mod = await import('./src/lib/server/session-tools/delegate.ts')
136
+ const mod = await import('./src/lib/server/session-tools/delegate')
137
137
  const { buildDelegateTools } = mod.default || mod['module.exports'] || mod
138
138
 
139
139
  const tools = buildDelegateTools({
@@ -169,7 +169,7 @@ describe('delegate fallback', () => {
169
169
 
170
170
  it('synthesizes a delegated task from write-style payloads', () => {
171
171
  const output = runWithFakeDelegates(`
172
- const mod = await import('./src/lib/server/session-tools/delegate.ts')
172
+ const mod = await import('./src/lib/server/session-tools/delegate')
173
173
  const { buildDelegateTools } = mod.default || mod['module.exports'] || mod
174
174
 
175
175
  const tools = buildDelegateTools({
@@ -205,7 +205,7 @@ describe('delegate fallback', () => {
205
205
 
206
206
  it('synthesizes a delegated task for action=start payloads that only provide files', () => {
207
207
  const output = runWithFakeDelegates(`
208
- const mod = await import('./src/lib/server/session-tools/delegate.ts')
208
+ const mod = await import('./src/lib/server/session-tools/delegate')
209
209
  const { buildDelegateTools } = mod.default || mod['module.exports'] || mod
210
210
 
211
211
  const tools = buildDelegateTools({
@@ -244,7 +244,7 @@ describe('delegate fallback', () => {
244
244
 
245
245
  it('uses nested data.task payloads from recent tool-call wrappers', () => {
246
246
  const output = runWithFakeDelegates(`
247
- const mod = await import('./src/lib/server/session-tools/delegate.ts')
247
+ const mod = await import('./src/lib/server/session-tools/delegate')
248
248
  const { buildDelegateTools } = mod.default || mod['module.exports'] || mod
249
249
 
250
250
  const tools = buildDelegateTools({
@@ -280,7 +280,7 @@ describe('delegate fallback', () => {
280
280
 
281
281
  it('falls back to reason text when malformed delegate wrappers omit task', () => {
282
282
  const output = runWithFakeDelegates(`
283
- const mod = await import('./src/lib/server/session-tools/delegate.ts')
283
+ const mod = await import('./src/lib/server/session-tools/delegate')
284
284
  const { buildDelegateTools } = mod.default || mod['module.exports'] || mod
285
285
 
286
286
  const tools = buildDelegateTools({
@@ -319,7 +319,7 @@ describe('delegate fallback', () => {
319
319
 
320
320
  it('accepts legacy id fields for lifecycle delegate actions', () => {
321
321
  const output = runWithFakeDelegates(`
322
- const mod = await import('./src/lib/server/session-tools/delegate.ts')
322
+ const mod = await import('./src/lib/server/session-tools/delegate')
323
323
  const { buildDelegateTools } = mod.default || mod['module.exports'] || mod
324
324
 
325
325
  const tools = buildDelegateTools({
@@ -347,7 +347,7 @@ describe('delegate fallback', () => {
347
347
 
348
348
  it('ranks authenticated delegate backends ahead of unauthenticated ones', () => {
349
349
  const output = runWithFakeDelegates(`
350
- const mod = await import('./src/lib/server/provider-health.ts')
350
+ const mod = await import('./src/lib/server/provider-health')
351
351
  const { rankDelegatesByHealth } = mod.default || mod['module.exports'] || mod
352
352
  const ranked = rankDelegatesByHealth(['delegate_to_claude_code', 'delegate_to_codex_cli'])
353
353
  console.log(JSON.stringify(ranked))