@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
@@ -1,92 +1,49 @@
1
1
  import { genId } from '@/lib/id'
2
+ import { dedup, hmrSingleton } from '@/lib/shared-utils'
2
3
  import fs from 'node:fs'
3
4
  import path from 'node:path'
4
- import { loadTasks, saveTasks, loadQueue, saveQueue, loadAgents, loadSchedules, saveSchedules, loadSessions, saveSessions, loadSettings, loadConnectors, UPLOAD_DIR } from './storage'
5
+ import { loadTasks, saveTasks, loadQueue, saveQueue, loadAgents, loadSchedules, saveSchedules, loadSessions, saveSessions, loadSettings } from './storage'
5
6
  import { notify } from './ws-hub'
7
+ import { perf } from './perf'
6
8
  import { WORKSPACE_DIR } from './data-dir'
7
9
  import { createOrchestratorSession } from './orchestrator'
8
- import { formatValidationFailure, validateTaskCompletion } from './task-validation'
9
- import { ensureTaskCompletionReport } from './task-reports'
10
+ import { formatValidationFailure } from './task-validation'
10
11
  import { pushMainLoopEventToMainSessions } from './main-agent-loop'
11
12
  import { executeSessionChatTurn } from './chat-execution'
12
13
  import { extractTaskResult, formatResultBody } from './task-result'
14
+ import {
15
+ collectTaskConnectorFollowupTargets as collectTaskConnectorFollowupTargetsImpl,
16
+ extractLikelyOutputFiles,
17
+ isSendableAttachment,
18
+ maybeResolveUploadMediaPathFromUrl,
19
+ notifyConnectorTaskFollowups,
20
+ resolveExistingOutputFilePath,
21
+ resolveTaskOriginConnectorFollowupTarget as resolveTaskOriginConnectorFollowupTargetImpl,
22
+ type ScheduleTaskMeta,
23
+ type SessionLike,
24
+ type SessionMessageLike,
25
+ } from './task-followups'
13
26
  import { getCheckpointSaver } from './langgraph-checkpoint'
14
27
  import { cascadeUnblock } from './dag-validation'
15
28
  import { performGuardianRollback } from './guardian'
16
29
  import { shouldAutoDeleteScheduleAfterTerminalRun } from '@/lib/schedule-origin'
17
- import type { Agent, BoardTask, Connector, Message, Session } from '@/types'
30
+ import type { Agent, BoardTask, Message, Session } from '@/types'
18
31
  import { buildAgentDisabledMessage, isAgentDisabled } from './agent-availability'
32
+ import {
33
+ didTaskValidationChange,
34
+ markInvalidCompletedTaskFailed,
35
+ markValidatedTaskCompleted,
36
+ refreshTaskCompletionValidation,
37
+ } from './task-lifecycle'
19
38
 
20
- // HMR-safe: pin processing flag to globalThis so hot reloads don't reset it
21
- const _queueState = ((globalThis as Record<string, unknown>).__swarmclaw_queue__ ??= { processing: false, pendingKick: false }) as { processing: boolean; pendingKick: boolean }
22
-
23
- interface SessionMessageLike {
24
- role?: string
25
- text?: string
26
- time?: number
27
- kind?: string
28
- historyExcluded?: boolean
29
- source?: {
30
- connectorId?: string
31
- channelId?: string
32
- threadId?: string
33
- }
34
- toolEvents?: Array<{ name?: string; output?: string }>
35
- streaming?: boolean
36
- imageUrl?: string
37
- }
39
+ export const collectTaskConnectorFollowupTargets = collectTaskConnectorFollowupTargetsImpl
40
+ export const resolveTaskOriginConnectorFollowupTarget = resolveTaskOriginConnectorFollowupTargetImpl
38
41
 
