@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,161 @@
1
+ /**
2
+ * Lightweight performance tracing for critical server paths.
3
+ *
4
+ * Emits structured `[perf]` log lines with timing data so workbench tests
5
+ * can measure where time is spent during chat turns, tool calls, queue
6
+ * processing, and storage operations.
7
+ *
8
+ * Usage:
9
+ * const end = perf.start('chat-execution', 'streamAgentChat', { sessionId })
10
+ * // ... do work ...
11
+ * end({ toolCount: 3, tokens: 1200 })
12
+ *
13
+ * Output:
14
+ * [perf] chat-execution/streamAgentChat 1423ms {sessionId:"abc",toolCount:3,tokens:1200}
15
+ */
16
+
17
+ interface PerfEntry {
18
+ category: string
19
+ label: string
20
+ durationMs: number
21
+ meta?: Record<string, unknown>
22
+ }
23
+
24
+ type OnEntryCallback = (entry: PerfEntry) => void
25
+
26
+ // Disabled by default — workbench tests call perf.setEnabled(true) to activate.
27
+ // The SWARMCLAW_PERF env var also enables it for CLI-level benchmarking.
28
+ let _enabled = process.env.SWARMCLAW_PERF === '1'
29
+ let _onEntry: OnEntryCallback | null = null
30
+ const _recentEntries: PerfEntry[] = []
31
+ const MAX_RECENT = 200
32
+
33
+ function emitEntry(entry: PerfEntry): void {
34
+ _recentEntries.push(entry)
35
+ if (_recentEntries.length > MAX_RECENT) _recentEntries.shift()
36
+
37
+ if (_onEntry) {
38
+ try { _onEntry(entry) } catch { /* listener errors are non-critical */ }
39
+ }
40
+
41
+ const metaStr = entry.meta && Object.keys(entry.meta).length > 0
42
+ ? ' ' + JSON.stringify(entry.meta)
43
+ : ''
44
+ console.log(`[perf] ${entry.category}/${entry.label} ${entry.durationMs}ms${metaStr}`)
45
+ }
46
+
47
+ const _noopEnd = () => 0
48
+
49
+ /**
50
+ * Start a performance measurement. Returns a function that, when called,
51
+ * records the elapsed time and emits a log entry.
52
+ *
53
+ * When perf is disabled (default in production), returns a shared no-op —
54
+ * zero allocation overhead.
55
+ */
56
+ function start(
57
+ category: string,
58
+ label: string,
59
+ meta?: Record<string, unknown>,
60
+ ): (extraMeta?: Record<string, unknown>) => number {
61
+ if (!_enabled) return _noopEnd
62
+ const t0 = performance.now()
63
+ return (extraMeta?: Record<string, unknown>) => {
64
+ const durationMs = Math.round(performance.now() - t0)
65
+ const merged = (meta || extraMeta)
66
+ ? { ...meta, ...extraMeta }
67
+ : undefined
68
+ emitEntry({ category, label, durationMs, meta: merged })
69
+ return durationMs
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Measure an async function. Returns the function's result.
75
+ */
76
+ async function measureAsync<T>(
77
+ category: string,
78
+ label: string,
79
+ fn: () => Promise<T>,
80
+ meta?: Record<string, unknown>,
81
+ ): Promise<T> {
82
+ const end = start(category, label, meta)
83
+ try {
84
+ const result = await fn()
85
+ end()
86
+ return result
87
+ } catch (err) {
88
+ end({ error: true })
89
+ throw err
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Measure a synchronous function. Returns the function's result.
95
+ */
96
+ function measureSync<T>(
97
+ category: string,
98
+ label: string,
99
+ fn: () => T,
100
+ meta?: Record<string, unknown>,
101
+ ): T {
102
+ const end = start(category, label, meta)
103
+ try {
104
+ const result = fn()
105
+ end()
106
+ return result
107
+ } catch (err) {
108
+ end({ error: true })
109
+ throw err
110
+ }
111
+ }
112
+
113
+ /** Enable/disable perf tracing at runtime. */
114
+ function setEnabled(enabled: boolean): void { _enabled = enabled }
115
+
116
+ /** Check if perf tracing is enabled. */
117
+ function isEnabled(): boolean { return _enabled }
118
+
119
+ /** Register a callback for every perf entry (useful for tests/workbench). */
120
+ function onEntry(cb: OnEntryCallback | null): void { _onEntry = cb }
121
+
122
+ /** Get recent perf entries (ring buffer, max 200). */
123
+ function getRecentEntries(): readonly PerfEntry[] { return _recentEntries }
124
+
125
+ /** Clear the recent entries buffer. */
126
+ function clearRecentEntries(): void { _recentEntries.length = 0 }
127
+
128
+ /**
129
+ * Wrap a Next.js API route handler with perf timing.
130
+ * Usage: export const GET = withApiPerf('GET /api/agents', handler)
131
+ */
132
+ function withApiPerf<T extends (...args: unknown[]) => Promise<Response>>(
133
+ label: string,
134
+ handler: T,
135
+ ): T {
136
+ return (async (...args: unknown[]) => {
137
+ const end = start('api', label)
138
+ try {
139
+ const response = await handler(...args)
140
+ end({ status: response.status })
141
+ return response
142
+ } catch (err) {
143
+ end({ error: true })
144
+ throw err
145
+ }
146
+ }) as T
147
+ }
148
+
149
+ export const perf = {
150
+ start,
151
+ measureAsync,
152
+ measureSync,
153
+ setEnabled,
154
+ isEnabled,
155
+ onEntry,
156
+ getRecentEntries,
157
+ clearRecentEntries,
158
+ withApiPerf,
159
+ }
160
+
161
+ export type { PerfEntry }
@@ -0,0 +1,115 @@
1
+ import type { PluginHooks, PluginToolDef } from '@/types'
2
+ import { canonicalizePluginId, expandPluginIds } from './tool-aliases'
3
+ import { dedup } from '@/lib/shared-utils'
4
+
5
+ type ApprovalGuidanceHook = NonNullable<PluginHooks['getApprovalGuidance']>
6
+
7
+ function trimString(value: unknown): string {
8
+ return typeof value === 'string' ? value.trim() : ''
9
+ }
10
+
11
+ function normalizeApprovalGuidanceLines(
12
+ value: string | string[] | null | undefined,
13
+ ): string[] {
14
+ if (typeof value === 'string') {
15
+ const trimmed = value.trim()
16
+ return trimmed ? [trimmed] : []
17
+ }
18
+ if (!Array.isArray(value)) return []
19
+ return value
20
+ .map((line) => (typeof line === 'string' ? line.trim() : ''))
21
+ .filter(Boolean)
22
+ }
23
+
24
+ function dedupeApprovalGuidanceLines(lines: string[]): string[] {
25
+ return dedup(lines.map((line) => line.trim()).filter(Boolean))
26
+ }
27
+
28
+ function formatApprovalToolLabel(toolNames: string[]): string {
29
+ const uniqueNames = dedup(toolNames.map((name) => name.trim()).filter(Boolean))
30
+ if (uniqueNames.length === 0) return 'its tools'
31
+ if (uniqueNames.length === 1) return `\`${uniqueNames[0]}\``
32
+ if (uniqueNames.length === 2) return `\`${uniqueNames[0]}\` and \`${uniqueNames[1]}\``
33
+ return `${uniqueNames.slice(0, -1).map((name) => `\`${name}\``).join(', ')}, and \`${uniqueNames.at(-1)}\``
34
+ }
35
+
36
+ function buildDefaultPluginApprovalGuidance(params: {
37
+ pluginId: string
38
+ pluginName: string
39
+ tools: PluginToolDef[]
40
+ }): ApprovalGuidanceHook {
41
+ const toolNames = params.tools
42
+ .map((tool) => (typeof tool?.name === 'string' ? tool.name.trim() : ''))
43
+ .filter(Boolean)
44
+ const toolLabel = formatApprovalToolLabel(toolNames)
45
+ const matchIds = new Set(
46
+ dedupeApprovalGuidanceLines([
47
+ params.pluginId,
48
+ ...toolNames,
49
+ ...expandPluginIds([params.pluginId]),
50
+ ...toolNames.flatMap((toolName) => expandPluginIds([toolName])),
51
+ ]).map((value) => canonicalizePluginId(value) || value.toLowerCase()),
52
+ )
53
+
54
+ return ({ approval, phase, approved }) => {
55
+ if (approval.category !== 'tool_access') return null
56
+ const requestedIds = [
57
+ trimString(approval.data.pluginId),
58
+ trimString(approval.data.toolId),
59
+ trimString(approval.data.toolName),
60
+ ].filter(Boolean)
61
+ const matchesPlugin = requestedIds.some((value) => {
62
+ const candidates = [value, ...expandPluginIds([value])]
63
+ return candidates.some((candidate) => matchIds.has(canonicalizePluginId(candidate) || candidate.toLowerCase()))
64
+ })
65
+ if (!matchesPlugin) return null
66
+
67
+ if (phase === 'connector_reminder') {
68
+ return `Approving this lets the agent use ${toolLabel} from ${params.pluginName}.`
69
+ }
70
+ if (approved === true) {
71
+ return [
72
+ `Access to ${params.pluginName} is approved. Continue with ${toolLabel} on the next turn.`,
73
+ 'Do not request the same access again in prose once it has been approved.',
74
+ ]
75
+ }
76
+ if (approved === false) {
77
+ return `Do not request access to ${params.pluginName} again unless the task or required capability materially changes.`
78
+ }
79
+ return [
80
+ `If access to ${params.pluginName} is granted, continue with ${toolLabel} on the next turn.`,
81
+ 'Do not ask for the same access again in prose while this approval is pending.',
82
+ ]
83
+ }
84
+ }
85
+
86
+ function composeApprovalGuidance(
87
+ defaultHook: ApprovalGuidanceHook,
88
+ customHook?: PluginHooks['getApprovalGuidance'],
89
+ ): ApprovalGuidanceHook {
90
+ return (ctx) => {
91
+ const combined = dedupeApprovalGuidanceLines([
92
+ ...normalizeApprovalGuidanceLines(defaultHook(ctx)),
93
+ ...normalizeApprovalGuidanceLines(customHook?.(ctx)),
94
+ ])
95
+ return combined.length > 0 ? combined : null
96
+ }
97
+ }
98
+
99
+ export function buildPluginHooks(
100
+ pluginId: string,
101
+ pluginName: string,
102
+ hooks: PluginHooks | undefined,
103
+ tools: PluginToolDef[] | undefined,
104
+ ): PluginHooks {
105
+ const nextHooks: PluginHooks = { ...(hooks || {}) }
106
+ nextHooks.getApprovalGuidance = composeApprovalGuidance(
107
+ buildDefaultPluginApprovalGuidance({
108
+ pluginId,
109
+ pluginName,
110
+ tools: tools || [],
111
+ }),
112
+ hooks?.getApprovalGuidance,
113
+ )
114
+ return nextHooks
115
+ }
@@ -53,7 +53,7 @@ describe('plugin install helpers', () => {
53
53
  assert.equal(sanitizePluginFilename('plugin.js'), 'plugin.js')
54
54
  assert.equal(sanitizePluginFilename('plugin.mjs'), 'plugin.mjs')
55
55
  assert.throws(() => sanitizePluginFilename('../plugin.js'), /Invalid filename/)
56
- assert.throws(() => sanitizePluginFilename('plugin.ts'), /Filename must end/)
56
+ assert.throws(() => sanitizePluginFilename('plugin'), /Filename must end/)
57
57
  })
58
58
  })
