@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,4 +1,4 @@
1
- import { loadQueue, loadSchedules, loadSessions, saveSessions, loadConnectors, saveConnectors, loadWebhookRetryQueue, upsertWebhookRetry, deleteWebhookRetry, loadWebhooks, loadAgents, loadSettings, appendWebhookLog, loadCredentials, decryptKey } from './storage'
1
+ import { loadQueue, loadSchedules, loadSessions, loadConnectors, saveConnectors, loadWebhookRetryQueue, upsertWebhookRetry, deleteWebhookRetry, loadWebhooks, loadAgents, loadSettings, appendWebhookLog, loadCredentials, decryptKey } from './storage'
2
2
  import { notify } from './ws-hub'
3
3
  import { processNext, cleanupFinishedTaskSessions, validateCompletedTasksQueue, recoverStalledRunningTasks, resumeQueue } from './queue'
4
4
  import { startScheduler, stopScheduler } from './scheduler'
@@ -18,24 +18,37 @@ import {
18
18
  getReconnectState,
19
19
  setReconnectState,
20
20
  } from './connectors/manager'
21
- import { startHeartbeatService, stopHeartbeatService, getHeartbeatServiceStatus } from './heartbeat-service'
21
+ import { startConnectorOutboxWorker, stopConnectorOutboxWorker } from './connectors/outbox'
22
+ import { startHeartbeatService, stopHeartbeatService, getHeartbeatServiceStatus, pruneHeartbeatState } from './heartbeat-service'
22
23
  import { hasOpenClawAgents, ensureGatewayConnected, disconnectGateway, getGateway } from './openclaw-gateway'
23
24
  import { enqueueSessionRun } from './session-run-manager'
24
25
  import { WORKSPACE_DIR } from './data-dir'
25
26
  import { DEFAULT_HEARTBEAT_INTERVAL_SEC } from '@/lib/heartbeat-defaults'
26
27
  import { genId } from '@/lib/id'
27
- import { isProductionRuntime } from '@/lib/runtime-env'
28
+ import { errorMessage, hmrSingleton } from '@/lib/shared-utils'
28
29
  import path from 'node:path'
29
30
  import type { Session, WebhookRetryEntry } from '@/types'
30
31
  import { createNotification } from '@/lib/server/create-notification'
31
32
  import { pingProvider, OPENAI_COMPATIBLE_DEFAULTS } from '@/lib/server/provider-health'
32
33
  import { runIntegrityMonitor } from '@/lib/server/integrity-monitor'
33
34
  import { recoverStaleDelegationJobs } from './delegation-jobs'
35
+ import { pruneMainLoopState } from './main-agent-loop'
36
+ import { sweepManagedProcesses } from './process-manager'
34
37
  import {
35
38
  listPendingApprovalsNeedingConnectorNotification,
36
39
  markApprovalConnectorNotificationAttempt,
37
40
  markApprovalConnectorNotificationSent,
38
41
  } from './approvals'
42
+ import {
43
+ buildSessionHeartbeatHealthDedupKey,
44
+ daemonAutostartEnvEnabled,
45
+ isDaemonBackgroundServicesEnabled,
46
+ parseCronToMs,
47
+ parseHeartbeatIntervalSec,
48
+ shouldNotifyProviderReachabilityIssue,
49
+ shouldSuppressSessionHeartbeatHealthAlert,
50
+ shouldSuppressSyntheticAgentHealthAlert,
51
+ } from './daemon-policy'
39
52
 
40
53
  const QUEUE_CHECK_INTERVAL = 30_000 // 30 seconds
41
54
  const BROWSER_SWEEP_INTERVAL = 60_000 // 60 seconds
@@ -52,74 +65,16 @@ const CONNECTOR_RESTART_BASE_MS = 30_000
52
65
  const CONNECTOR_RESTART_MAX_MS = 15 * 60 * 1000
53
66
  const MAX_WAKE_ATTEMPTS = 3
54
67
 
