@swarmclawai/swarmclaw 0.8.4 → 0.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (394) hide show
  1. package/README.md +9 -9
  2. package/bin/swarmclaw.js +5 -1
  3. package/bin/worker-cmd.js +73 -0
  4. package/package.json +2 -1
  5. package/src/app/api/agents/[id]/route.ts +17 -7
  6. package/src/app/api/agents/route.ts +21 -8
  7. package/src/app/api/approvals/route.test.ts +6 -6
  8. package/src/app/api/approvals/route.ts +2 -1
  9. package/src/app/api/auth/route.ts +2 -3
  10. package/src/app/api/chatrooms/[id]/chat/route.test.ts +299 -0
  11. package/src/app/api/chatrooms/[id]/chat/route.ts +3 -2
  12. package/src/app/api/chatrooms/[id]/route.ts +7 -6
  13. package/src/app/api/chats/[id]/chat/route.test.ts +496 -0
  14. package/src/app/api/chats/[id]/chat/route.ts +7 -3
  15. package/src/app/api/chats/[id]/clear/route.ts +9 -9
  16. package/src/app/api/chats/[id]/devserver/route.ts +2 -1
  17. package/src/app/api/chats/[id]/edit-resend/route.ts +3 -4
  18. package/src/app/api/chats/[id]/fork/route.ts +3 -5
  19. package/src/app/api/chats/[id]/restore/route.ts +6 -7
  20. package/src/app/api/chats/[id]/retry/route.ts +3 -4
  21. package/src/app/api/chats/[id]/route.ts +61 -62
  22. package/src/app/api/chats/route.ts +7 -1
  23. package/src/app/api/connectors/[id]/route.ts +7 -8
  24. package/src/app/api/connectors/route.ts +5 -4
  25. package/src/app/api/eval/run/route.ts +2 -1
  26. package/src/app/api/eval/suite/route.ts +2 -1
  27. package/src/app/api/external-agents/route.test.ts +1 -1
  28. package/src/app/api/external-agents/route.ts +2 -2
  29. package/src/app/api/files/serve/route.ts +1 -1
  30. package/src/app/api/gateways/[id]/route.ts +7 -5
  31. package/src/app/api/gateways/route.ts +1 -1
  32. package/src/app/api/knowledge/upload/route.ts +1 -1
  33. package/src/app/api/logs/route.ts +5 -7
  34. package/src/app/api/memory-images/[filename]/route.ts +2 -3
  35. package/src/app/api/openclaw/agent-files/route.ts +4 -3
  36. package/src/app/api/openclaw/approvals/route.ts +3 -4
  37. package/src/app/api/openclaw/config-sync/route.ts +3 -2
  38. package/src/app/api/openclaw/cron/route.ts +3 -2
  39. package/src/app/api/openclaw/dotenv-keys/route.ts +2 -1
  40. package/src/app/api/openclaw/exec-config/route.ts +3 -2
  41. package/src/app/api/openclaw/gateway/route.ts +5 -4
  42. package/src/app/api/openclaw/history/route.ts +3 -2
  43. package/src/app/api/openclaw/media/route.ts +2 -1
  44. package/src/app/api/openclaw/permissions/route.ts +3 -2
  45. package/src/app/api/openclaw/sandbox-env/route.ts +3 -2
  46. package/src/app/api/openclaw/skills/install/route.ts +2 -1
  47. package/src/app/api/openclaw/skills/remove/route.ts +2 -1
  48. package/src/app/api/openclaw/skills/route.ts +3 -2
  49. package/src/app/api/orchestrator/run/route.ts +5 -14
  50. package/src/app/api/perf/route.ts +43 -0
  51. package/src/app/api/plugins/dependencies/route.ts +2 -1
  52. package/src/app/api/plugins/install/route.ts +2 -1
  53. package/src/app/api/plugins/marketplace/route.ts +3 -2
  54. package/src/app/api/plugins/settings/route.ts +2 -1
  55. package/src/app/api/preview-server/route.ts +11 -10
  56. package/src/app/api/projects/[id]/route.ts +1 -1
  57. package/src/app/api/schedules/[id]/route.test.ts +128 -0
  58. package/src/app/api/schedules/[id]/route.ts +43 -43
  59. package/src/app/api/schedules/[id]/run/route.ts +11 -62
  60. package/src/app/api/schedules/route.ts +21 -87
  61. package/src/app/api/settings/route.ts +2 -0
  62. package/src/app/api/setup/doctor/route.ts +9 -8
  63. package/src/app/api/tasks/[id]/approve/route.ts +33 -30
  64. package/src/app/api/tasks/[id]/route.ts +12 -35
  65. package/src/app/api/tasks/import/github/route.ts +2 -1
  66. package/src/app/api/tasks/route.ts +79 -91
  67. package/src/app/api/wallets/[id]/approve/route.ts +2 -1
  68. package/src/app/api/wallets/[id]/route.ts +13 -19
  69. package/src/app/api/wallets/[id]/send/route.ts +2 -1
  70. package/src/app/api/wallets/route.ts +2 -1
  71. package/src/app/api/webhooks/[id]/route.ts +2 -1
  72. package/src/app/api/webhooks/route.test.ts +3 -1
  73. package/src/app/page.tsx +23 -331
  74. package/src/cli/index.js +19 -0
  75. package/src/cli/index.ts +38 -7
  76. package/src/cli/spec.js +9 -0
  77. package/src/components/activity/activity-feed.tsx +7 -4
  78. package/src/components/agents/agent-card.tsx +32 -6
  79. package/src/components/agents/agent-chat-list.tsx +55 -22
  80. package/src/components/agents/agent-files-editor.tsx +3 -2
  81. package/src/components/agents/agent-sheet.tsx +123 -22
  82. package/src/components/agents/inspector-panel.tsx +1 -1
  83. package/src/components/agents/openclaw-skills-panel.tsx +2 -1
  84. package/src/components/agents/trash-list.tsx +1 -1
  85. package/src/components/auth/access-key-gate.tsx +8 -2
  86. package/src/components/auth/setup-wizard.tsx +10 -9
  87. package/src/components/auth/user-picker.tsx +3 -2
  88. package/src/components/chat/chat-area.tsx +20 -1
  89. package/src/components/chat/chat-card.tsx +18 -3
  90. package/src/components/chat/chat-header.tsx +24 -4
  91. package/src/components/chat/chat-list.tsx +2 -11
  92. package/src/components/chat/heartbeat-history-panel.tsx +2 -1
  93. package/src/components/chat/message-bubble.tsx +45 -6
  94. package/src/components/chat/message-list.tsx +280 -145
  95. package/src/components/chat/streaming-bubble.tsx +217 -60
  96. package/src/components/chat/swarm-panel.test.ts +274 -0
  97. package/src/components/chat/swarm-panel.tsx +410 -0
  98. package/src/components/chat/swarm-status-card.tsx +346 -0
  99. package/src/components/chat/tool-call-bubble.tsx +48 -23
  100. package/src/components/chatrooms/chatroom-list.tsx +8 -5
  101. package/src/components/chatrooms/chatroom-message.tsx +10 -7
  102. package/src/components/chatrooms/chatroom-view.tsx +12 -9
  103. package/src/components/connectors/connector-health.tsx +6 -4
  104. package/src/components/connectors/connector-list.tsx +16 -11
  105. package/src/components/connectors/connector-sheet.tsx +12 -6
  106. package/src/components/home/home-view.tsx +38 -24
  107. package/src/components/input/chat-input.tsx +10 -1
  108. package/src/components/layout/app-layout.tsx +2 -38
  109. package/src/components/layout/sheet-layer.tsx +50 -0
  110. package/src/components/mcp-servers/mcp-server-list.tsx +37 -5
  111. package/src/components/mcp-servers/mcp-server-sheet.tsx +12 -2
  112. package/src/components/plugins/plugin-list.tsx +8 -4
  113. package/src/components/plugins/plugin-sheet.tsx +2 -1
  114. package/src/components/providers/provider-list.tsx +3 -2
  115. package/src/components/providers/provider-sheet.tsx +2 -1
  116. package/src/components/runs/run-list.tsx +11 -7
  117. package/src/components/schedules/schedule-card.tsx +5 -3
  118. package/src/components/shared/agent-switch-dialog.tsx +1 -1
  119. package/src/components/shared/attachment-chip.tsx +19 -3
  120. package/src/components/shared/notification-center.tsx +6 -3
  121. package/src/components/shared/settings/plugin-manager.tsx +3 -2
  122. package/src/components/shared/settings/section-embedding.tsx +2 -1
  123. package/src/components/shared/settings/section-orchestrator.tsx +2 -1
  124. package/src/components/shared/settings/section-user-preferences.tsx +107 -0
  125. package/src/components/shared/settings/settings-page.tsx +13 -9
  126. package/src/components/skills/clawhub-browser.tsx +15 -4
  127. package/src/components/skills/skill-list.tsx +15 -4
  128. package/src/components/tasks/approvals-panel.tsx +2 -1
  129. package/src/components/tasks/task-board.tsx +35 -37
  130. package/src/components/tasks/task-sheet.tsx +4 -3
  131. package/src/components/ui/full-screen-loader.tsx +164 -0
  132. package/src/components/wallets/wallet-approval-dialog.tsx +2 -1
  133. package/src/components/wallets/wallet-panel.tsx +6 -5
  134. package/src/components/wallets/wallet-section.tsx +3 -2
  135. package/src/components/webhooks/webhook-list.tsx +4 -5
  136. package/src/components/webhooks/webhook-sheet.tsx +6 -6
  137. package/src/hooks/use-app-bootstrap.ts +202 -0
  138. package/src/hooks/use-mounted-ref.ts +14 -0
  139. package/src/hooks/use-now.ts +31 -0
  140. package/src/hooks/use-openclaw-gateway.ts +2 -1
  141. package/src/instrumentation.ts +20 -8
  142. package/src/lib/agent-default-tools.test.ts +52 -0
  143. package/src/lib/agent-default-tools.ts +40 -0
  144. package/src/lib/api-client.test.ts +21 -0
  145. package/src/lib/api-client.ts +6 -11
  146. package/src/lib/canvas-content.test.ts +360 -0
  147. package/src/lib/chat-streaming-state.test.ts +49 -2
  148. package/src/lib/chat-streaming-state.ts +26 -10
  149. package/src/lib/fetch-timeout.test.ts +54 -0
  150. package/src/lib/fetch-timeout.ts +60 -3
  151. package/src/lib/live-tool-events.test.ts +77 -0
  152. package/src/lib/live-tool-events.ts +73 -0
  153. package/src/lib/local-observability.test.ts +2 -2
  154. package/src/lib/openclaw-endpoint.test.ts +1 -1
  155. package/src/lib/providers/anthropic.ts +12 -16
  156. package/src/lib/providers/index.ts +4 -2
  157. package/src/lib/providers/ollama.ts +9 -6
  158. package/src/lib/providers/openai.ts +11 -14
  159. package/src/lib/runtime-env.test.ts +8 -8
  160. package/src/lib/schedule-dedupe-advanced.test.ts +2 -2
  161. package/src/lib/schedule-dedupe.test.ts +1 -1
  162. package/src/lib/schedule-dedupe.ts +3 -2
  163. package/src/lib/server/agent-thread-session.test.ts +6 -6
  164. package/src/lib/server/agent-thread-session.ts +6 -9
  165. package/src/lib/server/alert-dispatch.ts +2 -1
  166. package/src/lib/server/api-routes.test.ts +6 -6
  167. package/src/lib/server/approval-connector-notify.test.ts +4 -4
  168. package/src/lib/server/approvals-auto-approve.test.ts +29 -29
  169. package/src/lib/server/approvals.test.ts +317 -0
  170. package/src/lib/server/approvals.ts +5 -4
  171. package/src/lib/server/autonomy-runtime.test.ts +11 -11
  172. package/src/lib/server/browser-state.ts +2 -2
  173. package/src/lib/server/capability-router.test.ts +1 -1
  174. package/src/lib/server/capability-router.ts +3 -2
  175. package/src/lib/server/chat-execution-advanced.test.ts +15 -2
  176. package/src/lib/server/chat-execution-connector-delivery.ts +67 -0
  177. package/src/lib/server/chat-execution-disabled.test.ts +3 -3
  178. package/src/lib/server/chat-execution-eval-history.test.ts +3 -3
  179. package/src/lib/server/chat-execution-heartbeat.test.ts +42 -1
  180. package/src/lib/server/chat-execution-session-sync.test.ts +119 -0
  181. package/src/lib/server/chat-execution-tool-events.ts +116 -0
  182. package/src/lib/server/chat-execution-utils.test.ts +479 -0
  183. package/src/lib/server/chat-execution-utils.ts +533 -0
  184. package/src/lib/server/chat-execution.ts +153 -748
  185. package/src/lib/server/chat-streaming-utils.ts +174 -0
  186. package/src/lib/server/chat-turn-tool-routing.ts +310 -0
  187. package/src/lib/server/chatroom-session-persistence.test.ts +2 -2
  188. package/src/lib/server/clawhub-client.ts +2 -1
  189. package/src/lib/server/collection-helpers.test.ts +92 -0
  190. package/src/lib/server/collection-helpers.ts +25 -3
  191. package/src/lib/server/connectors/access.ts +146 -0
  192. package/src/lib/server/connectors/bluebubbles.test.ts +1 -1
  193. package/src/lib/server/connectors/bluebubbles.ts +4 -4
  194. package/src/lib/server/connectors/commands.ts +367 -0
  195. package/src/lib/server/connectors/connector-routing.test.ts +4 -4
  196. package/src/lib/server/connectors/delivery.ts +142 -0
  197. package/src/lib/server/connectors/discord.ts +37 -40
  198. package/src/lib/server/connectors/email.ts +11 -10
  199. package/src/lib/server/connectors/googlechat.ts +4 -4
  200. package/src/lib/server/connectors/inbound-audio-transcription.ts +2 -1
  201. package/src/lib/server/connectors/ingress-delivery.ts +23 -0
  202. package/src/lib/server/connectors/manager-roundtrip.test.ts +300 -0
  203. package/src/lib/server/connectors/manager.test.ts +352 -77
  204. package/src/lib/server/connectors/manager.ts +134 -673
  205. package/src/lib/server/connectors/matrix.ts +4 -4
  206. package/src/lib/server/connectors/message-sentinel.ts +7 -0
  207. package/src/lib/server/connectors/openclaw.test.ts +1 -1
  208. package/src/lib/server/connectors/openclaw.ts +8 -10
  209. package/src/lib/server/connectors/outbox.test.ts +192 -0
  210. package/src/lib/server/connectors/outbox.ts +369 -0
  211. package/src/lib/server/connectors/pairing.test.ts +18 -1
  212. package/src/lib/server/connectors/pairing.ts +49 -4
  213. package/src/lib/server/connectors/policy.ts +9 -3
  214. package/src/lib/server/connectors/reconnect-state.ts +71 -0
  215. package/src/lib/server/connectors/response-media.ts +256 -0
  216. package/src/lib/server/connectors/runtime-state.ts +67 -0
  217. package/src/lib/server/connectors/session.test.ts +357 -0
  218. package/src/lib/server/connectors/session.ts +422 -0
  219. package/src/lib/server/connectors/signal.ts +7 -7
  220. package/src/lib/server/connectors/slack.ts +43 -43
  221. package/src/lib/server/connectors/teams.ts +4 -4
  222. package/src/lib/server/connectors/telegram.ts +37 -43
  223. package/src/lib/server/connectors/types.ts +31 -1
  224. package/src/lib/server/connectors/whatsapp.test.ts +108 -0
  225. package/src/lib/server/connectors/whatsapp.ts +106 -34
  226. package/src/lib/server/context-manager.test.ts +409 -0
  227. package/src/lib/server/cost.test.ts +1 -1
  228. package/src/lib/server/daemon-policy.ts +78 -0
  229. package/src/lib/server/daemon-state-connectors.test.ts +167 -0
  230. package/src/lib/server/daemon-state.test.ts +283 -55
  231. package/src/lib/server/daemon-state.ts +106 -109
  232. package/src/lib/server/data-dir.test.ts +5 -5
  233. package/src/lib/server/data-dir.ts +4 -0
  234. package/src/lib/server/delegation-jobs-advanced.test.ts +1 -1
  235. package/src/lib/server/delegation-jobs.test.ts +87 -0
  236. package/src/lib/server/delegation-jobs.ts +42 -48
  237. package/src/lib/server/devserver-launch.ts +1 -1
  238. package/src/lib/server/document-utils.ts +7 -9
  239. package/src/lib/server/elevenlabs.ts +2 -1
  240. package/src/lib/server/embeddings.test.ts +105 -0
  241. package/src/lib/server/ethereum.ts +3 -2
  242. package/src/lib/server/eval/agent-regression.ts +3 -2
  243. package/src/lib/server/eval/runner.ts +2 -1
  244. package/src/lib/server/eval/scorer.ts +2 -1
  245. package/src/lib/server/evm-swap.ts +2 -1
  246. package/src/lib/server/gateway/protocol.test.ts +1 -1
  247. package/src/lib/server/guardian.ts +2 -1
  248. package/src/lib/server/heartbeat-blocked-suppression.test.ts +151 -0
  249. package/src/lib/server/heartbeat-service-timer.test.ts +6 -6
  250. package/src/lib/server/heartbeat-service.test.ts +406 -0
  251. package/src/lib/server/heartbeat-service.ts +54 -7
  252. package/src/lib/server/heartbeat-wake.test.ts +19 -0
  253. package/src/lib/server/heartbeat-wake.ts +17 -16
  254. package/src/lib/server/integrity-monitor.test.ts +149 -0
  255. package/src/lib/server/json-utils.ts +22 -0
  256. package/src/lib/server/knowledge-db.test.ts +13 -13
  257. package/src/lib/server/link-understanding.ts +2 -1
  258. package/src/lib/server/llm-response-cache.test.ts +1 -1
  259. package/src/lib/server/main-agent-loop-advanced.test.ts +65 -3
  260. package/src/lib/server/main-agent-loop.test.ts +6 -6
  261. package/src/lib/server/main-agent-loop.ts +21 -7
  262. package/src/lib/server/mcp-client.test.ts +1 -1
  263. package/src/lib/server/mcp-conformance.test.ts +1 -1
  264. package/src/lib/server/mcp-conformance.ts +3 -2
  265. package/src/lib/server/memory-consolidation.ts +2 -1
  266. package/src/lib/server/memory-db.test.ts +485 -0
  267. package/src/lib/server/memory-db.ts +39 -26
  268. package/src/lib/server/memory-graph.test.ts +2 -2
  269. package/src/lib/server/memory-policy.test.ts +7 -7
  270. package/src/lib/server/memory-retrieval.test.ts +1 -1
  271. package/src/lib/server/openclaw-config-sync.ts +2 -1
  272. package/src/lib/server/openclaw-deploy.test.ts +1 -1
  273. package/src/lib/server/openclaw-deploy.ts +8 -12
  274. package/src/lib/server/openclaw-exec-config.ts +2 -1
  275. package/src/lib/server/openclaw-gateway.ts +6 -7
  276. package/src/lib/server/openclaw-skills-normalize.ts +2 -1
  277. package/src/lib/server/openclaw-sync.ts +7 -5
  278. package/src/lib/server/orchestrator-lg-structure.test.ts +17 -0
  279. package/src/lib/server/orchestrator-lg.ts +199 -327
  280. package/src/lib/server/path-utils.ts +31 -0
  281. package/src/lib/server/perf.ts +161 -0
  282. package/src/lib/server/plugins-approval-guidance.ts +115 -0
  283. package/src/lib/server/plugins.test.ts +1 -1
  284. package/src/lib/server/plugins.ts +22 -132
  285. package/src/lib/server/process-manager.ts +5 -8
  286. package/src/lib/server/provider-health.test.ts +137 -0
  287. package/src/lib/server/provider-health.ts +3 -3
  288. package/src/lib/server/provider-model-discovery.ts +3 -12
  289. package/src/lib/server/queue-followups.test.ts +9 -9
  290. package/src/lib/server/queue-reconcile.test.ts +2 -2
  291. package/src/lib/server/queue-recovery.test.ts +269 -0
  292. package/src/lib/server/queue.test.ts +570 -0
  293. package/src/lib/server/queue.ts +62 -455
  294. package/src/lib/server/resolve-image.ts +30 -0
  295. package/src/lib/server/runtime-settings.test.ts +4 -4
  296. package/src/lib/server/runtime-storage-write-paths.test.ts +60 -0
  297. package/src/lib/server/schedule-normalization.test.ts +279 -0
  298. package/src/lib/server/schedule-service.ts +263 -0
  299. package/src/lib/server/scheduler.ts +17 -74
  300. package/src/lib/server/session-mailbox.test.ts +191 -0
  301. package/src/lib/server/session-run-manager.test.ts +640 -0
  302. package/src/lib/server/session-run-manager.ts +59 -15
  303. package/src/lib/server/session-tools/autonomy-tools.test.ts +20 -20
  304. package/src/lib/server/session-tools/calendar.ts +2 -1
  305. package/src/lib/server/session-tools/canvas.ts +2 -1
  306. package/src/lib/server/session-tools/chatroom.ts +2 -1
  307. package/src/lib/server/session-tools/connector.ts +26 -28
  308. package/src/lib/server/session-tools/context-mgmt.ts +3 -2
  309. package/src/lib/server/session-tools/crawl.ts +4 -3
  310. package/src/lib/server/session-tools/crud.ts +105 -324
  311. package/src/lib/server/session-tools/delegate-fallback.test.ts +9 -9
  312. package/src/lib/server/session-tools/delegate.ts +6 -8
  313. package/src/lib/server/session-tools/discovery-approvals.test.ts +15 -15
  314. package/src/lib/server/session-tools/discovery.ts +4 -3
  315. package/src/lib/server/session-tools/document.ts +2 -1
  316. package/src/lib/server/session-tools/email.ts +2 -1
  317. package/src/lib/server/session-tools/extract.ts +2 -1
  318. package/src/lib/server/session-tools/file.ts +4 -3
  319. package/src/lib/server/session-tools/http.ts +2 -1
  320. package/src/lib/server/session-tools/human-loop.ts +2 -1
  321. package/src/lib/server/session-tools/image-gen.ts +4 -3
  322. package/src/lib/server/session-tools/index.ts +26 -30
  323. package/src/lib/server/session-tools/mailbox.ts +2 -1
  324. package/src/lib/server/session-tools/manage-connectors.test.ts +4 -4
  325. package/src/lib/server/session-tools/manage-schedules.test.ts +12 -12
  326. package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +5 -5
  327. package/src/lib/server/session-tools/manage-tasks.test.ts +2 -2
  328. package/src/lib/server/session-tools/monitor.ts +2 -1
  329. package/src/lib/server/session-tools/platform.ts +2 -1
  330. package/src/lib/server/session-tools/plugin-creator.ts +2 -1
  331. package/src/lib/server/session-tools/replicate.ts +3 -2
  332. package/src/lib/server/session-tools/session-tools-wiring.test.ts +6 -6
  333. package/src/lib/server/session-tools/shell.ts +4 -9
  334. package/src/lib/server/session-tools/subagent.ts +322 -170
  335. package/src/lib/server/session-tools/table.ts +6 -5
  336. package/src/lib/server/session-tools/wallet-tool.test.ts +3 -3
  337. package/src/lib/server/session-tools/wallet.ts +7 -6
  338. package/src/lib/server/session-tools/web-browser-config.test.ts +1 -0
  339. package/src/lib/server/session-tools/web-utils.ts +317 -0
  340. package/src/lib/server/session-tools/web.ts +62 -328
  341. package/src/lib/server/skill-prompt-budget.test.ts +1 -1
  342. package/src/lib/server/skills-normalize.ts +2 -1
  343. package/src/lib/server/storage-item-access.test.ts +302 -0
  344. package/src/lib/server/storage.ts +366 -314
  345. package/src/lib/server/stream-agent-chat.test.ts +82 -3
  346. package/src/lib/server/stream-agent-chat.ts +146 -510
  347. package/src/lib/server/stream-continuation.ts +412 -0
  348. package/src/lib/server/subagent-lineage.test.ts +647 -0
  349. package/src/lib/server/subagent-lineage.ts +435 -0
  350. package/src/lib/server/subagent-runtime.test.ts +484 -0
  351. package/src/lib/server/subagent-runtime.ts +419 -0
  352. package/src/lib/server/subagent-swarm.test.ts +391 -0
  353. package/src/lib/server/subagent-swarm.ts +564 -0
  354. package/src/lib/server/system-events.ts +3 -3
  355. package/src/lib/server/task-followups.test.ts +491 -0
  356. package/src/lib/server/task-followups.ts +391 -0
  357. package/src/lib/server/task-lifecycle.test.ts +205 -0
  358. package/src/lib/server/task-lifecycle.ts +200 -0
  359. package/src/lib/server/task-quality-gate.test.ts +1 -1
  360. package/src/lib/server/task-resume.ts +208 -0
  361. package/src/lib/server/task-service.test.ts +108 -0
  362. package/src/lib/server/task-service.ts +264 -0
  363. package/src/lib/server/task-validation.test.ts +1 -1
  364. package/src/lib/server/test-utils/run-with-temp-data-dir.ts +42 -0
  365. package/src/lib/server/tool-capability-policy.test.ts +2 -2
  366. package/src/lib/server/tool-capability-policy.ts +3 -2
  367. package/src/lib/server/tool-planning.ts +2 -1
  368. package/src/lib/server/tool-retry.ts +2 -3
  369. package/src/lib/server/wake-dispatcher.test.ts +303 -0
  370. package/src/lib/server/wake-dispatcher.ts +318 -0
  371. package/src/lib/server/wake-mode.test.ts +161 -0
  372. package/src/lib/server/wake-mode.ts +174 -0
  373. package/src/lib/server/wallet-service.ts +8 -9
  374. package/src/lib/server/watch-jobs.ts +2 -1
  375. package/src/lib/server/workspace-context.ts +2 -2
  376. package/src/lib/shared-utils.test.ts +142 -0
  377. package/src/lib/shared-utils.ts +62 -0
  378. package/src/lib/tool-event-summary.ts +2 -1
  379. package/src/lib/view-routes.test.ts +100 -0
  380. package/src/lib/wallet.test.ts +322 -6
  381. package/src/proxy.test.ts +4 -4
  382. package/src/proxy.ts +2 -3
  383. package/src/stores/set-if-changed.ts +40 -0
  384. package/src/stores/slices/agent-slice.ts +111 -0
  385. package/src/stores/slices/auth-slice.ts +25 -0
  386. package/src/stores/slices/data-slice.ts +301 -0
  387. package/src/stores/slices/index.ts +7 -0
  388. package/src/stores/slices/session-slice.ts +112 -0
  389. package/src/stores/slices/task-slice.ts +63 -0
  390. package/src/stores/slices/ui-slice.ts +192 -0
  391. package/src/stores/use-app-store.ts +17 -822
  392. package/src/stores/use-approval-store.ts +2 -1
  393. package/src/stores/use-chat-store.ts +8 -1
  394. package/src/types/index.ts +10 -0