59
59
 
@@ -17,6 +17,8 @@ import { log } from './logger'
17
17
  import { createNotification } from './create-notification'
18
18
  import { notify } from './ws-hub'
19
19
  import { decryptKey, encryptKey, loadSettings, saveSettings } from './storage'
20
+ import { buildPluginHooks } from './plugins-approval-guidance'
21
+ import { errorMessage } from '@/lib/shared-utils'
20
22
 
21
23
  const PLUGINS_DIR = path.join(DATA_DIR, 'plugins')
22
24
  const PLUGIN_WORKSPACES_DIR = path.join(PLUGINS_DIR, '.workspaces')
@@ -107,8 +109,6 @@ type HookRegistrar = {
107
109
  type HookContext<K extends keyof PluginHooks> =
108
110
  PluginHooks[K] extends ((ctx: infer C) => unknown) | undefined ? C : never
109
111
 
110
- type ApprovalGuidanceHook = NonNullable<PluginHooks['getApprovalGuidance']>
111
-
112
112
  /** Legacy OpenClaw format: activate(ctx)/deactivate() */
113
113
  interface OpenClawLegacyPlugin {
114
114
  name: string
@@ -117,116 +117,6 @@ interface OpenClawLegacyPlugin {
117
117
  deactivate?: () => void
118
118
  }
119
119
 
120
- function trimString(value: unknown): string {
121
- return typeof value === 'string' ? value.trim() : ''
122
- }
123
-
124
- function normalizeApprovalGuidanceLines(
125
- value: string | string[] | null | undefined,
126
- ): string[] {
127
- if (typeof value === 'string') {
128
- const trimmed = value.trim()
129
- return trimmed ? [trimmed] : []
130
- }
131
- if (!Array.isArray(value)) return []
132
- return value
133
- .map((line) => (typeof line === 'string' ? line.trim() : ''))
134
- .filter(Boolean)
135
- }
136
-
137
- function dedupeApprovalGuidanceLines(lines: string[]): string[] {
138
- return Array.from(new Set(lines.map((line) => line.trim()).filter(Boolean)))
139
- }
140
-
141
- function formatApprovalToolLabel(toolNames: string[]): string {
142
- const uniqueNames = Array.from(new Set(toolNames.map((name) => name.trim()).filter(Boolean)))
143
- if (uniqueNames.length === 0) return 'its tools'
144
- if (uniqueNames.length === 1) return `\`${uniqueNames[0]}\``
145
- if (uniqueNames.length === 2) return `\`${uniqueNames[0]}\` and \`${uniqueNames[1]}\``
146
- return `${uniqueNames.slice(0, -1).map((name) => `\`${name}\``).join(', ')}, and \`${uniqueNames.at(-1)}\``
147
- }
148
-
149
- function buildDefaultPluginApprovalGuidance(params: {
150
- pluginId: string
151
- pluginName: string
152
- tools: PluginToolDef[]
153
- }): ApprovalGuidanceHook {
154
- const toolNames = params.tools
155
- .map((tool) => (typeof tool?.name === 'string' ? tool.name.trim() : ''))
156
- .filter(Boolean)
157
- const toolLabel = formatApprovalToolLabel(toolNames)
158
- const matchIds = new Set(
159
- dedupeApprovalGuidanceLines([
160
- params.pluginId,
161
- ...toolNames,
162
- ...expandPluginIds([params.pluginId]),
163
- ...toolNames.flatMap((toolName) => expandPluginIds([toolName])),
164
- ]).map((value) => canonicalizePluginId(value) || value.toLowerCase()),
165
- )
166
-
167
- return ({ approval, phase, approved }) => {
168
- if (approval.category !== 'tool_access') return null
169
- const requestedIds = [
170
- trimString(approval.data.pluginId),
171
- trimString(approval.data.toolId),
172
- trimString(approval.data.toolName),
173
- ].filter(Boolean)
174
- const matchesPlugin = requestedIds.some((value) => {
175
- const candidates = [value, ...expandPluginIds([value])]
176
- return candidates.some((candidate) => matchIds.has(canonicalizePluginId(candidate) || candidate.toLowerCase()))
177
- })
178
- if (!matchesPlugin) return null
179
-
180
- if (phase === 'connector_reminder') {
181
- return `Approving this lets the agent use ${toolLabel} from ${params.pluginName}.`
182
- }
183
- if (approved === true) {
184
- return [
185
- `Access to ${params.pluginName} is approved. Continue with ${toolLabel} on the next turn.`,
186
- 'Do not request the same access again in prose once it has been approved.',
187
- ]
188
- }
189
- if (approved === false) {
190
- return `Do not request access to ${params.pluginName} again unless the task or required capability materially changes.`
191
- }
192
- return [
193
- `If access to ${params.pluginName} is granted, continue with ${toolLabel} on the next turn.`,
194
- 'Do not ask for the same access again in prose while this approval is pending.',
195
- ]
196
- }
197
- }
198
-
199
- function composeApprovalGuidance(
200
- defaultHook: ApprovalGuidanceHook,
201
- customHook?: PluginHooks['getApprovalGuidance'],
202
- ): ApprovalGuidanceHook {
203
- return (ctx) => {
204
- const combined = dedupeApprovalGuidanceLines([
205
- ...normalizeApprovalGuidanceLines(defaultHook(ctx)),
206
- ...normalizeApprovalGuidanceLines(customHook?.(ctx)),
207
- ])
208
- return combined.length > 0 ? combined : null
209
- }
210
- }
211
-
212
- function buildPluginHooks(
213
- pluginId: string,
214
- pluginName: string,
215
- hooks: PluginHooks | undefined,
216
- tools: PluginToolDef[] | undefined,
217
- ): PluginHooks {
218
- const nextHooks: PluginHooks = { ...(hooks || {}) }
219
- nextHooks.getApprovalGuidance = composeApprovalGuidance(
220
- buildDefaultPluginApprovalGuidance({
221
- pluginId,
222
- pluginName,
223
- tools: tools || [],
224
- }),
225
- hooks?.getApprovalGuidance,
226
- )
227
- return nextHooks
228
- }
229
-
230
120
  /**
231
121
  * Real OpenClaw plugin format: function export `(api) => {}` or object with `register(api)`.
232
122
  * Supports api.registerHook(), api.registerTool(), api.registerCommand(), api.registerService().
@@ -569,7 +459,7 @@ function normalizePlugin(mod: unknown): Plugin | null {
569
459
  } catch (err: unknown) {
570
460
  log.error('plugins', 'OpenClaw register() failed', {
571
461
  pluginName,
572
- error: err instanceof Error ? err.message : String(err),
462
+ error: errorMessage(err),
573
463
  })
574
464
  return null
575
465
  }
@@ -610,7 +500,7 @@ function normalizePlugin(mod: unknown): Plugin | null {
610
500
  } catch (err: unknown) {
611
501
  log.error('plugins', 'OpenClaw activate() failed', {
612
502
  pluginName: oc.name,
613
- error: err instanceof Error ? err.message : String(err),
503
+ error: errorMessage(err),
614
504
  })
615
505
  return null
616
506
  }
@@ -643,7 +533,7 @@ function createPluginRequire(): NodeRequire | null {
643
533
  return createRequire(path.join(process.cwd(), 'package.json'))
644
534
  } catch (err: unknown) {
645
535
  log.warn('plugins', 'createRequire failed; external plugins disabled', {
646
- error: err instanceof Error ? err.message : String(err),
536
+ error: errorMessage(err),
647
537
  })
648
538
  return null
649
539
  }
@@ -679,7 +569,7 @@ class PluginManager {
679
569
  })
680
570
  watcher.on('error', (err: unknown) => {
681
571
  log.warn('plugins', 'Plugin watcher disabled after runtime watch failure', {
682
- error: err instanceof Error ? err.message : String(err),
572
+ error: errorMessage(err),
683
573
  })
684
574
  if (this.watcher === watcher) {
685
575
  try { watcher.close() } catch { /* ignore */ }