55
- function parseBoolish(value: unknown, fallback: boolean): boolean {
56
- if (typeof value === 'boolean') return value
57
- if (typeof value !== 'string') return fallback
58
- const normalized = value.trim().toLowerCase()
59
- if (!normalized) return fallback
60
- if (['1', 'true', 'yes', 'on'].includes(normalized)) return true
61
- if (['0', 'false', 'no', 'off'].includes(normalized)) return false
62
- return fallback
63
- }
64
-
65
- function daemonAutostartEnvEnabled(): boolean {
66
- return parseBoolish(process.env.SWARMCLAW_DAEMON_AUTOSTART, isProductionRuntime())
67
- }
68
-
69
- export function isDaemonBackgroundServicesEnabled(): boolean {
70
- return parseBoolish(process.env.SWARMCLAW_DAEMON_BACKGROUND_SERVICES, true)
71
- }
72
-
73
- function parseHeartbeatIntervalSec(value: unknown, fallback = DEFAULT_HEARTBEAT_INTERVAL_SEC): number {
74
- const parsed = typeof value === 'number'
75
- ? value
76
- : typeof value === 'string'
77
- ? Number.parseInt(value, 10)
78
- : Number.NaN
79
- if (!Number.isFinite(parsed)) return fallback
80
- return Math.max(0, Math.min(3600, Math.trunc(parsed)))
81
- }
82
-
83
- export function shouldNotifyProviderReachabilityIssue(provider: string): boolean {
84
- return provider !== 'openclaw'
85
- }
86
-
87
- const SYNTHETIC_HEALTH_SESSION_USERS = new Set(['workbench', 'comparison-bench'])
88
- const SYNTHETIC_HEALTH_SESSION_PREFIXES = ['wb-', 'cmp-']
89
-
90
- function hasSyntheticHealthPrefix(value: unknown): boolean {
91
- const normalized = typeof value === 'string' ? value.trim().toLowerCase() : ''
92
- return SYNTHETIC_HEALTH_SESSION_PREFIXES.some((prefix) => normalized.startsWith(prefix))
93
- }
94
-
95
- export function shouldSuppressSessionHeartbeatHealthAlert(
96
- session: Pick<Session, 'id' | 'name' | 'user' | 'shortcutForAgentId'>,
97
- ): boolean {
98
- const user = typeof session.user === 'string' ? session.user.trim().toLowerCase() : ''
99
- if (SYNTHETIC_HEALTH_SESSION_USERS.has(user)) return true
100
- if (hasSyntheticHealthPrefix(session.id)) return true
101
- if (hasSyntheticHealthPrefix(session.shortcutForAgentId)) return true
102
-
103
- const name = typeof session.name === 'string' ? session.name.trim().toLowerCase() : ''
104
- return name.startsWith('workbench ')
105
- || name.startsWith('assistant benchmark ')
106
- || name.startsWith('comparison ')
107
- }
108
-
109
- export function shouldSuppressSyntheticAgentHealthAlert(agentId: string): boolean {
110
- return hasSyntheticHealthPrefix(agentId)
111
- }
112
-
113
- export function buildSessionHeartbeatHealthDedupKey(
114
- sessionId: string,
115
- state: 'stale' | 'auto-disabled',
116
- ): string {
117
- return `health-alert:session-heartbeat:${state}:${sessionId}`
68
+ export {
69
+ buildSessionHeartbeatHealthDedupKey,
70
+ isDaemonBackgroundServicesEnabled,
71
+ shouldNotifyProviderReachabilityIssue,
72
+ shouldSuppressSessionHeartbeatHealthAlert,
73
+ shouldSuppressSyntheticAgentHealthAlert,
118
74
  }
119
75
 
120
76
  // Store daemon state on globalThis to survive HMR reloads
121
- const gk = '__swarmclaw_daemon__' as const
122
- const ds: {
77
+ interface DaemonState {
123
78
  queueIntervalId: ReturnType<typeof setInterval> | null
124
79
  browserSweepId: ReturnType<typeof setInterval> | null
125
80
  healthIntervalId: ReturnType<typeof setInterval> | null
@@ -138,8 +93,9 @@ const ds: {
138
93
  manualStopRequested: boolean
139
94
  running: boolean
140
95
  lastProcessedAt: number | null
141
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
142
- } = (globalThis as any)[gk] ?? ((globalThis as any)[gk] = {
96
+ }
97
+
98
+ const ds: DaemonState = hmrSingleton<DaemonState>('__swarmclaw_daemon__', () => ({
143
99
  queueIntervalId: null,
144
100
  browserSweepId: null,
145
101
  healthIntervalId: null,
@@ -155,7 +111,7 @@ const ds: {
155
111
  manualStopRequested: false,
156
112
  running: false,
157
113
  lastProcessedAt: null,
158
- })
114
+ }))
159
115
 
160
116
  // Backfill fields for hot-reloaded daemon state objects from older code versions.
161
117
  if (!ds.staleSessionIds) ds.staleSessionIds = new Set<string>()
@@ -193,7 +149,7 @@ export function startDaemon(options?: { source?: string; manualStart?: boolean }
193
149
  startBrowserSweep()
194
150
  startHeartbeatService()
195
151
  startMemoryConsolidation()
196
- syncDaemonBackgroundServices()
152
+ syncDaemonBackgroundServices({ runConnectorHealthCheckImmediately: false })
197
153
  return
198
154
  }
199
155
  ds.running = true
@@ -210,18 +166,18 @@ export function startDaemon(options?: { source?: string; manualStart?: boolean }
210
166
  startBrowserSweep()
211
167
  startHeartbeatService()
212
168
  startMemoryConsolidation()
213
- syncDaemonBackgroundServices()
169
+ syncDaemonBackgroundServices({ runConnectorHealthCheckImmediately: false })
214
170
  } catch (err: unknown) {
215
171
  ds.running = false
216
172
  notify('daemon')
217
- console.error('[daemon] Failed to start:', err instanceof Error ? err.message : String(err))
173
+ console.error('[daemon] Failed to start:', errorMessage(err))
218
174
  throw err
219
175
  }
220
176
 
221
177
  if (isDaemonBackgroundServicesEnabled()) {
222
178
  // Auto-start enabled connectors only when the full background stack is enabled.
223
179
  autoStartConnectors().catch((err: unknown) => {
224
- console.error('[daemon] Error auto-starting connectors:', err instanceof Error ? err.message : String(err))
180
+ console.error('[daemon] Error auto-starting connectors:', errorMessage(err))
225
181
  })
226
182
  }
227
183
  }
@@ -239,10 +195,11 @@ export function stopDaemon(options?: { source?: string; manualStop?: boolean })
239
195
  stopBrowserSweep()
240
196
  stopHealthMonitor()
241
197
  stopConnectorHealthMonitor()
198
+ stopConnectorOutboxWorker()
242
199
  stopHeartbeatService()
243
200
  stopMemoryConsolidation()
244
201
  stopEvalScheduler()
245
- stopAllConnectors().catch(() => {})
202
+ stopAllConnectors({ disable: false }).catch(() => {})
246
203
  }
247
204
 
248
205
  function startBrowserSweep() {
@@ -270,6 +227,7 @@ function stopBrowserSweep() {
270
227
  function startQueueProcessor() {
271
228
  if (ds.queueIntervalId) return
272
229
  ds.queueIntervalId = setInterval(async () => {
230
+ if (!ds.running) return
273
231
  const queue = loadQueue()
274
232
  if (queue.length > 0) {
275
233
  console.log(`[daemon] Processing ${queue.length} queued task(s)`)
@@ -323,7 +281,7 @@ async function runConnectorHealthChecks(now: number) {
323
281
  try {
324
282
  await checkConnectorHealth()
325
283
  } catch (err: unknown) {
326
- console.error('[health] Connector isAlive check failed:', err instanceof Error ? err.message : String(err))
284
+ console.error('[health] Connector isAlive check failed:', errorMessage(err))
327
285
  }
328
286
 
329
287
  const connectors = loadConnectors()
@@ -369,7 +327,7 @@ async function runConnectorHealthChecks(now: number) {
369
327
  clearReconnectState(connector.id)
370
328
  await sendHealthAlert(`Connector "${connector.name}" (${connector.platform}) was down and has been auto-restarted.`)
371
329
  } catch (err: unknown) {
372
- const message = err instanceof Error ? err.message : String(err)
330
+ const message = errorMessage(err)
373
331
  const next = advanceConnectorReconnectState(current, message, now, {
374
332
  initialBackoffMs: CONNECTOR_RESTART_BASE_MS,
375
333
  maxBackoffMs: CONNECTOR_RESTART_MAX_MS,
@@ -474,9 +432,8 @@ async function processWebhookRetries() {
474
432
  heartbeatEnabled: (agent.heartbeatEnabled as boolean | undefined) ?? false,
475
433
  heartbeatIntervalSec: (agent.heartbeatIntervalSec as number | null | undefined) ?? null,
476
434
  }
477
- sessions[session.id as string] = session
478
- const { saveSessions: save } = await import('./storage')
479
- save(sessions)
435
+ const { upsertSession: upsert } = await import('./storage')
436
+ upsert(session.id as string, session)
480
437
  }
481
438
 
482
439
  const payloadPreview = (entry.payload || '').slice(0, 12_000)
@@ -518,7 +475,7 @@ async function processWebhookRetries() {
518
475
  deleteWebhookRetry(entry.id)
519
476
  console.log(`[webhook-retry] Successfully retried ${entry.id} for webhook ${entry.webhookId} (attempt ${entry.attempts})`)
520
477
  } catch (err: unknown) {
521
- const errorMsg = err instanceof Error ? err.message : String(err)
478
+ const errorMsg = errorMessage(err)
522
479
  entry.attempts += 1
523
480
 
524
481
  if (entry.attempts >= entry.maxAttempts) {
@@ -699,7 +656,7 @@ async function runOpenClawGatewayHealthChecks() {
699
656
  const { runOpenClawDoctor } = await import('./openclaw-doctor')
700
657
  await runOpenClawDoctor({ fix: true })
701
658
  } catch (err: unknown) {
702
- console.warn('[daemon] openclaw doctor --fix failed:', err instanceof Error ? err.message : String(err))
659
+ console.warn('[daemon] openclaw doctor --fix failed:', errorMessage(err))
703
660
  }
704
661
  repair.attempts += 1
705
662
  repair.lastAttemptAt = now
@@ -750,7 +707,7 @@ async function runPendingApprovalConnectorNotifications(now: number) {
750
707
  entityId: reminder.approvalId,
751
708
  })
752
709
  } catch (err: unknown) {
753
- const errorMsg = err instanceof Error ? err.message : String(err)
710
+ const errorMsg = errorMessage(err)
754
711
  markApprovalConnectorNotificationAttempt(reminder.approvalId, {
755
712
  at: now,
756
713
  connectorId: reminder.connectorId,
@@ -763,6 +720,33 @@ async function runPendingApprovalConnectorNotifications(now: number) {
763
720
  }
764
721
  }
765
722
 
723
+ /**
724
+ * Prune orphaned entries from module-level Maps/Sets that reference
725
+ * sessions, connectors, or agents that no longer exist in storage.
726
+ * Runs every health-check cycle (2 minutes).
727
+ */
728
+ function pruneOrphanedState(sessions: Record<string, unknown>): void {
729
+ const liveSessionIds = new Set(Object.keys(sessions))
730
+
731
+ // Main-loop state map (per-session autonomous state)
732
+ pruneMainLoopState(liveSessionIds)
733
+
734
+ // Heartbeat service tracking maps
735
+ pruneHeartbeatState(liveSessionIds)
736
+
737
+ // Process manager — sweep completed processes older than TTL
738
+ sweepManagedProcesses()
739
+
740
+ // Daemon-local: prune openclawRepairState for agents that no longer exist
741
+ const agents = loadAgents()
742
+ for (const agentId of ds.openclawRepairState.keys()) {
743
+ if (!agents[agentId]) ds.openclawRepairState.delete(agentId)
744
+ }
745
+ for (const agentId of ds.openclawDownAgentIds) {
746
+ if (!agents[agentId]) ds.openclawDownAgentIds.delete(agentId)
747
+ }
748
+ }
749
+
766
750
  async function runHealthChecks() {
767
751
  // Continuously keep the completed queue honest.
768
752
  validateCompletedTasksQueue()
@@ -774,7 +758,7 @@ async function runHealthChecks() {
774
758
  const sessions = loadSessions()
775
759
  const now = Date.now()
776
760
  const currentlyStale = new Set<string>()
777
- let sessionsDirty = false
761
+ const dirtySessionIds: string[] = []
778
762
 
779
763
  for (const session of Object.values(sessions) as Record<string, unknown>[]) {
780
764
  if (!session?.id || typeof session.id !== 'string') continue
@@ -799,7 +783,7 @@ async function runHealthChecks() {
799
783
  if (staleForMs > autoDisableAfter) {
800
784
  session.heartbeatEnabled = false
801
785
  session.lastActiveAt = now
802
- sessionsDirty = true
786
+ dirtySessionIds.push(sessionId)
803
787
  ds.staleSessionIds.delete(sessionId)
804
788
  await sendHealthAlert({
805
789
  text: `Auto-disabled heartbeat for stale session "${sessionLabel}" after ${Math.round(staleForMs / 60_000)}m of inactivity.`,
@@ -831,26 +815,32 @@ async function runHealthChecks() {
831
815
  }
832
816
  }
833
817
 
834
- if (sessionsDirty) saveSessions(sessions)
818
+ for (const sid of dirtySessionIds) {
819
+ const s = sessions[sid]
820
+ if (s) {
821
+ const { upsertSession: upsert } = await import('./storage')
822
+ upsert(sid, s)
823
+ }
824
+ }
835
825
 
836
826
  // Provider reachability checks
837
827
  try {
838
828
  await runProviderHealthChecks()
839
829
  } catch (err: unknown) {
840
- console.error('[daemon] Provider health check failed:', err instanceof Error ? err.message : String(err))
830
+ console.error('[daemon] Provider health check failed:', errorMessage(err))
841
831
  }
842
832
 
843
833
  // OpenClaw gateway health checks + auto-repair
844
834
  try {
845
835
  await runOpenClawGatewayHealthChecks()
846
836
  } catch (err: unknown) {
847
- console.error('[daemon] OpenClaw gateway health check failed:', err instanceof Error ? err.message : String(err))
837
+ console.error('[daemon] OpenClaw gateway health check failed:', errorMessage(err))
848
838
  }
849
839
 
850
840
  try {
851
841
  await runPendingApprovalConnectorNotifications(now)
852
842
  } catch (err: unknown) {
853
- console.error('[daemon] Approval connector reminder check failed:', err instanceof Error ? err.message : String(err))
843
+ console.error('[daemon] Approval connector reminder check failed:', errorMessage(err))
854
844
  }
855
845
 
856
846
  // Integrity drift monitoring for identity/config/plugin files.
@@ -879,14 +869,21 @@ async function runHealthChecks() {
879
869
  await sendHealthAlert(`Integrity monitor detected ${integrity.drifts.length} file drift event(s).`)
880
870
  }
881
871
  } catch (err: unknown) {
882
- console.error('[daemon] Integrity monitor check failed:', err instanceof Error ? err.message : String(err))
872
+ console.error('[daemon] Integrity monitor check failed:', errorMessage(err))
883
873
  }
884
874
 
885
875
  // Process webhook retry queue
886
876
  try {
887
877
  await processWebhookRetries()
888
878
  } catch (err: unknown) {
889
- console.error('[daemon] Webhook retry processing failed:', err instanceof Error ? err.message : String(err))
879
+ console.error('[daemon] Webhook retry processing failed:', errorMessage(err))
880
+ }
881
+
882
+ // Periodic memory hygiene: prune orphaned state for deleted sessions/connectors
883
+ try {
884
+ pruneOrphanedState(sessions)
885
+ } catch (err: unknown) {
886
+ console.error('[daemon] Memory hygiene sweep failed:', errorMessage(err))
890
887
  }
891
888
  }
892
889
 
@@ -906,28 +903,32 @@ function stopHealthMonitor() {
906
903
  }
907
904
  }
908
905
 
909
- function syncDaemonBackgroundServices() {
906
+ function syncDaemonBackgroundServices(options?: { runConnectorHealthCheckImmediately?: boolean }) {
910
907
  if (isDaemonBackgroundServicesEnabled()) {
911
908
  startHealthMonitor()
912
- startConnectorHealthMonitor()
909
+ startConnectorHealthMonitor({
910
+ runImmediately: options?.runConnectorHealthCheckImmediately !== false,
911
+ })
912
+ startConnectorOutboxWorker()
913
913
  startEvalScheduler()
914
914
  return
915
915
  }
916
916
  stopHealthMonitor()
917
917
  stopConnectorHealthMonitor()
918
+ stopConnectorOutboxWorker()
918
919
  stopEvalScheduler()
919
920
  }
920
921
 
921
- function startConnectorHealthMonitor() {
922
+ function startConnectorHealthMonitor(options?: { runImmediately?: boolean }) {
922
923
  if (ds.connectorHealthIntervalId) return
923
924
 
924
925
  const tick = () => {
925
926
  runConnectorHealthChecks(Date.now()).catch((err) => {
926
- console.error('[daemon] Connector health tick failed:', err instanceof Error ? err.message : String(err))
927
+ console.error('[daemon] Connector health tick failed:', errorMessage(err))
927
928
  })
928
929
  }
929
930
 
930
- tick()
931
+ if (options?.runImmediately !== false) tick()
931
932
  ds.connectorHealthIntervalId = setInterval(tick, CONNECTOR_HEALTH_CHECK_INTERVAL)
932
933
  }
933
934
 
@@ -949,7 +950,7 @@ function runConsolidationTick() {
949
950
  }
950
951
  }),
951
952
  ).catch((err: unknown) => {
952
- console.error('[daemon] Memory consolidation failed:', err instanceof Error ? err.message : String(err))
953
+ console.error('[daemon] Memory consolidation failed:', errorMessage(err))
953
954
  })
954
955
  }
955
956
 
@@ -978,14 +979,6 @@ function stopMemoryConsolidation() {
978
979
 
979
980
  const EVAL_DEFAULT_INTERVAL_MS = 24 * 3600_000 // 24 hours
980
981
 
981
- function parseCronToMs(cron: string | null | undefined): number | null {
982
- if (!cron || typeof cron !== 'string') return null
983
- // Simple heuristic: extract hours from common cron patterns like "0 */6 * * *"
984
- const hourMatch = cron.match(/\*\/(\d+)/)
985
- if (hourMatch) return parseInt(hourMatch[1], 10) * 3600_000
986
- return EVAL_DEFAULT_INTERVAL_MS
987
- }
988
-
989
982
  async function runEvalSchedulerTick() {
990
983
  try {
991
984
  const settings = loadSettings()
@@ -1009,11 +1002,11 @@ async function runEvalSchedulerTick() {
1009
1002
  type: result.percentage >= 60 ? 'info' : 'warning',
1010
1003
  })
1011
1004
  } catch (err: unknown) {
1012
- console.error(`[daemon:eval] Failed for agent ${agentId}:`, err instanceof Error ? err.message : String(err))
1005
+ console.error(`[daemon:eval] Failed for agent ${agentId}:`, errorMessage(err))
1013
1006
  }
1014
1007
  }
1015
1008
  } catch (err: unknown) {
1016
- console.error('[daemon:eval] Scheduler tick error:', err instanceof Error ? err.message : String(err))
1009
+ console.error('[daemon:eval] Scheduler tick error:', errorMessage(err))
1017
1010
  }
1018
1011
  }
1019
1012
 
@@ -1022,7 +1015,7 @@ function startEvalScheduler() {
1022
1015
  try {
1023
1016
  const settings = loadSettings()
1024
1017
  if (!settings.autonomyEvalEnabled) return
1025
- const intervalMs = parseCronToMs(settings.autonomyEvalCron) || EVAL_DEFAULT_INTERVAL_MS
1018
+ const intervalMs = parseCronToMs(settings.autonomyEvalCron, EVAL_DEFAULT_INTERVAL_MS) || EVAL_DEFAULT_INTERVAL_MS
1026
1019
  ds.evalSchedulerIntervalId = setInterval(runEvalSchedulerTick, intervalMs)
1027
1020
  console.log(`[daemon:eval] Eval scheduler started (interval=${Math.round(intervalMs / 3600_000)}h)`)
1028
1021
  } catch {
@@ -1086,6 +1079,10 @@ export async function runDaemonHealthCheckNow() {
1086
1079
  ])
1087
1080
  }
1088
1081
 
1082
+ export async function runConnectorHealthCheckNowForTest(now = Date.now()) {
1083
+ await runConnectorHealthChecks(now)
1084
+ }
1085
+
1089
1086
  export function getDaemonStatus() {
1090
1087
  const queue = loadQueue()
1091
1088
  const schedules = loadSchedules()
@@ -28,7 +28,7 @@ describe('data-dir resolution', () => {
28
28
 
29
29
  try {
30
30
  const result = spawnSync(process.execPath, ['--import', 'tsx', '--input-type=module', '--eval', `
31
- const modNs = await import('./src/lib/server/data-dir.ts')
31
+ const modNs = await import('./src/lib/server/data-dir')
32
32
  const mod = modNs.default || modNs['module.exports'] || modNs
33
33
  console.log(JSON.stringify({
34
34
  dataDir: mod.DATA_DIR,
@@ -60,12 +60,12 @@ describe('data-dir resolution', () => {
60
60
 
61
61
  try {
62
62
  const env = { ...process.env, HOME: fakeHome, npm_lifecycle_event: 'build:ci' }
63
- delete env.DATA_DIR
64
- delete env.WORKSPACE_DIR
65
- delete env.BROWSER_PROFILES_DIR
63
+ delete (env as any).DATA_DIR
64
+ delete (env as any).WORKSPACE_DIR
65
+ delete (env as any).BROWSER_PROFILES_DIR
66
66
 
67
67
  const result = spawnSync(process.execPath, ['--import', 'tsx', '--input-type=module', '--eval', `
68
- const modNs = await import('./src/lib/server/data-dir.ts')
68
+ const modNs = await import('./src/lib/server/data-dir')
69
69
  const mod = modNs.default || modNs['module.exports'] || modNs
70
70
  console.log(JSON.stringify({
71
71
  isBuildBootstrap: mod.IS_BUILD_BOOTSTRAP,
@@ -20,6 +20,10 @@ function resolveDataDir(): string {
20
20
  }
21
21
 
22
22
  export const DATA_DIR = resolveDataDir()
23
+ export const CONNECTORS_DATA_DIR = path.join(DATA_DIR, 'connectors')
24
+ export const OPENCLAW_DATA_DIR = path.join(DATA_DIR, 'openclaw')
25
+ export const MEMORY_IMAGES_DIR = path.join(DATA_DIR, 'memory-images')
26
+ export const APP_LOG_PATH = path.join(DATA_DIR, 'app.log')
23
27
 
24
28
  function supportsChildWrites(dir: string): boolean {
25
29
  try {
@@ -275,7 +275,7 @@ describe('delegation-jobs-advanced', () => {
275
275
  // Kept: file-6..file-9 (4) + all batch2 (10) + all batch3 (10) = 24
276
276
  const first = afterBatch3.artifacts[0]
277
277
  assert.equal(first.type, 'file')
278
- assert.equal(first.value, '/output/file-6.ts')
278
+ assert.equal(first.value, '/output/file-6')
279
279
 
280
280
  const last = afterBatch3.artifacts[23]
281
281
  assert.equal(last.type, 'image')
@@ -99,6 +99,93 @@ describe('delegation-jobs', () => {
99
99
  assert.match(String(latest?.error || ''), /interrupted/i)
100
100
  })
101
101
 
102
+ // ---------------------------------------------------------------------------
103
+ // Reliability fix #4: atomic updateDelegationJob preserves fields
104
+ // ---------------------------------------------------------------------------
105
+
106
+ it('atomic update preserves all original fields', () => {
107
+ const job = delegationJobs.createDelegationJob({
108
+ kind: 'subagent',
109
+ task: 'Atomic update test',
110
+ agentId: 'ag-atomic',
111
+ agentName: 'Atomic Agent',
112
+ parentSessionId: 'parent-atomic',
113
+ })
114
+
115
+ // Partial update should not lose unrelated fields
116
+ const updated = delegationJobs.updateDelegationJob(job.id, { status: 'running' })
117
+ assert.ok(updated)
118
+ assert.equal(updated!.status, 'running')
119
+ assert.equal(updated!.task, 'Atomic update test')
120
+ assert.equal(updated!.agentId, 'ag-atomic')
121
+ assert.equal(updated!.agentName, 'Atomic Agent')
122
+ assert.equal(updated!.parentSessionId, 'parent-atomic')
123
+ assert.equal(updated!.kind, 'subagent')
124
+ })
125
+
126
+ it('sequential updates preserve intermediate state', () => {
127
+ const job = delegationJobs.createDelegationJob({
128
+ kind: 'subagent',
129
+ task: 'Sequential updates',
130
+ })
131
+
132
+ delegationJobs.updateDelegationJob(job.id, { status: 'running', startedAt: Date.now() })
133
+ delegationJobs.updateDelegationJob(job.id, { result: 'partial result' })
134
+
135
+ const final = delegationJobs.getDelegationJob(job.id)
136
+ assert.ok(final)
137
+ assert.equal(final!.status, 'running')
138
+ assert.equal(final!.result, 'partial result')
139
+ assert.ok(final!.startedAt! > 0)
140
+ })
141
+
142
+ it('updateDelegationJob returns null for non-existent job', () => {
143
+ const result = delegationJobs.updateDelegationJob('nonexistent-abc', { status: 'running' })
144
+ assert.equal(result, null)
145
+ })
146
+
147
+ // ---------------------------------------------------------------------------
148
+ // Reliability fix (bonus): cancel ordering — state committed before handle delete
149
+ // ---------------------------------------------------------------------------
150
+
151
+ it('cancel records checkpoint and timestamp atomically', () => {
152
+ const job = delegationJobs.createDelegationJob({
153
+ kind: 'subagent',
154
+ task: 'Cancel ordering test',
155
+ })
156
+
157
+ const cancelled = delegationJobs.cancelDelegationJob(job.id)
158
+ assert.ok(cancelled)
159
+ assert.equal(cancelled!.status, 'cancelled')
160
+ assert.ok(cancelled!.completedAt! > 0)
161
+ const cps = cancelled!.checkpoints ?? []
162
+ const lastCp = cps[cps.length - 1]
163
+ assert.equal(lastCp?.status, 'cancelled')
164
+ assert.equal(lastCp?.note, 'Job cancelled')
165
+ })
166
+
167
+ it('cancel is idempotent — repeated cancel returns unchanged job', () => {
168
+ const job = delegationJobs.createDelegationJob({
169
+ kind: 'subagent',
170
+ task: 'Idempotent cancel',
171
+ })
172
+
173
+ const first = delegationJobs.cancelDelegationJob(job.id)!
174
+ const second = delegationJobs.cancelDelegationJob(job.id)!
175
+ assert.equal(second.status, 'cancelled')
176
+ assert.equal((second.checkpoints ?? []).length, (first.checkpoints ?? []).length)
177
+ })
178
+
179
+ it('does not cancel completed jobs', () => {
180
+ const job = delegationJobs.createDelegationJob({
181
+ kind: 'subagent',
182
+ task: 'Completed job cancel',
183
+ })
184
+ delegationJobs.completeDelegationJob(job.id, 'All done')
185
+ const afterCancel = delegationJobs.cancelDelegationJob(job.id)
186
+ assert.equal(afterCancel!.status, 'completed')
187
+ })
188
+
102
189
  it('cancels all running jobs for a parent session', () => {
103
190
  const jobA = delegationJobs.createDelegationJob({
104
191
  kind: 'delegate',