39
- interface SessionLike {
40
- name?: string
41
- user?: string
42
- cwd?: string
43
- messages?: SessionMessageLike[]
44
- connectorContext?: {
45
- connectorId?: string | null
46
- channelId?: string | null
47
- threadId?: string | null
48
- senderId?: string | null
49
- senderName?: string | null
50
- }
51
- lastActiveAt?: number
52
- heartbeatEnabled?: boolean | null
53
- active?: boolean
54
- currentRunId?: string | null
55
- }
56
-
57
- interface ScheduleTaskMeta extends BoardTask {
58
- user?: string | null
59
- createdInSessionId?: string | null
60
- createdByAgentId?: string | null
61
- }
62
-
63
- interface RunningConnectorLike {
64
- id: string
65
- platform: string
66
- agentId: string | null
67
- supportsSend: boolean
68
- configuredTargets: string[]
69
- recentChannelId: string | null
70
- }
71
-
72
- interface ConnectorTaskFollowupTarget {
73
- connectorId: string
74
- channelId: string
75
- threadId?: string | null
76
- }
42
+ // HMR-safe: pin processing flag to globalThis so hot reloads don't reset it
43
+ const _queueState = hmrSingleton('__swarmclaw_queue__', () => ({ processing: false, pendingKick: false }))
77
44
 
78
45
  const DISABLED_AGENT_RETRY_MS = 60_000
79
46
 