@@ -0,0 +1,164 @@
1
+ export function FullScreenLoader(props: {
2
+ stage?: string | null
3
+ stalled?: boolean
4
+ onReload?: () => void
5
+ onReset?: () => void
6
+ }) {
7
+ return (
8
+ <div className="h-full flex flex-col items-center justify-center bg-bg overflow-hidden select-none">
9
+ {/* Animated orbital ring */}
10
+ <div className="relative w-[120px] h-[120px] mb-8">
11
+ {/* Outer glow pulse */}
12
+ <div
13
+ className="absolute inset-[-20px] rounded-full"
14
+ style={{
15
+ background: 'radial-gradient(circle, rgba(99,102,241,0.08) 0%, transparent 70%)',
16
+ animation: 'sc-glow 2.5s ease-in-out infinite',
17
+ }}
18
+ />
19
+
20
+ {/* Orbital ring */}
21
+ <div
22
+ className="absolute inset-0 rounded-full border border-white/[0.06]"
23
+ style={{ animation: 'sc-ring 3s linear infinite' }}
24
+ />
25
+
26
+ {/* Orbiting dots */}
27
+ {[0, 1, 2, 3, 4, 5].map((i) => (
28
+ <div
29
+ key={i}
30
+ className="absolute inset-0"
31
+ style={{
32
+ animation: `sc-orbit 2.4s cubic-bezier(0.4, 0, 0.2, 1) infinite`,
33
+ animationDelay: `${i * -0.4}s`,
34
+ }}
35
+ >
36
+ <div
37
+ className="absolute top-0 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full"
38
+ style={{
39
+ width: i === 0 ? 8 : 6,
40
+ height: i === 0 ? 8 : 6,
41
+ background: i === 0 ? '#818CF8' : `rgba(129, 140, 248, ${0.7 - i * 0.1})`,
42
+ boxShadow: i === 0 ? '0 0 12px rgba(99,102,241,0.5)' : 'none',
43
+ }}
44
+ />
45
+ </div>
46
+ ))}
47
+
48
+ {/* Center logo mark */}
49
+ <div className="absolute inset-0 flex items-center justify-center">
50
+ <div
51
+ className="relative"
52
+ style={{ animation: 'sc-breathe 2.5s ease-in-out infinite' }}
53
+ >
54
+ <svg width="36" height="36" viewBox="0 0 36 36" fill="none">
55
+ {/* Hexagonal claw mark */}
56
+ <path
57
+ d="M18 4L30 11V25L18 32L6 25V11L18 4Z"
58
+ stroke="rgba(129, 140, 248, 0.3)"
59
+ strokeWidth="1"
60
+ fill="none"
61
+ />
62
+ <path
63
+ d="M18 9L25 13V23L18 27L11 23V13L18 9Z"
64
+ stroke="rgba(129, 140, 248, 0.5)"
65
+ strokeWidth="1.5"
66
+ fill="rgba(99, 102, 241, 0.06)"
67
+ />
68
+ {/* Claw lines */}
69
+ <path d="M14 15L18 20L22 15" stroke="#818CF8" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
70
+ <path d="M12 13L18 20L24 13" stroke="rgba(129, 140, 248, 0.3)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
71
+ </svg>
72
+ </div>
73
+ </div>
74
+ </div>
75
+
76
+ {/* Brand text */}
77
+ <div
78
+ className="text-[15px] font-display font-700 tracking-[0.15em] uppercase"
79
+ style={{
80
+ background: 'linear-gradient(135deg, rgba(255,255,255,0.6), rgba(129, 140, 248, 0.8))',
81
+ WebkitBackgroundClip: 'text',
82
+ WebkitTextFillColor: 'transparent',
83
+ animation: 'sc-text-fade 2s ease-in-out infinite alternate, fade-up 0.6s var(--ease-spring) 0.2s both',
84
+ }}
85
+ >
86
+ SwarmClaw
87
+ </div>
88
+
89
+ {/* Loading bar */}
90
+ <div className="mt-4 w-[100px] h-[2px] rounded-full bg-white/[0.06] overflow-hidden" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.3s both' }}>
91
+ <div
92
+ className="h-full rounded-full bg-accent-bright/60"
93
+ style={{ animation: 'sc-progress 1.5s ease-in-out infinite' }}
94
+ />
95
+ </div>
96
+
97
+ {props.stage ? (
98
+ <p
99
+ className="mt-4 text-[12px] text-text-3"
100
+ style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.4s both' }}
101
+ >
102
+ {props.stage}
103
+ </p>
104
+ ) : null}
105
+
106
+ {props.stalled ? (
107
+ <div
108
+ className="mt-6 max-w-[360px] px-4 text-center"
109
+ style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.5s both' }}
110
+ >
111
+ <p className="text-[12px] text-text-2">
112
+ Startup is taking longer than expected. This usually means the browser kept stale local state while the dev server restarted.
113
+ </p>
114
+ <div className="mt-4 flex items-center justify-center gap-3">
115
+ <button
116
+ type="button"
117
+ onClick={props.onReload}
118
+ className="px-4 py-2 rounded-[12px] border border-white/[0.08] bg-surface text-[12px] text-text-2 transition-colors hover:bg-surface-2"
119
+ >
120
+ Reload
121
+ </button>
122
+ <button
123
+ type="button"
124
+ onClick={props.onReset}
125
+ className="px-4 py-2 rounded-[12px] border border-white/[0.08] bg-transparent text-[12px] text-text-3 transition-colors hover:bg-white/[0.04]"
126
+ >
127
+ Reset Local Session
128
+ </button>
129
+ </div>
130
+ </div>
131
+ ) : null}
132
+
133
+ {/* Loading animation keyframes */}
134
+ <style>{`
135
+ @keyframes sc-orbit {
136
+ from { transform: rotate(0deg); }
137
+ to { transform: rotate(360deg); }
138
+ }
139
+ @keyframes sc-ring {
140
+ from { transform: rotate(0deg) scale(1); }
141
+ 50% { transform: rotate(180deg) scale(1.02); }
142
+ to { transform: rotate(360deg) scale(1); }
143
+ }
144
+ @keyframes sc-breathe {
145
+ 0%, 100% { transform: scale(1); opacity: 0.9; }
146
+ 50% { transform: scale(1.06); opacity: 1; }
147
+ }
148
+ @keyframes sc-glow {
149
+ 0%, 100% { opacity: 0.5; transform: scale(0.9); }
150
+ 50% { opacity: 1; transform: scale(1.1); }
151
+ }
152
+ @keyframes sc-text-fade {
153
+ 0% { opacity: 0.6; }
154
+ 100% { opacity: 1; }
155
+ }
156
+ @keyframes sc-progress {
157
+ 0% { width: 0; margin-left: 0; }
158
+ 50% { width: 70%; margin-left: 15%; }
159
+ 100% { width: 0; margin-left: 100%; }
160
+ }
161
+ `}</style>
162
+ </div>
163
+ )
164
+ }
@@ -5,6 +5,7 @@ import { api } from '@/lib/api-client'
5
5
  import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