@@ -690,7 +580,7 @@ class PluginManager {
690
580
  this.watcher = watcher
691
581
  } catch (err: unknown) {
692
582
  log.warn('plugins', 'Failed to watch plugins directory', {
693
- error: err instanceof Error ? err.message : String(err),
583
+ error: errorMessage(err),
694
584
  })
695
585
  }
696
586
  }
@@ -879,7 +769,7 @@ class PluginManager {
879
769
  try {
880
770
  fs.writeFileSync(PLUGIN_FAILURES, JSON.stringify(state, null, 2))
881
771
  } catch (err: unknown) {
882
- log.warn('plugins', 'Failed to persist plugin failure state', { error: err instanceof Error ? err.message : String(err) })
772
+ log.warn('plugins', 'Failed to persist plugin failure state', { error: errorMessage(err) })
883
773
  }
884
774
  }
885
775
 
@@ -903,7 +793,7 @@ class PluginManager {
903
793
  } catch (err: unknown) {
904
794
  log.error('plugins', 'Failed to write plugins config while auto-disabling plugin', {
905
795
  pluginId: id,
906
- error: err instanceof Error ? err.message : String(err),
796
+ error: errorMessage(err),
907
797
  })
908
798
  return
909
799
  }
@@ -932,7 +822,7 @@ class PluginManager {
932
822
  }