80
- function sameReasons(a?: string[] | null, b?: string[] | null): boolean {
81
- const av = Array.isArray(a) ? a : []
82
- const bv = Array.isArray(b) ? b : []
83
- if (av.length !== bv.length) return false
84
- for (let i = 0; i < av.length; i++) {
85
- if (av[i] !== bv[i]) return false
86
- }
87
- return true
88
- }
89
-
90
47
  function normalizeInt(value: unknown, fallback: number, min: number, max: number): number {
91
48
  const parsed = typeof value === 'number'
92
49
  ? value
@@ -110,7 +67,7 @@ function deriveTaskRoutePreferences(task: BoardTask): {
110
67
  preferredGatewayUseCase?: string | null
111
68
  } {
112
69
  const tags = Array.isArray(task.tags)
113
- ? [...new Set(task.tags.map((tag) => (typeof tag === 'string' ? tag.trim().toLowerCase() : '')).filter(Boolean))]
70
+ ? dedup(task.tags.map((tag) => (typeof tag === 'string' ? tag.trim().toLowerCase() : '')).filter(Boolean))
114
71
  : []
115
72
  const customUseCase = typeof task.customFields?.openclawUseCase === 'string'
116
73
  ? task.customFields.openclawUseCase
@@ -561,267 +518,6 @@ function latestAssistantText(session: SessionLike | null | undefined): string {
561
518
  return ''
562
519
  }
563
520
 
564
- function isEnabledFlag(value: unknown): boolean {
565
- if (typeof value === 'boolean') return value
566
- if (typeof value !== 'string') return false
567
- const normalized = value.trim().toLowerCase()
568
- return normalized === '1'
569
- || normalized === 'true'
570
- || normalized === 'yes'
571
- || normalized === 'on'
572
- || normalized === 'enabled'
573
- }
574
-
575
- function normalizeWhatsappTarget(raw: string): string {
576
- const trimmed = raw.trim()
577
- if (!trimmed) return trimmed
578
- if (trimmed.includes('@')) return trimmed
579
- let cleaned = trimmed.replace(/[^\d+]/g, '')
580
- if (cleaned.startsWith('+')) cleaned = cleaned.slice(1)
581
- if (cleaned.startsWith('0') && cleaned.length >= 10) {
582
- cleaned = `44${cleaned.slice(1)}`
583
- }
584
- cleaned = cleaned.replace(/[^\d]/g, '')
585
- return cleaned ? `${cleaned}@s.whatsapp.net` : trimmed
586
- }
587
-
588
- function fillTaskFollowupTemplate(template: string, data: {
589
- status: string
590
- title: string
591
- summary: string
592
- taskId: string
593
- }): string {
594
- return template
595
- .replaceAll('{status}', data.status)
596
- .replaceAll('{title}', data.title)
597
- .replaceAll('{summary}', data.summary)
598
- .replaceAll('{taskId}', data.taskId)
599
- }
600
-
601
- function maybeResolveUploadMediaPathFromUrl(url: string | undefined): string | undefined {
602
- if (!url || !url.startsWith('/api/uploads/')) return undefined
603
- const rawName = url.slice('/api/uploads/'.length).split(/[?#]/)[0] || ''
604
- let decoded: string
605
- try { decoded = decodeURIComponent(rawName) } catch { decoded = rawName }
606
- const safeName = decoded.replace(/[^a-zA-Z0-9._-]/g, '')
607
- if (!safeName) return undefined
608
- const fullPath = path.join(UPLOAD_DIR, safeName)
609
- return fs.existsSync(fullPath) ? fullPath : undefined
610
- }
611
-
612
- const OUTPUT_FILE_BACKTICK_RE = /`([^`\n]+\.(?:txt|md|json|csv|pdf|png|jpe?g|webp|gif|svg|mp4|webm|mov|zip|tar|gz|log|yml|yaml|xml|html|css|js|ts|tsx|jsx|py|go|rs|java|swift|kt|sql))`/gi
613
- const OUTPUT_FILE_PATH_RE = /\b((?:\.{1,2}\/|~\/|\/)?[\w./-]+\.(?:txt|md|json|csv|pdf|png|jpe?g|webp|gif|svg|mp4|webm|mov|zip|tar|gz|log|yml|yaml|xml|html|css|js|ts|tsx|jsx|py|go|rs|java|swift|kt|sql))\b/gi
614
- const MAX_CONNECTOR_ATTACHMENT_BYTES = 25 * 1024 * 1024
615
-
616
- function extractLikelyOutputFiles(text: string): string[] {
617
- const out: string[] = []
618
- const seen = new Set<string>()
619
- const push = (raw: string) => {
620
- const value = raw.trim().replace(/^['"]|['"]$/g, '')
621
- if (!value || /^https?:\/\//i.test(value)) return
622
- if (value.startsWith('/api/uploads/')) return
623
- const key = value.toLowerCase()
624
- if (seen.has(key)) return
625
- seen.add(key)
626
- out.push(value)
627
- }
628
-
629
- for (const match of text.matchAll(OUTPUT_FILE_BACKTICK_RE)) {
630
- push(match[1] || '')
631
- if (out.length >= 8) return out
632
- }
633
- for (const match of text.matchAll(OUTPUT_FILE_PATH_RE)) {
634
- push(match[1] || '')
635
- if (out.length >= 8) return out
636
- }
637
-
638
- return out
639
- }
640
-
641
- function resolveExistingOutputFilePath(fileRef: string, cwd: string): string | null {
642
- const ref = (fileRef || '').trim()
643
- if (!ref) return null
644
- if (ref.startsWith('/api/uploads/')) {
645
- return maybeResolveUploadMediaPathFromUrl(ref) || null
646
- }
647
- const withoutFileScheme = ref.replace(/^file:\/\//i, '')
648
- const candidates = path.isAbsolute(withoutFileScheme)
649
- ? [withoutFileScheme]
650
- : [
651
- cwd ? path.resolve(cwd, withoutFileScheme) : '',
652
- path.resolve(WORKSPACE_DIR, withoutFileScheme),
653
- ].filter(Boolean)
654
-
655
- for (const candidate of candidates) {
656
- try {
657
- const stat = fs.statSync(candidate)
658
- if (stat.isFile()) return candidate
659
- } catch {
660
- // ignore missing candidate
661
- }
662
- }
663
- return null
664
- }
665
-
666
- function isSendableAttachment(filePath: string): boolean {
667
- try {
668
- const stat = fs.statSync(filePath)
669
- return stat.isFile() && stat.size <= MAX_CONNECTOR_ATTACHMENT_BYTES
670
- } catch {
671
- return false
672
- }
673
- }
674
-
675
- export function resolveTaskOriginConnectorFollowupTarget(params: {
676
- task: BoardTask
677
- sessions: Record<string, SessionLike>
678
- connectors: Record<string, Connector>
679
- running: RunningConnectorLike[]
680
- }): ConnectorTaskFollowupTarget | null {
681
- const { task, sessions, connectors, running } = params
682
- const metaTask = task as ScheduleTaskMeta
683
- const delegatedByAgentId = typeof metaTask.delegatedByAgentId === 'string'
684
- ? metaTask.delegatedByAgentId.trim()
685
- : ''
686
- const allowedOwners = new Set([task.agentId, delegatedByAgentId].filter(Boolean))
687
- const sourceSessionId = typeof metaTask.createdInSessionId === 'string'
688
- ? metaTask.createdInSessionId.trim()
689
- : ''
690
-
691
- const runningById = new Map<string, RunningConnectorLike>()
692
- for (const entry of running) {
693
- if (!entry?.id) continue
694
- runningById.set(entry.id, entry)
695
- }
696
-
697
- const normalizeTarget = (raw: {
698
- connectorId?: string | null
699
- channelId?: string | null
700
- threadId?: string | null
701
- }): ConnectorTaskFollowupTarget | null => {
702
- const connectorId = typeof raw.connectorId === 'string' ? raw.connectorId.trim() : ''
703
- if (!connectorId) return null
704
- const connector = connectors[connectorId]
705
- if (!connector) return null
706
- const ownerId = typeof connector.agentId === 'string' ? connector.agentId.trim() : ''
707
- if (ownerId && !allowedOwners.has(ownerId)) return null
708
-
709
- const runtime = runningById.get(connectorId)
710
- if (runtime && !runtime.supportsSend) return null
711
-
712
- const channelId = typeof raw.channelId === 'string' ? raw.channelId.trim() : ''
713
- if (!channelId) return null
714
- const normalizedChannelId = connector.platform === 'whatsapp'
715
- ? normalizeWhatsappTarget(channelId)
716
- : channelId
717
- const threadId = typeof raw.threadId === 'string' ? raw.threadId.trim() : ''
718
- return {
719
- connectorId,
720
- channelId: normalizedChannelId,
721
- ...(threadId ? { threadId } : {}),
722
- }
723
- }
724
-
725
- const explicitTarget = normalizeTarget({
726
- connectorId: typeof metaTask.followupConnectorId === 'string' ? metaTask.followupConnectorId : null,
727
- channelId: typeof metaTask.followupChannelId === 'string' ? metaTask.followupChannelId : null,
728
- threadId: typeof metaTask.followupThreadId === 'string' ? metaTask.followupThreadId : null,
729
- })
730
- if (explicitTarget) return explicitTarget
731
-
732
- if (!sourceSessionId) return null
733
- const sourceSession = sessions[sourceSessionId]
734
- if (!sourceSession) return null
735
-
736
- const sessionContextTarget = normalizeTarget({
737
- connectorId: typeof sourceSession.connectorContext?.connectorId === 'string'
738
- ? sourceSession.connectorContext.connectorId
739
- : null,
740
- channelId: typeof sourceSession.connectorContext?.channelId === 'string'
741
- ? sourceSession.connectorContext.channelId
742
- : null,
743
- threadId: typeof sourceSession.connectorContext?.threadId === 'string'
744
- ? sourceSession.connectorContext.threadId
745
- : null,
746
- })
747
- if (sessionContextTarget) return sessionContextTarget
748
-
749
- if (!Array.isArray(sourceSession.messages)) return null
750
-
751
- for (let i = sourceSession.messages.length - 1; i >= 0; i--) {
752
- const message = sourceSession.messages[i]
753
- if (!message || message.role !== 'user') continue
754
- if (message.historyExcluded === true) continue
755
-
756
- const connectorId = typeof message.source?.connectorId === 'string'
757
- ? message.source.connectorId.trim()
758
- : ''
759
- if (!connectorId) continue
760
-
761
- const connector = connectors[connectorId]
762
- if (!connector) continue
763
- const runtime = runningById.get(connectorId)
764
- const sourceChannel = typeof message.source?.channelId === 'string'
765
- ? message.source.channelId.trim()
766
- : ''
767
- const fallbackChannel = runtime?.recentChannelId
768
- || runtime?.configuredTargets?.[0]
769
- || connector.config?.outboundJid
770
- || connector.config?.outboundTarget
771
- || ''
772
- const target = normalizeTarget({
773
- connectorId,
774
- channelId: sourceChannel || fallbackChannel,
775
- threadId: typeof message.source?.threadId === 'string' ? message.source.threadId : null,
776
- })
777
- if (target) return target
778
- }
779
-
780
- return null
781
- }
782
-
783
- export function collectTaskConnectorFollowupTargets(params: {
784
- task: BoardTask
785
- sessions: Record<string, SessionLike>
786
- connectors: Record<string, Connector>
787
- running: RunningConnectorLike[]
788
- }): ConnectorTaskFollowupTarget[] {
789
- const { task, sessions, connectors, running } = params
790
- const originTarget = resolveTaskOriginConnectorFollowupTarget({ task, sessions, connectors, running })
791
- if (originTarget) return [originTarget]
792
-
793
- const targets: ConnectorTaskFollowupTarget[] = []
794
- const seen = new Set<string>()
795
- const pushTarget = (target: ConnectorTaskFollowupTarget | null | undefined) => {
796
- if (!target?.connectorId || !target?.channelId) return
797
- const key = `${target.connectorId}|${target.channelId}|${target.threadId || ''}`
798
- if (seen.has(key)) return
799
- seen.add(key)
800
- targets.push(target)
801
- }
802
-
803
- for (const entry of running) {
804
- if (!entry.supportsSend || !entry.id) continue
805
- const connector = connectors[entry.id]
806
- if (!connector) continue
807
- if (connector.agentId !== task.agentId) continue
808
- if (!isEnabledFlag(connector.config?.taskFollowups)) continue
809
- const channelTargetRaw = entry.configuredTargets[0]
810
- || connector.config?.outboundJid
811
- || connector.config?.outboundTarget
812
- || ''
813
- if (!channelTargetRaw) continue
814
- pushTarget({
815
- connectorId: entry.id,
816
- channelId: connector.platform === 'whatsapp'
817
- ? normalizeWhatsappTarget(channelTargetRaw)
818
- : channelTargetRaw,
819
- })
820
- }
821
-
822
- return targets
823
- }
824
-
825
521
  // Task result extraction now uses Zod-validated structured data
826
522
  // from ./task-result.ts (extractTaskResult, formatResultBody)
827
523
 
@@ -906,7 +602,13 @@ export function reconcileFinishedRunningTasks(): { reconciled: number; deadLette
906
602
  if (!hasFinishedExecutionSession(session)) continue
907
603
 
908
604
  const fallbackText = latestAssistantText(session)
909
- if (!fallbackText && !task.result) continue
605
+ if (!fallbackText && !task.result) {
606
+ task.status = 'failed'
607
+ task.result = 'Agent session finished without producing output.'
608
+ task.updatedAt = now
609
+ tasksDirty = true
610
+ continue
611
+ }
910
612
 
911
613
  applyTaskPolicyDefaults(task)
912
614
  const taskResult = extractTaskResult(
@@ -919,18 +621,13 @@ export function reconcileFinishedRunningTasks(): { reconciled: number; deadLette
919
621
  task.artifacts = taskResult.artifacts.slice(0, 24)
920
622
  task.outputFiles = extractLikelyOutputFiles(enrichedResult).slice(0, 24)
921
623
  task.updatedAt = now
922
- const report = ensureTaskCompletionReport(task)
923
- if (report?.relativePath) task.completionReportPath = report.relativePath
924
- const validation = validateTaskCompletion(task, { report, settings })
925
- task.validation = validation
624
+ const { validation } = refreshTaskCompletionValidation(task, settings)
926
625
  if (!task.comments) task.comments = []
927
626
 
928
627
  if (validation.ok) {
929
- task.status = 'completed'
930
- task.completedAt = now
628
+ markValidatedTaskCompleted(task, { now })
931
629
  task.retryScheduledAt = null
932
630
  task.deadLetteredAt = null
933
- task.error = null
934
631
  task.checkpoint = {
935
632
  ...(task.checkpoint || {}),
936
633
  lastRunId: sessionId,
@@ -1057,7 +754,7 @@ function notifyMainChatScheduleResult(task: BoardTask): void {
1057
754
  const agents = loadAgents()
1058
755
  const agent = agents[task.agentId]
1059
756
  if (agent?.threadSessionId && sessions[agent.threadSessionId]) {
1060
- const thread = sessions[agent.threadSessionId] as SessionLike
757
+ const thread = sessions[(agent as any).threadSessionId] as SessionLike
1061
758
  const threadLast = Array.isArray(thread.messages) ? thread.messages.at(-1) : null
1062
759
  if (!(threadLast?.role === 'assistant' && threadLast?.text === body && typeof threadLast?.time === 'number' && now - threadLast.time < 30_000)) {
1063
760
  if (!Array.isArray(thread.messages)) thread.messages = []
@@ -1080,91 +777,13 @@ function cleanupTerminalOneOffSchedule(task: BoardTask): void {
1080
777
 
1081
778
  const schedules = loadSchedules()
1082
779
  const schedule = schedules[scheduleId]
1083
- if (!shouldAutoDeleteScheduleAfterTerminalRun(schedule)) return
780
+ if (!shouldAutoDeleteScheduleAfterTerminalRun(schedule as any)) return
1084
781
 
1085
782
  delete schedules[scheduleId]
1086
783
  saveSchedules(schedules)
1087
784
  notify('schedules')
1088
785
  }
1089
786
 
1090
- async function notifyConnectorTaskFollowups(params: {
1091
- task: BoardTask
1092
- statusLabel: string
1093
- summaryText: string
1094
- imageUrl?: string
1095
- mediaPath?: string
1096
- mediaFileName?: string
1097
- }) {
1098
- const { task, statusLabel, summaryText, imageUrl, mediaPath, mediaFileName } = params
1099
-
1100
- const connectors = loadConnectors()
1101
- const running = (await import('./connectors/manager')).listRunningConnectors()
1102
- const manager = await import('./connectors/manager')
1103
- const sessions = loadSessions()
1104
- const targets = collectTaskConnectorFollowupTargets({
1105
- task,
1106
- sessions: sessions as Record<string, SessionLike>,
1107
- connectors,
1108
- running: running as RunningConnectorLike[],
1109
- })
1110
- if (!targets.length) return
1111
- const originTarget = resolveTaskOriginConnectorFollowupTarget({
1112
- task,
1113
- sessions: sessions as Record<string, SessionLike>,
1114
- connectors,
1115
- running: running as RunningConnectorLike[],
1116
- })
1117
- const preferredTargetKey = originTarget
1118
- ? `${originTarget.connectorId}|${originTarget.channelId}|${originTarget.threadId || ''}`
1119
- : ''
1120
-
1121
- const summary = summaryText.trim().slice(0, 1400)
1122
- for (const target of targets) {
1123
- const connector = connectors[target.connectorId]
1124
- if (!connector) continue
1125
-
1126
- const template = typeof connector.config?.taskFollowupTemplate === 'string'
1127
- ? connector.config.taskFollowupTemplate.trim()
1128
- : ''
1129
- const message = template
1130
- ? fillTaskFollowupTemplate(template, {
1131
- status: statusLabel,
1132
- title: task.title || task.id,
1133
- summary,
1134
- taskId: task.id,
1135
- })
1136
- : [
1137
- `Task ${statusLabel}: ${task.title}`,
1138
- summary || 'No summary provided.',
1139
- ].join('\n\n')
1140
- const targetKey = `${target.connectorId}|${target.channelId}|${target.threadId || ''}`
1141
- const preferredChannelNote = !template && preferredTargetKey && targetKey === preferredTargetKey
1142
- ? '\n\n(Update sent in the same channel that requested this task.)'
1143
- : ''
1144
- const outboundMessage = `${message}${preferredChannelNote}`
1145
-
1146
- const resolvedMediaPath = mediaPath || maybeResolveUploadMediaPathFromUrl(imageUrl)
1147
- try {
1148
- await manager.sendConnectorMessage({
1149
- connectorId: target.connectorId,
1150
- channelId: target.channelId,
1151
- threadId: target.threadId || undefined,
1152
- text: outboundMessage,
1153
- ...(resolvedMediaPath
1154
- ? {
1155
- mediaPath: resolvedMediaPath,
1156
- fileName: mediaFileName || path.basename(resolvedMediaPath),
1157
- caption: outboundMessage,
1158
- }
1159
- : {}),
1160
- })
1161
- } catch (err: unknown) {
1162
- const errMsg = err instanceof Error ? err.message : String(err)
1163
- console.warn(`[queue] Failed task follow-up send on connector ${target.connectorId}: ${errMsg}`)
1164
- }
1165
- }
1166
- }
1167
-
1168
787
  /**
1169
788
  * Notify agent thread sessions when a task completes or fails.
1170
789
  * - Always pushes to the executing agent's thread
@@ -1213,9 +832,9 @@ function notifyAgentThreadTaskResult(task: BoardTask): void {
1213
832
  // Get working directory from execution session
1214
833
  const execCwd = runSession?.cwd || ''
1215
834
  const existingOutputPaths = outputFileRefs
1216
- .map((fileRef) => resolveExistingOutputFilePath(fileRef, execCwd))
1217
- .filter((candidate): candidate is string => Boolean(candidate))
1218
- const firstLocalOutputPath = existingOutputPaths.find((candidate) => isSendableAttachment(candidate))
835
+ .map((fileRef: string) => resolveExistingOutputFilePath(fileRef, execCwd))
836
+ .filter((candidate: any): candidate is string => Boolean(candidate))
837
+ const firstLocalOutputPath = existingOutputPaths.find((candidate: string) => isSendableAttachment(candidate))
1219
838
  const followupMediaPath = firstArtifactMediaPath || firstLocalOutputPath || undefined
1220
839
 
1221
840
  const buildMsg = (text: string): Message => {
@@ -1228,7 +847,7 @@ function notifyAgentThreadTaskResult(task: BoardTask): void {
1228
847
  const parts = [prefix]
1229
848
  if (execCwd) parts.push(`Working directory: \`${execCwd}\``)
1230
849
  if (outputFileRefs.length > 0) {
1231
- parts.push(`Output files:\n${outputFileRefs.slice(0, 8).map((fileRef) => `- \`${fileRef}\``).join('\n')}`)
850
+ parts.push(`Output files:\n${outputFileRefs.slice(0, 8).map((fileRef: string) => `- \`${fileRef}\``).join('\n')}`)
1232
851
  }
1233
852
  if (task.completionReportPath) parts.push(`Task report: \`${task.completionReportPath}\``)