6
6
  import type { WalletTransaction } from '@/types'
7
7
  import { formatWalletAmount, getWalletAssetSymbol, getWalletAtomicAmount } from '@/lib/wallet'
8
+ import { errorMessage } from '@/lib/shared-utils'
8
9
 
9
10
  interface WalletApprovalDialogProps {
10
11
  transaction: WalletTransaction
@@ -28,7 +29,7 @@ export function WalletApprovalDialog({ transaction, walletAddress, onClose, onRe
28
29
  onResolved()
29
30
  onClose()
30
31
  } catch (err: unknown) {
31
- setError(err instanceof Error ? err.message : String(err))
32
+ setError(errorMessage(err))
32
33
  } finally {
33
34
  setSubmitting(false)
34
35
  }
@@ -21,6 +21,7 @@ import {
21
21
  } from '@/lib/wallet'
22
22
  import { type WalletTransactionFilter, filterWalletTransactions, getWalletTransactionStatusGroup } from '@/lib/wallet-transactions'
23
23
  import { toast } from 'sonner'
24
+ import { dedup, errorMessage } from '@/lib/shared-utils'
24
25
 
25
26
  type SafeWallet = Omit<AgentWallet, 'encryptedPrivateKey'> & {
26
27
  balanceAtomic?: string
@@ -39,7 +40,7 @@ function getAgentWalletIds(agent: Agent | undefined | null): string[] {
39
40
  const legacy = typeof agent?.walletId === 'string' && agent.walletId.trim()
40
41
  ? [agent.walletId.trim()]
41
42
  : []
42
- return [...new Set([...ids, ...legacy])]
43
+ return dedup([...ids, ...legacy])
43
44
  }
44
45
 
45
46
  function getAgentActiveWalletId(agent: Agent | undefined | null, fallbackWallets: SafeWallet[] = []): string | null {
@@ -256,7 +257,7 @@ export function WalletPanel() {
256
257
  setEditingLimits(false)
257
258
  loadDetail()
258
259
  } catch (err: unknown) {
259
- toast.error(err instanceof Error ? err.message : String(err))
260
+ toast.error(errorMessage(err))
260
261
  }
261
262
  setSaving(false)
262
263
  }, [selectedWalletId, selectedWallet, perTxLimit, dailyLimit, requireApproval, loadDetail])
@@ -269,7 +270,7 @@ export function WalletPanel() {
269
270
  toast.success('Default wallet updated')
270
271
  loadWallets()
271
272
  } catch (err: unknown) {
272
- toast.error(err instanceof Error ? err.message : String(err))
273
+ toast.error(errorMessage(err))
273
274
  }
274
275
  setSettingDefault(false)
275
276
  }, [selectedWalletId, loadWallets])