933
823
 
934
824
  private markPluginFailure(id: string, stage: string, err: unknown, disableEligible: boolean): void {
935
- const errorText = err instanceof Error ? err.message : String(err)
825
+ const errorText = errorMessage(err)
936
826
  const state = this.readFailureState()
937
827
  const failureKey = this.canonicalPluginId(id)
938
828
  const nextCount = (state[failureKey]?.count || 0) + 1
@@ -966,7 +856,7 @@ class PluginManager {
966
856
  try {
967
857
  this.clearFailureState(id)
968
858
  } catch (err: unknown) {
969
- log.warn('plugins', 'markPluginSuccess failed', { error: err instanceof Error ? err.message : String(err), pluginId: id })
859
+ log.warn('plugins', 'markPluginSuccess failed', { error: errorMessage(err), pluginId: id })
970
860
  }
971
861
  }
972
862
 
@@ -1053,7 +943,7 @@ class PluginManager {
1053
943
  } catch (err: unknown) {
1054
944
  log.error('plugins', 'Failed to load external plugin', {
1055
945
  pluginId: file,
1056
- error: err instanceof Error ? err.message : String(err),
946
+ error: errorMessage(err),
1057
947
  })
1058
948
  this.markPluginFailure(file, 'load.require', err, true)
1059
949
  }
@@ -1150,7 +1040,7 @@ class PluginManager {
1150
1040
  pluginId: id,
1151
1041
  pluginName: p.meta.name,
1152
1042
  hookName: String(hookName),
1153
- error: err instanceof Error ? err.message : String(err),
1043
+ error: errorMessage(err),
1154
1044
  })
