@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
@@ -4,7 +4,8 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } fr
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
5
  import { useChatStore } from '@/stores/use-chat-store'
6
6
  import { useChatroomStore } from '@/stores/use-chatroom-store'
7
- import { fetchMessages } from '@/lib/chats'
7
+ import { useNow } from '@/hooks/use-now'
8
+ import { useMountedRef } from '@/hooks/use-mounted-ref'
8
9
  import { api } from '@/lib/api-client'
9
10
  import { ConfirmDialog } from '@/components/shared/confirm-dialog'
10
11
  import type { Agent, Session } from '@/types'
@@ -17,12 +18,13 @@ interface Props {
17
18
  }
18
19
 
19
20
  export function AgentChatList({ inSidebar, onSelect }: Props) {
21
+ const mountedRef = useMountedRef()
22
+ const now = useNow()
20
23
  const agents = useAppStore((s) => s.agents)
21
24
  const sessions = useAppStore((s) => s.sessions)
22
25
  const loadAgents = useAppStore((s) => s.loadAgents)
23
26
  const currentAgentId = useAppStore((s) => s.currentAgentId)
24
27
  const setCurrentAgent = useAppStore((s) => s.setCurrentAgent)
25
- const setMessages = useChatStore((s) => s.setMessages)
26
28
  const setAgentSheetOpen = useAppStore((s) => s.setAgentSheetOpen)
27
29
  const tasks = useAppStore((s) => s.tasks)
28
30
  const togglePinAgent = useAppStore((s) => s.togglePinAgent)
@@ -60,17 +62,19 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
60
62
  try {
61
63
  await api('DELETE', '/chats', { ids: sessionIds })
62
64
  await loadSessions()
65
+ if (!mountedRef.current) return
63
66
  toast.success(`Deleted ${sessionIds.length} chat(s)`)
64
67
  setBulkMode(false)
65
68
  setSelectedIds(new Set())
66
69
  } catch {
67
70
  toast.error('Failed to delete chats')
68
71
  }
69
- }, [selectedIds, agents, loadSessions])
72
+ }, [selectedIds, agents, loadSessions, mountedRef])
70
73
 
71
74
  // FLIP animation refs
72
75
  const rowRefs = useRef<Map<string, HTMLElement>>(new Map())
73
76
  const previousTopRef = useRef<Map<string, number>>(new Map())
77
+ const scrollTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
74
78
 