@@ -322,7 +323,7 @@ export function WalletPanel() {
322
323
  setCreateChain('solana')
323
324
  loadWallets()
324
325
  } catch (err: unknown) {
325
- setCreateError(err instanceof Error ? err.message : String(err))
326
+ setCreateError(errorMessage(err))
326
327
  }
327
328
  setCreating(false)
328
329
  }, [createAgentId, createChain, loadWallets])
@@ -636,7 +637,7 @@ export function WalletPanel() {
636
637
  setReassigning(false)
637
638
  loadWallets()
638
639
  } catch (err: unknown) {
639
- setReassignError(err instanceof Error ? err.message : String(err) || 'Reassign failed')
640
+ setReassignError(errorMessage(err) || 'Reassign failed')
640
641
  }
641
642
  setReassignSaving(false)
642
643
  }}
@@ -5,6 +5,7 @@ import { api } from '@/lib/api-client'
5
5
  import { copyTextToClipboard } from '@/lib/clipboard'
6
6
  import type { AgentWallet, WalletAssetBalance, WalletPortfolioSummary, WalletChain } from '@/types'
7
7
  import { toast } from 'sonner'
8
+ import { errorMessage } from '@/lib/shared-utils'
8
9
  import {
9
10
  SUPPORTED_WALLET_CHAINS,
10
11
  formatWalletAmount,
@@ -67,7 +68,7 @@ export function WalletSection({ agentId, wallets, activeWalletId, onWalletCreate
67
68
  toast.success('Agent wallet created successfully')
68
69
  await onWalletCreated()
69
70
  } catch (err: unknown) {
70
- const msg = err instanceof Error ? err.message : String(err)
71
+ const msg = errorMessage(err)
71
72
  setError(msg)
72
73
  toast.error(msg)
73
74
  } finally {
@@ -92,7 +93,7 @@ export function WalletSection({ agentId, wallets, activeWalletId, onWalletCreate
92
93
  toast.success('Default wallet updated')
93
94
  await onWalletCreated()
94
95
  } catch (err: unknown) {
95
- const msg = err instanceof Error ? err.message : String(err)
96
+ const msg = errorMessage(err)
96
97
  setError(msg)
97
98
  toast.error(msg)
98
99
  } finally {
@@ -4,9 +4,8 @@ import { useEffect, useMemo, useState } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
5
  import { copyTextToClipboard } from '@/lib/clipboard'
6
6
 
7
- function webhookUrl(id: string): string {
8
- if (typeof window === 'undefined') return `/api/webhooks/${id}`
9
- return `${window.location.origin}/api/webhooks/${id}`
7
+ function webhookPath(id: string): string {
8
+ return `/api/webhooks/${id}`
10
9
  }
11
10
 
12
11
  function formatEvents(events: string[] | undefined): string {
@@ -74,7 +73,7 @@ export function WebhookList({ inSidebar }: { inSidebar?: boolean }) {
74
73
  <div className={`flex-1 overflow-y-auto ${inSidebar ? 'pb-10' : 'pb-20'}`}>
75
74
  {list.map((hook, idx) => {
76
75
  const agentName = hook.agentId ? agents[hook.agentId]?.name : null
77
- const endpoint = webhookUrl(hook.id)
76
+ const endpoint = webhookPath(hook.id)
78
77
  const copiedEndpoint = copied === `endpoint:${hook.id}`
79
78
  const copiedSecret = copied === `secret:${hook.id}`
80
79
  const hasSecret = typeof hook.secret === 'string' && hook.secret.trim().length > 0
@@ -121,7 +120,7 @@ export function WebhookList({ inSidebar }: { inSidebar?: boolean }) {
121
120
  <button
122
121
  onClick={(e) => {
123
122
  e.stopPropagation()
124
- copyText(`endpoint:${hook.id}`, endpoint)
123
+ copyText(`endpoint:${hook.id}`, `${window.location.origin}${endpoint}`)
125
124
  }}
126
125
  title={copiedEndpoint ? 'Copied endpoint' : 'Copy endpoint URL'}
127
126
  className={`shrink-0 w-8 h-8 rounded-[8px] flex items-center justify-center transition-all cursor-pointer border-none ${
@@ -7,6 +7,7 @@ import { ConfirmDialog } from '@/components/shared/confirm-dialog'
7
7
  import { api } from '@/lib/api-client'
8
8
  import { copyTextToClipboard } from '@/lib/clipboard'
9
9
  import type { Webhook, WebhookLogEntry } from '@/types'
10
+ import { dedup } from '@/lib/shared-utils'
10
11
  import { toast } from 'sonner'
11
12
 
12
13
  type WebhookApiResponse = Webhook | { error: string }
@@ -14,9 +15,8 @@ type DeleteWebhookResponse = { ok: boolean } | { error: string }
14
15
 
15
16
  const inputClass = 'w-full px-4 py-3 rounded-[14px] bg-bg border border-white/[0.06] text-text text-[14px] outline-none focus:border-accent-bright/40 transition-colors placeholder:text-text-3/70'
16
17
 
17
- function webhookUrl(id: string): string {
18
- if (typeof window === 'undefined') return `/api/webhooks/${id}`
19
- return `${window.location.origin}/api/webhooks/${id}`
18
+ function webhookPath(id: string): string {
19
+ return `/api/webhooks/${id}`
20
20
  }
21
21
 
22
22
  function parseEvents(input: string): string[] {
@@ -24,7 +24,7 @@ function parseEvents(input: string): string[] {
24
24
  .split(/[\n,]+/)
25
25
  .map((v) => v.trim())
26
26
  .filter(Boolean)
27
- return Array.from(new Set(values))
27
+ return dedup(values)
28
28
  }
29
29
 
30
30
  function makeSecret(length = 28): string {
@@ -66,7 +66,7 @@ export function WebhookSheet() {
66
66
  const [deleting, setDeleting] = useState(false)
67
67
 
68
68
  const editing = editingId ? (webhooks[editingId] as Webhook | undefined) : null
69
- const endpoint = editing ? webhookUrl(editing.id) : ''
69
+ const endpoint = editing ? webhookPath(editing.id) : ''
70
70
  const eligibleAgents = useMemo(
71
71
  () => Object.values(agents),
72
72
  [agents]
@@ -264,7 +264,7 @@ export function WebhookSheet() {
264
264
  className={`${inputClass} font-mono text-[12px]`}
265
265
  />
266
266
  <button
267
- onClick={() => copyText('endpoint', endpoint)}
267
+ onClick={() => copyText('endpoint', `${window.location.origin}${endpoint}`)}
268
268
  className="px-3.5 py-2 rounded-[10px] border border-accent-bright/20 bg-accent-soft/40 text-accent-bright text-[12px] font-600 cursor-pointer hover:bg-accent-soft transition-colors"
269
269
  style={{ fontFamily: 'inherit' }}
270
270
  >
@@ -0,0 +1,202 @@
1
+ import { useEffect, useState, useCallback } from 'react'
2
+ import { useAppStore } from '@/stores/use-app-store'
3
+ import { getStoredAccessKey, clearStoredAccessKey, api } from '@/lib/api-client'
4
+ import { safeStorageGet, safeStorageSet } from '@/lib/safe-storage'
5
+ import { connectWs, disconnectWs } from '@/lib/ws-client'
6
+ import { fetchWithTimeout, isAbortError, isTimeoutError } from '@/lib/fetch-timeout'
7
+ import { isDevelopmentLikeRuntime } from '@/lib/runtime-env'
8
+ import { useWs } from '@/hooks/use-ws'
9
+ import { useMountedRef } from '@/hooks/use-mounted-ref'
10
+ import type { Agent } from '@/types'
11
+
12
+ const AUTH_CHECK_TIMEOUT_MS = isDevelopmentLikeRuntime() ? 20_000 : 8_000
13
+ const POST_AUTH_BOOTSTRAP_TIMEOUT_MS = isDevelopmentLikeRuntime() ? 20_000 : 8_000
14
+
15
+ function isExpectedAuthProbeError(err: unknown): boolean {
16
+ return isAbortError(err) || isTimeoutError(err)
17
+ }
18
+
19
+ export function useAppBootstrap() {
20
+ const currentUser = useAppStore((s) => s.currentUser)
21
+ const setUser = useAppStore((s) => s.setUser)
22
+ const hydrated = useAppStore((s) => s._hydrated)
23
+ const hydrate = useAppStore((s) => s.hydrate)
24
+ const loadNetworkInfo = useAppStore((s) => s.loadNetworkInfo)
25
+ const loadSessions = useAppStore((s) => s.loadSessions)
26
+ const loadSettings = useAppStore((s) => s.loadSettings)
27
+
28
+ const [authChecked, setAuthChecked] = useState(false)
29
+ const [authenticated, setAuthenticated] = useState(false)
30
+ const [setupDone, setSetupDone] = useState<boolean | null>(null)
31
+ const [agentReady, setAgentReady] = useState(false)
32
+ const mountedRef = useMountedRef()
33
+
34
+ const checkAuth = useCallback(async () => {
35
+ try {
36
+ const res = await fetchWithTimeout('/api/auth', {}, AUTH_CHECK_TIMEOUT_MS)
37
+ const data = await res.json().catch((err) => {
38
+ console.warn('Failed to parse /api/auth JSON:', err)
39
+ return {}
40
+ })
41
+ if (data?.authenticated === true) {
42
+ if (!mountedRef.current) return
43
+ setAuthenticated(true)
44
+ setAuthChecked(true)
45
+ return
46
+ }
47
+ } catch (err) {
48
+ if (!isExpectedAuthProbeError(err)) {
49
+ console.warn('Auth check probe failed, falling back to stored key:', err)
50
+ }
51
+ }
52
+
53
+ const key = getStoredAccessKey()
54
+ if (!key) {
55
+ if (!mountedRef.current) return
56
+ setAuthenticated(false)
57
+ setAuthChecked(true)
58
+ return
59
+ }
60
+
61
+ try {
62
+ const res = await fetchWithTimeout('/api/auth', {
63
+ method: 'POST',
64
+ headers: { 'Content-Type': 'application/json' },
65
+ body: JSON.stringify({ key }),
66
+ }, AUTH_CHECK_TIMEOUT_MS)
67
+ if (res.ok) {
68
+ if (!mountedRef.current) return
69
+ setAuthenticated(true)
70
+ } else {
71
+ clearStoredAccessKey()
72
+ if (!mountedRef.current) return
73
+ setAuthenticated(false)
74
+ }
75
+ } catch (err) {
76
+ if (!isExpectedAuthProbeError(err)) {
77
+ console.warn('Stored key auth check failed:', err)
78
+ }
79
+ clearStoredAccessKey()
80
+ if (!mountedRef.current) return
81
+ setAuthenticated(false)
82
+ } finally {
83
+ if (!mountedRef.current) return
84
+ setAuthChecked(true)
85
+ }
86
+ }, [mountedRef])
87
+
88
+ const syncUserFromServer = useCallback(async () => {
89
+ if (currentUser) return
90
+ try {
91
+ const settings = await api<{ userName?: string }>('GET', '/settings', undefined, {
92
+ timeoutMs: POST_AUTH_BOOTSTRAP_TIMEOUT_MS,
93
+ retries: 0,
94
+ })
95
+ if (settings.userName) {
96
+ setUser(settings.userName)
97
+ }
98
+ } catch (err) {
99
+ console.warn('Failed to sync user from server:', err)
100
+ }
101
+ }, [currentUser, setUser])
102
+
103
+ useEffect(() => {
104
+ hydrate()
105
+ }, [hydrate])
106
+
107
+ useEffect(() => {
108
+ if (!hydrated) return
109
+ if (safeStorageGet('sc_setup_done') === '1') {
110
+ setSetupDone((current) => current ?? true)
111
+ }
112
+ }, [hydrated])
113
+
114
+ useEffect(() => {
115
+ if (hydrated) checkAuth()
116
+ }, [hydrated, checkAuth])
117
+
118
+ useEffect(() => {
119
+ if (!authenticated) return
120
+ connectWs()
121
+ syncUserFromServer()
122
+ loadNetworkInfo()
123
+ loadSettings()
124
+ loadSessions()
125
+ return () => { disconnectWs() }
126
+ }, [authenticated, loadNetworkInfo, loadSessions, loadSettings, syncUserFromServer])
127
+
128
+ useWs('sessions', loadSessions, 15000)
129
+
130
+ useEffect(() => {
131
+ if (!authenticated || !currentUser) return
132
+ let cancelled = false
133
+ ;(async () => {
134
+ try {
135
+ const agents = await api<Record<string, Agent>>('GET', '/agents', undefined, {
136
+ timeoutMs: POST_AUTH_BOOTSTRAP_TIMEOUT_MS,
137
+ retries: 0,
138
+ })
139
+ if (cancelled) return
140
+ useAppStore.setState({ agents })
141
+
142
+ const { currentAgentId, appSettings } = useAppStore.getState()
143
+ const targetId = (currentAgentId && agents[currentAgentId])
144
+ ? currentAgentId
145
+ : (appSettings.defaultAgentId && agents[appSettings.defaultAgentId])
146
+ ? appSettings.defaultAgentId
147
+ : Object.values(agents)[0]?.id || null
148
+
149
+ if (targetId) {
150
+ await useAppStore.getState().setCurrentAgent(targetId)
151
+ }
152
+ } catch (err) {
153
+ console.warn('Failed to initialize agents:', err)
154
+ }
155
+ if (!cancelled && mountedRef.current) setAgentReady(true)
156
+ })()
157
+ return () => { cancelled = true }
158
+ }, [authenticated, currentUser, mountedRef])
159
+
160
+ useEffect(() => {
161
+ if (!authenticated || !currentUser) return
162
+ let cancelled = false
163
+ ;(async () => {
164
+ try {
165
+ const [settingsResult, credsResult] = await Promise.allSettled([
166
+ api<{ setupCompleted?: boolean }>('GET', '/settings', undefined, {
167
+ timeoutMs: POST_AUTH_BOOTSTRAP_TIMEOUT_MS,
168
+ retries: 0,
169
+ }),
170
+ api<Record<string, unknown>>('GET', '/credentials', undefined, {
171
+ timeoutMs: POST_AUTH_BOOTSTRAP_TIMEOUT_MS,
172
+ retries: 0,
173
+ }),
174
+ ])
175
+ if (cancelled) return
176
+ const settings = settingsResult.status === 'fulfilled' ? settingsResult.value : {}
177
+ const creds = credsResult.status === 'fulfilled' ? credsResult.value : {}
178
+ const bothFailed = settingsResult.status === 'rejected' && credsResult.status === 'rejected'
179
+ const hasCreds = Object.keys(creds).length > 0
180
+ const done = bothFailed ? true : settings.setupCompleted === true || hasCreds
181
+ if (done) safeStorageSet('sc_setup_done', '1')
182
+ if (!mountedRef.current) return
183
+ setSetupDone(done)
184
+ } catch (err) {
185
+ console.warn('Failed to check setup state:', err)
186
+ if (!cancelled && mountedRef.current) setSetupDone(true)
187
+ }
188
+ })()
189
+ return () => { cancelled = true }
190
+ }, [authenticated, currentUser, mountedRef])
191
+
192
+ return {
193
+ hydrated,
194
+ authChecked,
195
+ authenticated,
196
+ setAuthenticated,
197
+ currentUser,
198
+ setupDone,
199
+ setSetupDone,
200
+ agentReady
201
+ }
202
+ }
@@ -0,0 +1,14 @@
1
+ import { useEffect, useRef } from 'react'
2
+
3
+ export function useMountedRef() {
4
+ const mountedRef = useRef(true)
5
+
6
+ useEffect(() => {
7
+ mountedRef.current = true
8
+ return () => {
9
+ mountedRef.current = false
10
+ }
11
+ }, [])
12
+
13
+ return mountedRef
14
+ }
@@ -0,0 +1,31 @@
1
+ import { useEffect, useState } from 'react'
2
+
3
+ interface UseNowOptions {
4
+ enabled?: boolean
5
+ intervalMs?: number
6
+ }
7
+
8
+ export function useNow(options: UseNowOptions = {}): number | null {
9
+ const { enabled = true, intervalMs = 60_000 } = options
10
+ const [now, setNow] = useState<number | null>(null)
11
+
12
+ useEffect(() => {
13
+ const frame = window.requestAnimationFrame(() => {
14
+ setNow(Date.now())
15
+ })
16
+ if (!enabled || intervalMs <= 0) {
17
+ return () => window.cancelAnimationFrame(frame)
18
+ }
19
+
20
+ const timer = window.setInterval(() => {
21
+ setNow(Date.now())
22
+ }, intervalMs)
23
+
24
+ return () => {
25
+ window.cancelAnimationFrame(frame)
26
+ window.clearInterval(timer)
27
+ }
28
+ }, [enabled, intervalMs])
29
+
30
+ return now
31
+ }
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useCallback, useEffect, useRef, useState } from 'react'
4
4
  import { api } from '@/lib/api-client'
5
+ import { errorMessage } from '@/lib/shared-utils'
5
6
  import { useWs } from './use-ws'
6
7
 
7
8
  /** Call an OpenClaw gateway RPC method via the proxy route. */
@@ -27,7 +28,7 @@ export function useOpenClawRpc<T = unknown>(method: string | null, params?: unkn
27
28
  setData(res.result)
28
29
  }
29
30
  } catch (err: unknown) {
30
- setError(err instanceof Error ? err.message : String(err))
31
+ setError(errorMessage(err))
31
32
  } finally {
32
33
  setLoading(false)
33
34
  }
@@ -1,16 +1,26 @@
1
+ import { hmrSingleton } from '@/lib/shared-utils'
2
+
1
3
  export async function register() {
2
4
  if (process.env.NEXT_RUNTIME === 'nodejs') {
5
+ const isWorkerOnly = process.env.SWARMCLAW_WORKER_ONLY === '1'
3
6
  const { initWsServer, closeWsServer } = await import('./lib/server/ws-hub')
4
7
  const { ensureDaemonStarted } = await import('./lib/server/daemon-state')
5
- ensureDaemonStarted('instrumentation')
6
-
7
- initWsServer()
8
+
9
+ // In worker-only mode, we FORCE the daemon to start, but skip the WebSocket listener
10
+ if (isWorkerOnly) {
11
+ console.log('[instrumentation] Booting in WORKER ONLY mode')
12
+ ensureDaemonStarted('worker-boot')
13
+ } else {
14
+ // In normal mode, we start the WS server, and conditionally start the daemon if autostart allows
15
+ initWsServer()
16
+ ensureDaemonStarted('instrumentation')
17
+ }
8
18
 
9
19
  // Graceful shutdown: stop background services and close WS connections
10
- const shutdownState = (
11
- (globalThis as Record<string, unknown>).__swarmclaw_shutdown_state__
12
- ??= { registered: false, shuttingDown: false }
13
- ) as { registered: boolean; shuttingDown: boolean }
20
+ const shutdownState = hmrSingleton('__swarmclaw_shutdown_state__', () => ({
21
+ registered: false,
22
+ shuttingDown: false,
23
+ }))
14
24
 
15
25
  const shutdown = async (signal: string) => {
16
26
  if (shutdownState.shuttingDown) return
@@ -22,7 +32,9 @@ export async function register() {
22
32
  } catch (err) {
23
33
  console.error('[instrumentation] Failed to stop daemon during shutdown:', err)
24
34
  }
25
- await closeWsServer()
35
+ if (!isWorkerOnly) {
36
+ await closeWsServer()
37
+ }
26
38
  process.exit(0)
27
39
  }
28
40
  if (!shutdownState.registered) {