1155
1045
  this.markPluginFailure(id, `hook.${String(hookName)}`, err, true)
1156
1046
  }
@@ -1181,7 +1071,7 @@ class PluginManager {
1181
1071
  pluginId: id,
1182
1072
  pluginName: p.meta.name,
1183
1073
  toolName: params.toolName,
1184
- error: err instanceof Error ? err.message : String(err),
1074
+ error: errorMessage(err),
1185
1075
  })
1186
1076
  this.markPluginFailure(id, 'hook.beforeToolExec', err, true)
1187
1077
  }
@@ -1212,7 +1102,7 @@ class PluginManager {
1212
1102
  pluginId: id,
1213
1103
  pluginName: p.meta.name,
1214
1104
  hookName,
1215
- error: err instanceof Error ? err.message : String(err),
1105
+ error: errorMessage(err),
1216
1106
  })
1217
1107
  this.markPluginFailure(id, `hook.${String(hookName)}`, err, true)
1218
1108
  }
@@ -1240,7 +1130,7 @@ class PluginManager {
1240
1130
  log.error('plugins', 'getAgentContext hook failed', {
1241
1131
  pluginId: id,
1242
1132
  pluginName: p.meta.name,
1243
- error: err instanceof Error ? err.message : String(err),
1133
+ error: errorMessage(err),
1244
1134
  })
1245
1135
  this.markPluginFailure(id, 'hook.getAgentContext', err, true)
1246
1136
  }