75
79
  const setRowRef = useCallback((id: string, el: HTMLElement | null) => {
76
80
  if (el) rowRefs.current.set(id, el)
@@ -79,6 +83,14 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
79
83
 
80
84
  useEffect(() => { loadAgents() }, [loadAgents])
81
85
 
86
+ useEffect(() => {
87
+ return () => {
88
+ if (scrollTimerRef.current) {
89
+ clearTimeout(scrollTimerRef.current)
90
+ }
91
+ }
92
+ }, [])
93
+
82
94
  // Build agent list sorted by last activity in their thread session
83
95
  const sortedAgents = useMemo(() => {
84
96
  return Object.values(agents)
@@ -98,7 +110,7 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
98
110
  // Compute agents active in chatrooms (message in last 30min or currently streaming)
99
111
  const chatroomActiveAgentIds = useMemo(() => {
100
112
  const set = new Set<string>()
101
- const cutoff = Date.now() - 30 * 60 * 1000
113
+ const cutoff = now ? now - 30 * 60 * 1000 : Number.POSITIVE_INFINITY
102
114
  for (const chatroom of Object.values(chatrooms)) {
103
115
  for (let i = chatroom.messages.length - 1; i >= 0; i--) {
104
116
  const msg = chatroom.messages[i]
@@ -108,7 +120,7 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
108
120
  }
109
121
  for (const agentId of chatroomStreaming.keys()) set.add(agentId)
110
122
  return set
111
- }, [chatrooms, chatroomStreaming])
123
+ }, [chatrooms, chatroomStreaming, now])
112
124
 
113
125
  // Compute running tasks per agent
114
126
  const runningAgentIds = useMemo(() => {
@@ -122,7 +134,7 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
122
134
  // Apply chatFilter
123
135
  const filteredAgents = useMemo(() => {
124
136
  if (chatFilter === 'all') return sortedAgents
125
- const now = Date.now()
137
+ if (!now) return sortedAgents
126
138
  return sortedAgents.filter((a) => {
127
139
  const threadSession = a.threadSessionId ? sessions[a.threadSessionId] as Session | undefined : undefined
128
140
  const isRunning = runningAgentIds.has(a.id) || (threadSession?.active ?? false)
@@ -133,7 +145,7 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
133
145
  const lastActive = threadSession?.lastActiveAt || a.updatedAt
134
146
  return now - lastActive < 86_400_000
135
147
  })
136
- }, [sortedAgents, chatFilter, sessions, runningAgentIds, streamingSessionId, chatroomActiveAgentIds])
148
+ }, [sortedAgents, chatFilter, sessions, runningAgentIds, streamingSessionId, chatroomActiveAgentIds, now])
137
149
 
138
150
  const defaultAgent = useMemo(() => {
139
151
  const id = appSettings.defaultAgentId
@@ -172,20 +184,13 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
172
184
  return
173
185
  }
174
186
  await setCurrentAgent(agent.id)
175
- // Load messages for the thread
176
- const state = useAppStore.getState()
177
- if (state.currentSessionId) {
178
- try {
179
- const msgs = await fetchMessages(state.currentSessionId)
180
- setMessages(msgs)
181
- } catch (err: unknown) {
182
- console.error('[agent-chat-list] Failed to load messages:', err instanceof Error ? err.message : String(err))
183
- }
184
- }
185
187
  onSelect?.()
186
188
  // Delay scroll so React renders the new messages first
187
- if (typeof window !== 'undefined') {
188
- setTimeout(() => {
189
+ if (mountedRef.current && typeof window !== 'undefined') {
190
+ if (scrollTimerRef.current) {
191
+ clearTimeout(scrollTimerRef.current)
192
+ }
193
+ scrollTimerRef.current = setTimeout(() => {
189
194
  window.dispatchEvent(new CustomEvent('swarmclaw:scroll-bottom'))
190
195
  }, 100)
191
196
  }
@@ -218,7 +223,7 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
218
223
  }
219
224
 
220
225
  return (
221
- <div className="flex-1 overflow-y-auto">
226
+ <div className="flex-1 overflow-y-auto" data-testid="agent-chat-list">
222
227
  {/* Filter control + bulk mode toggle */}
223
228
  {sortedAgents.length > 2 && (
224
229
  <div className="flex items-center gap-1 px-4 pt-2.5 pb-1">
@@ -266,6 +271,8 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
266
271
  value={search}
267
272
  onChange={(e) => setSearch(e.target.value)}
268
273
  placeholder="Search agents..."
274
+ aria-label="Search agents"
275
+ data-testid="agent-search"
269
276
  className="w-full px-4 py-2.5 rounded-[12px] border border-white/[0.04] bg-surface text-text
270
277
  text-[13px] outline-none transition-all duration-200 placeholder:text-text-3/70 focus-glow"
271
278
  style={{ fontFamily: 'inherit' }}
@@ -277,7 +284,7 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
277
284
  const threadSession = defaultAgent.threadSessionId ? sessions[defaultAgent.threadSessionId] as Session | undefined : undefined
278
285
  const lastMsg = threadSession?.messages?.at(-1)
279
286
  const heartbeatOn = defaultAgent.heartbeatEnabled === true && (defaultAgent.plugins?.length ?? 0) > 0
280
- const recentlyActive = (threadSession?.lastActiveAt ?? 0) > Date.now() - 30 * 60 * 1000
287
+ const recentlyActive = !!now && (threadSession?.lastActiveAt ?? 0) > now - 30 * 60 * 1000
281
288
  const isDisabled = defaultAgent.disabled === true
282
289
  const isWorking = !isDisabled && (runningAgentIds.has(defaultAgent.id) || (threadSession?.active ?? false) || heartbeatOn || recentlyActive || chatroomActiveAgentIds.has(defaultAgent.id))
283
290
  const isTyping = streamingSessionId === defaultAgent.threadSessionId
@@ -294,7 +301,20 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
294
301
  ${isActive
295
302
  ? 'bg-accent-soft border-accent-bright/25'
296
303
  : 'bg-accent-soft/40 border-accent-bright/15 hover:bg-accent-soft/55'}`}
304
+ role="button"
305
+ tabIndex={0}
306
+ data-testid="agent-row"
307
+ data-agent-id={defaultAgent.id}
308
+ data-agent-name={defaultAgent.name}
309
+ aria-label={`Open agent chat ${defaultAgent.name}`}
297
310
  onClick={() => bulkMode ? toggleSelected(defaultAgent.id) : handleSelect(defaultAgent)}
311
+ onKeyDown={(event) => {
312
+ if (event.key === 'Enter' || event.key === ' ') {
313
+ event.preventDefault()
314
+ if (bulkMode) toggleSelected(defaultAgent.id)
315
+ else void handleSelect(defaultAgent)
316
+ }
317
+ }}
298
318
  >
299
319
  <div className="flex items-center gap-3">
300
320
  {bulkMode && (
@@ -368,7 +388,7 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
368
388
  const lastMsg = threadSession?.messages?.at(-1)
369
389
  const isActive = currentAgentId === agent.id
370
390
  const heartbeatOn = agent.heartbeatEnabled === true && (agent.plugins?.length ?? 0) > 0
371
- const recentlyActive = (threadSession?.lastActiveAt ?? 0) > Date.now() - 30 * 60 * 1000
391
+ const recentlyActive = !!now && (threadSession?.lastActiveAt ?? 0) > now - 30 * 60 * 1000
372
392
  const isDisabled = agent.disabled === true
373
393
  const isWorking = !isDisabled && (runningAgentIds.has(agent.id) || (threadSession?.active ?? false) || heartbeatOn || recentlyActive || chatroomActiveAgentIds.has(agent.id))
374
394
  const isTyping = streamingSessionId === agent.threadSessionId
@@ -382,7 +402,20 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
382
402
  ${isActive
383
403
  ? 'bg-accent-soft/80 border border-accent-bright/20'
384
404
  : 'bg-transparent hover:bg-white/[0.02]'}`}
405
+ role="button"
406
+ tabIndex={0}
407
+ data-testid="agent-row"
408
+ data-agent-id={agent.id}
409
+ data-agent-name={agent.name}
410
+ aria-label={`Open agent chat ${agent.name}`}
385
411
  onClick={() => bulkMode ? toggleSelected(agent.id) : handleSelect(agent)}
412
+ onKeyDown={(event) => {
413
+ if (event.key === 'Enter' || event.key === ' ') {
414
+ event.preventDefault()
415
+ if (bulkMode) toggleSelected(agent.id)
416
+ else void handleSelect(agent)
417
+ }
418
+ }}
386
419
  >
387
420
  <div className="flex items-center gap-2.5">
388
421
  {bulkMode && (
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useCallback, useEffect, useState } from 'react'
4
4
  import { api } from '@/lib/api-client'
5
+ import { errorMessage } from '@/lib/shared-utils'
5
6
  import { PersonalityBuilder } from './personality-builder'
6
7
 
7
8
  const FILES = ['SOUL.md', 'IDENTITY.md', 'USER.md', 'TOOLS.md', 'HEARTBEAT.md', 'MEMORY.md', 'AGENTS.md'] as const
@@ -47,7 +48,7 @@ export function AgentFilesEditor({ agentId }: Props) {
47
48
  return next
48
49
  })
49
50
  } catch (err: unknown) {
50
- const message = err instanceof Error ? err.message : String(err)
51
+ const message = errorMessage(err)
51
52
  setFiles((prev) => {
52
53
  const next = { ...prev }
53
54
  for (const f of FILES) {
@@ -83,7 +84,7 @@ export function AgentFilesEditor({ agentId }: Props) {
83
84
  [filename]: { ...prev[filename], saving: false, original: prev[filename].content },
84
85
  }))
85
86
  } catch (err: unknown) {
86
- const message = err instanceof Error ? err.message : String(err)
87
+ const message = errorMessage(err)
87
88
  setFiles((prev) => ({
88
89
  ...prev,
89
90
  [filename]: { ...prev[filename], saving: false, error: message },
@@ -4,6 +4,8 @@ import { useCallback, useEffect, useRef, useState } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
5
  import { createAgent, updateAgent, deleteAgent } from '@/lib/agents'
6
6
  import { api } from '@/lib/api-client'
7
+ import { fetchProviderModelDiscovery } from '@/lib/provider-model-discovery-client'
8
+ import { sleep } from '@/lib/shared-utils'
7
9
  import { BottomSheet } from '@/components/shared/bottom-sheet'
8
10
  import { toast } from 'sonner'
9
11
  import { ModelCombobox } from '@/components/shared/model-combobox'
@@ -19,9 +21,23 @@ import { SectionLabel } from '@/components/shared/section-label'
19
21
  import { SoulLibraryPicker } from './soul-library-picker'
20
22
  import { HintTip } from '@/components/shared/hint-tip'
21
23
  import { isOllamaCloudModel } from '@/lib/ollama-model'
24
+ import { errorMessage } from '@/lib/shared-utils'
25
+ import { getDefaultAgentPluginIds } from '@/lib/agent-default-tools'
22
26
 
23
27
  const HB_PRESETS = [1800, 3600, 7200, 21600, 43200] as const
24
28
  const FALLBACK_ELEVENLABS_VOICE_ID = 'JBFqnCBsd6RMkjVDRZzb'
29
+ const AUTO_SYNC_MODEL_PROVIDER_IDS = new Set<ProviderType>([
30
+ 'openai',
31
+ 'anthropic',
32
+ 'google',
33
+ 'deepseek',
34
+ 'groq',
35
+ 'together',
36
+ 'mistral',
37
+ 'xai',
38
+ 'fireworks',
39
+ 'ollama',
40
+ ])
25
41
 
26
42
  type AgentSheetSectionId = 'overview' | 'instructions' | 'model' | 'tools'
27
43
  type SafeAgentWallet = Omit<AgentWallet, 'encryptedPrivateKey'> & {
@@ -137,6 +153,12 @@ export function AgentSheet() {
137
153
  const setEditingId = useAppStore((s) => s.setEditingAgentId)
138
154
  const agents = useAppStore((s) => s.agents)
139
155
  const loadAgents = useAppStore((s) => s.loadAgents)
156
+ const currentSessionId = useAppStore((s) => s.currentSessionId)
157
+ const currentSession = useAppStore((s) => {
158
+ const id = s.currentSessionId
159
+ return id ? s.sessions[id] : null
160
+ })
161
+ const refreshSession = useAppStore((s) => s.refreshSession)
140
162
  const projects = useAppStore((s) => s.projects)
141
163
  const loadProjects = useAppStore((s) => s.loadProjects)
142
164
  const providers = useAppStore((s) => s.providers)
@@ -238,6 +260,7 @@ export function AgentSheet() {
238
260
  const [soulLibraryOpen, setSoulLibraryOpen] = useState(false)
239
261
  const promptFileRef = useRef<HTMLInputElement>(null)
240
262
  const importFileRef = useRef<HTMLInputElement>(null)
263
+ const lastAutoSyncedModelsKeyRef = useRef<string | null>(null)
241
264
  const sectionRefs = useRef<Record<AgentSheetSectionId, HTMLDivElement | null>>({
242
265
  overview: null,
243
266
  instructions: null,
@@ -288,12 +311,62 @@ export function AgentSheet() {
288
311
  : 'Built-in fallback'
289
312
  const providerSummary = openclawEnabled ? 'OpenClaw gateway' : (currentProvider?.name || provider)
290
313
  const modelSummary = openclawEnabled ? (gatewayProfileId ? 'Gateway-managed' : 'default') : (model || 'Select a model')
314
+ const syncLiveProviderModels = useCallback(async (
315
+ providerId: ProviderType,
316
+ nextCredentialId: string | null,
317
+ nextEndpoint: string | null,
318
+ force = false,
319
+ ): Promise<{ synced: boolean; models: string[] } | null> => {
320
+ if (openclawEnabled) return null
321
+ if (!AUTO_SYNC_MODEL_PROVIDER_IDS.has(providerId)) return null
322
+ const providerInfo = providers.find((item) => item.id === providerId)
323
+ if (!providerInfo?.supportsModelDiscovery) return null
324
+
325
+ const result = await fetchProviderModelDiscovery({
326
+ providerId,
327
+ credentialId: nextCredentialId,
328
+ endpoint: nextEndpoint,
329
+ force,
330
+ })
331
+
332
+ if (!result.ok || result.models.length === 0) return { synced: false, models: result.models }
333
+
334
+ const sameModels = providerInfo.models.length === result.models.length
335
+ && providerInfo.models.every((item, index) => item === result.models[index])
336
+
337
+ if (!sameModels) {
338
+ await api('PUT', `/providers/${providerId}/models`, { models: result.models })
339
+ await loadProviders()
340
+ }
341
+
342
+ setModel((currentModel) => currentModel.trim() || result.models[0] || '')
343
+ return { synced: !sameModels, models: result.models }
344
+ }, [loadProviders, openclawEnabled, providers])
291
345
 
292
346
  const providerNeedsKey = !editing && (
293
347
  (currentProvider?.requiresApiKey && providerCredentials.length === 0 && !addingKey) ||
294
348
  (provider === 'ollama' && ollamaMode === 'cloud' && providerCredentials.length === 0 && !addingKey)
295
349
  )
296
350
 
351
+ useEffect(() => {
352
+ if (!open) {
353
+ lastAutoSyncedModelsKeyRef.current = null
354
+ return
355
+ }
356
+ if (openclawEnabled) return
357
+ if (!AUTO_SYNC_MODEL_PROVIDER_IDS.has(provider)) return
358
+ if (!currentProvider?.supportsModelDiscovery) return
359
+
360
+ const requiresCredential = currentProvider.requiresApiKey || (provider === 'ollama' && ollamaMode === 'cloud')
361
+ if (requiresCredential && !credentialId) return
362
+
363
+ const syncKey = `${provider}::${credentialId || ''}::${apiEndpoint?.trim() || ''}`
364
+ if (lastAutoSyncedModelsKeyRef.current === syncKey) return
365
+ lastAutoSyncedModelsKeyRef.current = syncKey
366
+
367
+ void syncLiveProviderModels(provider, credentialId, apiEndpoint, false).catch(() => {})
368
+ }, [apiEndpoint, credentialId, currentProvider, ollamaMode, open, openclawEnabled, provider, syncLiveProviderModels])
369
+
297
370
  useEffect(() => {
298
371
  if (open) {
299
372
  loadSettings()
@@ -390,7 +463,7 @@ export function AgentSheet() {
390
463
  setRoutingTargets([])
391
464
  setPlatformAssignScope('self')
392
465
  setAgentAgentIds([])
393
- setTools([])
466
+ setTools(getDefaultAgentPluginIds())
394
467
  setSkills([])
395
468
  setSkillIds([])
396
469
  setMcpDisabledTools([])
@@ -580,7 +653,7 @@ export function AgentSheet() {
580
653
  priority: typeof target.priority === 'number' ? target.priority : index + 1,
581
654
  })),
582
655
  subAgentIds: canDelegateToAgents ? subAgentIds : [],
583
- tools,
656
+ plugins: tools,
584
657
  skills,
585
658
  skillIds,
586
659
  mcpServerIds,
@@ -621,6 +694,17 @@ export function AgentSheet() {
621
694
  toast.success('Agent created')
622
695
  }
623
696
  await loadAgents()
697
+ if (
698
+ editing
699
+ && currentSessionId
700
+ && currentSession?.agentId === editing.id
701
+ && (
702
+ currentSession.shortcutForAgentId === editing.id
703
+ || currentSessionId === editing.threadSessionId
704
+ )
705
+ ) {
706
+ await refreshSession(currentSessionId)
707
+ }
624
708
  setSoulInitial(soul)
625
709
  setSoulSaveState('saved')
626
710
  setTimeout(() => setSoulSaveState('idle'), 1500)
@@ -657,8 +741,8 @@ export function AgentSheet() {
657
741
  gatewayProfileId: editing.gatewayProfileId || null,
658
742
  routingStrategy: editing.routingStrategy || null,
659
743
  routingTargets: editing.routingTargets || [],
660
- tools: editing.tools,
661
- plugins: editing.plugins,
744
+ tools: editing.plugins || editing.tools || [],
745
+ plugins: editing.plugins || editing.tools || [],
662
746
  capabilities: editing.capabilities,
663
747
  elevenLabsVoiceId: editing.elevenLabsVoiceId || null,
664
748
  soul: editing.soul,
@@ -714,8 +798,19 @@ export function AgentSheet() {
714
798
  })
715
799
  if (result.deviceId) setTestDeviceId(result.deviceId)
716
800
  if (result.ok) {
801
+ let syncedModels: string[] = []
802
+ try {
803
+ const synced = await syncLiveProviderModels(provider, credentialId, apiEndpoint, true)
804
+ syncedModels = synced?.models || []
805
+ } catch {
806
+ // Best-effort: a passing connection test should still pass if model sync fails.
807
+ }
717
808
  setTestStatus('pass')
718
- setTestMessage(result.message)
809
+ setTestMessage(
810
+ syncedModels.length > 0
811
+ ? `${result.message} Synced ${syncedModels.length} live model${syncedModels.length === 1 ? '' : 's'} into the model picker.`
812
+ : result.message,
813
+ )
719
814
  return true
720
815
  } else {
721
816
  setTestStatus('fail')
@@ -745,7 +840,7 @@ export function AgentSheet() {
745
840
  if (!passed) return
746
841
  if (!openclawEnabled) {
747
842
  // Brief pause so the user can see the success state on the button
748
- await new Promise((r) => setTimeout(r, 1500))
843
+ await sleep(1500)
749
844
  }
750
845
  }
751
846
  setSaving(true)
@@ -767,8 +862,8 @@ export function AgentSheet() {
767
862
  return (
768
863
  <>
769
864
  <BottomSheet open={open} onClose={onClose} wide>
770
- <div className="mb-10 flex items-start justify-between">
771
- <div>
865
+ <div className="mb-10 flex items-start justify-between gap-6 pr-14 sm:pr-20">
866
+ <div className="min-w-0">
772
867
  <div className="mb-2 flex flex-wrap items-center gap-2">
773
868
  <h2 className="font-display text-[28px] font-700 tracking-[-0.03em]">
774
869
  {editing ? 'Edit Agent' : 'New Agent'}
@@ -783,7 +878,7 @@ export function AgentSheet() {
783
878
  </div>
784
879
  <p className="text-[14px] text-text-3">Define an AI agent and optional multi-agent delegation behavior</p>
785
880
  </div>
786
- <div className="flex items-center gap-3 mt-1.5">
881
+ <div className="mt-1.5 flex shrink-0 items-center gap-3">
787
882
  <label className="text-[11px] font-600 text-text-3 uppercase tracking-[0.08em]">OpenClaw</label>
788
883
  <button
789
884
  type="button"
@@ -1627,7 +1722,7 @@ export function AgentSheet() {
1627
1722
  setAddingKey(false)
1628
1723
  setNewKeyName('')
1629
1724
  setNewKeyValue('')
1630
- } catch (err: unknown) { toast.error(`Failed to save: ${err instanceof Error ? err.message : String(err)}`) }
1725
+ } catch (err: unknown) { toast.error(`Failed to save: ${errorMessage(err)}`) }
1631
1726
  finally { setSavingKey(false) }
1632
1727
  }}
1633
1728
  className="px-4 py-1.5 rounded-[8px] bg-accent-bright text-white text-[12px] font-600 cursor-pointer border-none hover:brightness-110 transition-all disabled:opacity-40"
@@ -1877,18 +1972,24 @@ export function AgentSheet() {
1877
1972
  <button
1878
1973
  type="button"
1879
1974
  disabled={savingKey || !newKeyValue.trim()}
1880
- onClick={async () => {
1881
- setSavingKey(true)
1882
- try {
1883
- const cred = await api<{ id: string }>('POST', '/credentials', { provider, name: newKeyName.trim() || `${provider} key`, apiKey: newKeyValue.trim() })
1884
- await loadCredentials()
1885
- setCredentialId(cred.id)
1886
- setAddingKey(false)
1887
- setNewKeyName('')
1888
- setNewKeyValue('')
1889
- } catch (err: unknown) { toast.error(`Failed to save: ${err instanceof Error ? err.message : String(err)}`) }
1890
- finally { setSavingKey(false) }
1891
- }}
1975
+ onClick={async () => {
1976
+ setSavingKey(true)
1977
+ try {
1978
+ const cred = await api<{ id: string }>('POST', '/credentials', { provider, name: newKeyName.trim() || `${provider} key`, apiKey: newKeyValue.trim() })
1979
+ await loadCredentials()
1980
+ setCredentialId(cred.id)
1981
+ const synced = await syncLiveProviderModels(provider, cred.id, apiEndpoint, true).catch(() => null)
1982
+ setAddingKey(false)
1983
+ setNewKeyName('')
1984
+ setNewKeyValue('')
1985
+ if (synced?.models.length) {
1986
+ toast.success(`Key saved. Synced ${synced.models.length} model${synced.models.length === 1 ? '' : 's'}.`)
1987
+ } else {
1988
+ toast.success('Key saved')
1989
+ }
1990
+ } catch (err: unknown) { toast.error(`Failed to save: ${errorMessage(err)}`) }
1991
+ finally { setSavingKey(false) }
1992
+ }}
1892
1993
  className="px-4 py-1.5 rounded-[8px] bg-accent-bright text-white text-[12px] font-600 cursor-pointer border-none hover:brightness-110 transition-all disabled:opacity-40"
1893
1994
  style={{ fontFamily: 'inherit' }}
1894
1995
  >
@@ -65,7 +65,7 @@ export function InspectorPanel({ agent, onEditAgent, onClearHistory, onDeleteAge
65
65
  if (!visibleTabs.find((t) => t.id === inspectorTab)) {
66
66
  setInspectorTab('overview')
67
67
  }
68
- }, [isOpenClaw]) // eslint-disable-next-line react-hooks/exhaustive-deps
68
+ }, [isOpenClaw])
69
69
 
70
70
  // Close on Escape
71
71
  useEffect(() => {
@@ -5,6 +5,7 @@ import type { OpenClawSkillEntry, SkillAllowlistMode } from '@/types'
5
5
  import { api } from '@/lib/api-client'
6
6
  import { SkillInstallDialog } from './skill-install-dialog'
7
7
  import { ConfirmDialog } from '@/components/shared/confirm-dialog'
8
+ import { errorMessage } from '@/lib/shared-utils'
8
9
 
9
10
  interface Props {
10
11
  agentId: string
@@ -32,7 +33,7 @@ export function OpenClawSkillsPanel({ agentId, initialMode = 'all', initialAllow
32
33
  const result = await api<OpenClawSkillEntry[]>('GET', `/openclaw/skills?agentId=${agentId}`)
33
34
  setSkills(Array.isArray(result) ? result : [])
34
35
  } catch (err: unknown) {
35
- setError(err instanceof Error ? err.message : String(err))
36
+ setError(errorMessage(err))
36
37
  } finally {
37
38
  setLoading(false)
38
39
  }
@@ -12,7 +12,7 @@ export function TrashList() {
12
12
  const loadAgents = useAppStore((s) => s.loadAgents)
13
13
  const [confirmPermanent, setConfirmPermanent] = useState<Agent | null>(null)
14
14
 
15
- useEffect(() => { loadTrashedAgents() }, []) // eslint-disable-next-line react-hooks/exhaustive-deps
15
+ useEffect(() => { loadTrashedAgents() }, [])
16
16
 
17
17
  const handleRestore = async (id: string) => {
18
18
  await api('POST', '/agents/trash', { id })
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useState, useEffect } from 'react'
4
4
  import { setStoredAccessKey } from '@/lib/api-client'
5
- import { fetchWithTimeout } from '@/lib/fetch-timeout'
5
+ import { fetchWithTimeout, isAbortError, isTimeoutError } from '@/lib/fetch-timeout'
6
6
 
7
7
  interface AccessKeyGateProps {
8
8
  onAuthenticated: () => void
@@ -10,6 +10,10 @@ interface AccessKeyGateProps {
10
10
 
11
11
  const AUTH_CHECK_TIMEOUT_MS = 8_000
12
12
 
13
+ function isExpectedAuthCheckError(err: unknown): boolean {
14
+ return isAbortError(err) || isTimeoutError(err)
15
+ }
16
+
13
17
  export function AccessKeyGate({ onAuthenticated }: AccessKeyGateProps) {
14
18
  const [key, setKey] = useState('')
15
19
  const [error, setError] = useState('')
@@ -29,7 +33,9 @@ export function AccessKeyGate({ onAuthenticated }: AccessKeyGateProps) {
29
33
  setFirstTime(true)
30
34
  }
31
35
  } catch (err) {
32
- console.error('Auth check failed:', err)
36
+ if (!isExpectedAuthCheckError(err)) {
37
+ console.error('Auth check failed:', err)
38
+ }
33
39
  } finally {
34
40
  if (!cancelled) setChecking(false)
35
41
  }
@@ -5,6 +5,7 @@ import { api } from '@/lib/api-client'
5
5
  import { OpenClawDeployPanel } from '@/components/openclaw/openclaw-deploy-panel'
6
6
  import { useAppStore } from '@/stores/use-app-store'
7
7
  import type { ProviderType, Credential, GatewayProfile } from '@/types'
8
+ import { dedup, errorMessage } from '@/lib/shared-utils'
8
9
  import {
9
10
  ONBOARDING_PATHS,
10
11
  SETUP_PROVIDERS,
@@ -499,12 +500,12 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
499
500
  ...(current || {}),
500
501
  ...(nextDeployment || {}),
501
502
  }))
502
- setProviderTags((current) => Array.from(new Set([
503
+ setProviderTags((current) => dedup([
503
504
  ...current,
504
505
  'onboarding',
505
506
  ...(nextDeployment?.useCase ? [nextDeployment.useCase] : []),
506
507
  ...(nextDeployment?.exposure ? [nextDeployment.exposure] : []),
507
- ])))
508
+ ]))
508
509
  }
509
510
  setCheckState('idle')
510
511
  setCheckMessage('')
@@ -544,7 +545,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
544
545
  return !!result.ok
545
546
  } catch (err: unknown) {
546
547
  setCheckState('error')
547
- setCheckMessage(err instanceof Error ? err.message : String(err))
548
+ setCheckMessage(errorMessage(err))
548
549
  setCheckErrorCode(null)
549
550
  return false
550
551
  }
@@ -560,7 +561,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
560
561
  } catch (err: unknown) {
561
562
  setDoctorState('error')
562
563
  setDoctorReport(null)
563
- setDoctorError(err instanceof Error ? err.message : String(err))
564
+ setDoctorError(errorMessage(err))
564
565
  }
565
566
  }
566
567
 
@@ -619,7 +620,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
619
620
  resetProviderForm()
620
621
  setStep('providers')
621
622
  } catch (err: unknown) {
622
- setError(err instanceof Error ? err.message : String(err))
623
+ setError(errorMessage(err))
623
624
  } finally {
624
625
  setSaving(false)
625
626
  }
@@ -698,10 +699,10 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
698
699
  name: configuredProvider.name,
699
700
  endpoint: normalizedEndpoint,
700
701
  credentialId: configuredProvider.credentialId || null,
701
- tags: Array.from(new Set([
702
+ tags: dedup([
702
703
  'onboarding',
703
704
  ...(configuredProvider.tags || []),
704
- ])),
705
+ ]),
705
706
  notes: configuredProvider.notes || `Created during setup for ${configuredProvider.name}.`,
706
707
  deployment: configuredProvider.deployment || null,
707
708
  status: configuredProvider.deployment?.lastVerifiedOk ? 'healthy' : 'pending',
@@ -765,7 +766,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
765
766
  setCreatedAgents(created)
766
767
  setStep('done')
767
768
  } catch (err: unknown) {
768
- setError(err instanceof Error ? err.message : String(err))
769
+ setError(errorMessage(err))
769
770
  } finally {
770
771
  setSaving(false)
771
772
  }
@@ -779,7 +780,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
779
780
  setCreatedAgents([])
780
781
  setStep('done')
781
782
  } catch (err: unknown) {
782
- setError(err instanceof Error ? err.message : String(err))
783
+ setError(errorMessage(err))
783
784
  } finally {
784
785
  setSaving(false)
785
786
  }
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { useState } from 'react'
3
+ import { useId, useState } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
5
  import { AgentAvatar } from '@/components/agents/agent-avatar'
6
6
  import { api } from '@/lib/api-client'
@@ -9,7 +9,8 @@ export function UserPicker() {
9
9
  const setUser = useAppStore((s) => s.setUser)
10
10
  const loadSettings = useAppStore((s) => s.loadSettings)
11
11
  const [name, setName] = useState('')
12
- const [avatarSeed, setAvatarSeed] = useState(() => Math.random().toString(36).slice(2, 10))
12
+ const defaultAvatarSeed = useId().replace(/:/g, '')
13
+ const [avatarSeed, setAvatarSeed] = useState(defaultAvatarSeed)
13
14
 
14
15
  const handleSubmit = async (e: React.FormEvent) => {
15
16
  e.preventDefault()