1234
853
  if (resumeLines.length > 0) parts.push(resumeLines.join(' | '))
@@ -1238,7 +857,7 @@ function notifyAgentThreadTaskResult(task: BoardTask): void {
1238
857
 
1239
858
  // 1. Push to executing agent's thread
1240
859
  if (agent?.threadSessionId && sessions[agent.threadSessionId]) {
1241
- const thread = sessions[agent.threadSessionId]
860
+ const thread = sessions[(agent as any).threadSessionId]
1242
861
  if (!Array.isArray(thread.messages)) thread.messages = []
1243
862
  const body = buildResultBlock(`Task ${statusLabel}: **${taskLink}**`)
1244
863
  thread.messages.push(buildMsg(body))
@@ -1356,42 +975,32 @@ export function validateCompletedTasksQueue() {
1356
975
  if (task.status !== 'completed') continue
1357
976
  checked++
1358
977
 
1359
- const report = ensureTaskCompletionReport(task)
1360
- if (report?.relativePath && task.completionReportPath !== report.relativePath) {
1361
- task.completionReportPath = report.relativePath
978
+ const previousValidation = task.validation || null
979
+ const previousReportPath = task.completionReportPath || null
980
+ const { validation } = refreshTaskCompletionValidation(task, settings)
981
+ if (task.completionReportPath !== previousReportPath) {
1362
982
  tasksDirty = true
1363
983
  }
1364
-
1365
- const validation = validateTaskCompletion(task, { report, settings })
1366
- const prevValidation = task.validation || null
1367
- const validationChanged = !prevValidation
1368
- || prevValidation.ok !== validation.ok
1369
- || !sameReasons(prevValidation.reasons, validation.reasons)
984
+ const validationChanged = didTaskValidationChange(previousValidation, validation)
1370
985
 
1371
986
  if (validationChanged) {
1372
- task.validation = validation
1373
987
  tasksDirty = true
1374
988
  }
1375
989
 
1376
990
  if (validation.ok) {
1377
991
  if (!task.completedAt) {
1378
- task.completedAt = now
1379
- task.updatedAt = now
992
+ markValidatedTaskCompleted(task, { now, preserveCompletedAt: true })
1380
993
  tasksDirty = true
1381
994
  }
1382
995
  continue
1383
996
  }
1384
997
 
1385
- task.status = 'failed'
1386
- task.completedAt = null
1387
- task.error = formatValidationFailure(validation.reasons).slice(0, 500)
1388
- task.updatedAt = now
1389
- if (!task.comments) task.comments = []
1390
- task.comments.push({
1391
- id: genId(),
1392
- author: 'System',
1393
- text: `Task auto-failed completed-queue validation.\n\n${validation.reasons.map((r) => `- ${r}`).join('\n')}`,
1394
- createdAt: now,
998
+ markInvalidCompletedTaskFailed(task, validation, {
999
+ now,
1000
+ comment: {
1001
+ author: 'System',
1002
+ text: `Task auto-failed completed-queue validation.\n\n${validation.reasons.map((r) => `- ${r}`).join('\n')}`,
1003
+ },
1395
1004
  })
1396
1005
  tasksDirty = true
1397
1006
  demoted++
@@ -1502,6 +1111,7 @@ export function dequeueNextRunnableTask(queue: string[], tasks: Record<string, B
1502
1111
  export async function processNext() {
1503
1112
  if (_queueState.processing) return
1504
1113
  _queueState.processing = true
1114
+ const endQueuePerf = perf.start('queue', 'processNext')
1505
1115
 
1506
1116
  try {
1507
1117
  // Recover orphaned tasks: status is 'queued' but missing from the queue array
@@ -1722,7 +1332,9 @@ export async function processNext() {
1722
1332
  console.log(`[queue] Running task "${task.title}" (${taskId}) with ${agent.name}`)
1723
1333
 
1724
1334
  try {
1335
+ const endTaskRunPerf = perf.start('queue', 'executeTaskRun', { taskId, agentName: agent.name })
1725
1336
  const result = await executeTaskRun(task, agent, sessionId)
1337
+ endTaskRunPerf()
1726
1338
  const t2 = loadTasks()
1727
1339
  const settings = loadSettings()
1728
1340
  if (t2[taskId]) {
@@ -1739,20 +1351,15 @@ export async function processNext() {
1739
1351
  t2[taskId].artifacts = taskResult.artifacts.slice(0, 24)
1740
1352
  t2[taskId].outputFiles = extractLikelyOutputFiles(enrichedResult).slice(0, 24)
1741
1353
  t2[taskId].updatedAt = Date.now()
1742
- const report = ensureTaskCompletionReport(t2[taskId])
1743
- if (report?.relativePath) t2[taskId].completionReportPath = report.relativePath
1744
- const validation = validateTaskCompletion(t2[taskId], { report, settings })
1745
- t2[taskId].validation = validation
1354
+ const { validation } = refreshTaskCompletionValidation(t2[taskId], settings)
1746
1355
 
1747
1356
  const now = Date.now()
1748
1357
  // Add a completion/failure comment from the orchestrator.
1749
1358
  if (!t2[taskId].comments) t2[taskId].comments = []
1750
1359
 
1751
1360
  if (validation.ok) {
1752
- t2[taskId].status = 'completed'
1753
- t2[taskId].completedAt = now
1361
+ markValidatedTaskCompleted(t2[taskId], { now })
1754
1362
  t2[taskId].retryScheduledAt = null
1755
- t2[taskId].error = null
1756
1363
  t2[taskId].checkpoint = {
1757
1364
  ...(t2[taskId].checkpoint || {}),
1758
1365
  lastRunId: sessionId,
@@ -1918,6 +1525,7 @@ export async function processNext() {
1918
1525
  }
1919
1526
  }
1920
1527
  } finally {
1528
+ endQueuePerf()
1921
1529
  _queueState.processing = false
1922
1530
  // If tasks were enqueued while we were processing, kick another round
1923
1531
  if (_queueState.pendingKick) {
@@ -1994,7 +1602,6 @@ export function recoverStalledRunningTasks(): { recovered: number; deadLettered:
1994
1602
  disableSessionHeartbeat(task.sessionId)
1995
1603
  changed = true
1996
1604
  if (state === 'retry') {
1997
- task.retryScheduledAt = Date.now() + 30_000
1998
1605
  pushQueueUnique(queue, task.id)
1999
1606
  recovered++
2000
1607
  pushMainLoopEventToMainSessions({