@@ -1265,7 +1155,7 @@ class PluginManager {
1265
1155
  lines.push(`- ${result}`)
1266
1156
  }
1267
1157
  } catch (err: unknown) {
1268
- log.error('plugins', 'getCapabilityDescription hook failed', { pluginId: id, error: err instanceof Error ? err.message : String(err) })
1158
+ log.error('plugins', 'getCapabilityDescription hook failed', { pluginId: id, error: errorMessage(err) })
1269
1159
  }
1270
1160
  }
1271
1161
 
@@ -1293,7 +1183,7 @@ class PluginManager {
1293
1183
  }
1294
1184
  }
1295
1185
  } catch (err: unknown) {
1296
- log.error('plugins', 'getOperatingGuidance hook failed', { pluginId: id, error: err instanceof Error ? err.message : String(err) })
1186
+ log.error('plugins', 'getOperatingGuidance hook failed', { pluginId: id, error: errorMessage(err) })
1297
1187
  }
1298
1188
  }
1299
1189
 
@@ -1330,7 +1220,7 @@ class PluginManager {
1330
1220
  } catch (err: unknown) {
1331
1221
  log.error('plugins', 'getApprovalGuidance hook failed', {
1332
1222
  pluginId: id,
1333
- error: err instanceof Error ? err.message : String(err),
1223
+ error: errorMessage(err),
1334
1224
  })
1335
1225
  }
1336
1226
  }
@@ -1617,7 +1507,7 @@ class PluginManager {
1617
1507
 
1618
1508
  return metas
1619
1509
  } catch (err: unknown) {
1620
- log.error('plugins', 'listPlugins failed', { error: err instanceof Error ? err.message : String(err) })
1510
+ log.error('plugins', 'listPlugins failed', { error: errorMessage(err) })
1621
1511
  return []
1622
1512
  }
1623
1513
  }
@@ -1714,7 +1604,7 @@ class PluginManager {
1714
1604
  dependencyInstalledAt: Date.now(),
1715
1605
  })
1716
1606
  } catch (err: unknown) {
1717
- const message = err instanceof Error ? err.message : String(err)
1607
+ const message = errorMessage(err)
1718
1608
  this.setMeta(sanitizedFilename, {
1719
1609
  packageManager,
1720
1610
  dependencyInstallStatus: 'error',
@@ -1832,7 +1722,7 @@ export function getPluginManager(): PluginManager {
1832
1722
  }
1833
1723
  return _manager
1834
1724
  } catch (err: unknown) {
1835
- log.error('plugins', 'getPluginManager critical failure', { error: err instanceof Error ? err.message : String(err) })
1725
+ log.error('plugins', 'getPluginManager critical failure', { error: errorMessage(err) })
1836
1726
  throw err
1837
1727
  }
1838
1728
  }
@@ -1,4 +1,5 @@
1
1
  import { genId } from '@/lib/id'
2
+ import { hmrSingleton, sleep } from '@/lib/shared-utils'
2
3
  import { spawn, type ChildProcessWithoutNullStreams } from 'child_process'
3
4
 
4
5
  const MAX_LOG_CHARS = 200_000
@@ -52,12 +53,11 @@ interface RuntimeState {
52
53
  exitWaiters: Map<string, Promise<ProcessRecord>>
53
54
  }
54
55
 
55
- const globalKey = '__swarmclaw_process_manager__' as const
56
- const state: RuntimeState = (globalThis as any)[globalKey] ?? ((globalThis as any)[globalKey] = {
56
+ const state: RuntimeState = hmrSingleton<RuntimeState>('__swarmclaw_process_manager__', () => ({
57
57
  records: new Map<string, ProcessRecord>(),
58
58
  children: new Map<string, ChildProcessWithoutNullStreams>(),
59
59
  exitWaiters: new Map<string, Promise<ProcessRecord>>(),
60
- })
60
+ }))
61
61
 
62
62
  function now() {
63
63
  return Date.now()
@@ -91,9 +91,6 @@ function normalizeLines(text: string): string[] {
91
91
  return text.split('\n')
92
92
  }
93
93
 
94
- async function wait(ms: number): Promise<void> {
95
- await new Promise((resolve) => setTimeout(resolve, ms))
96
- }
97
94
 
98
95
  function getShellCommand(command: string): { shell: string; args: string[] } {
99
96
  return { shell: '/bin/zsh', args: ['-lc', command] }
@@ -177,7 +174,7 @@ export async function startManagedProcess(opts: StartProcessOptions): Promise<St
177
174
  Math.max(100, BACKGROUND_STARTUP_GRACE_MS),
178
175
  Math.max(200, timeoutMs),
179
176
  )
180
- await wait(startupWaitMs)
177
+ await sleep(startupWaitMs)
181
178
  const rec = state.records.get(id)
182
179
  if (rec && rec.status !== 'running') {
183
180
  return {
@@ -197,7 +194,7 @@ export async function startManagedProcess(opts: StartProcessOptions): Promise<St
197
194
 
198
195
  const completed = await Promise.race([
199
196
  exitPromise.then((r) => ({ type: 'exit' as const, record: r })),
200
- wait(yieldMs).then(() => ({ type: 'yield' as const })),
197
+ sleep(yieldMs).then(() => ({ type: 'yield' as const })),
201
198
  ])
202
199
 
203
200
  if (completed.type === 'yield') {