@swarmclawai/swarmclaw 0.8.4 → 0.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (394) hide show
  1. package/README.md +9 -9
  2. package/bin/swarmclaw.js +5 -1
  3. package/bin/worker-cmd.js +73 -0
  4. package/package.json +2 -1
  5. package/src/app/api/agents/[id]/route.ts +17 -7
  6. package/src/app/api/agents/route.ts +21 -8
  7. package/src/app/api/approvals/route.test.ts +6 -6
  8. package/src/app/api/approvals/route.ts +2 -1
  9. package/src/app/api/auth/route.ts +2 -3
  10. package/src/app/api/chatrooms/[id]/chat/route.test.ts +299 -0
  11. package/src/app/api/chatrooms/[id]/chat/route.ts +3 -2
  12. package/src/app/api/chatrooms/[id]/route.ts +7 -6
  13. package/src/app/api/chats/[id]/chat/route.test.ts +496 -0
  14. package/src/app/api/chats/[id]/chat/route.ts +7 -3
  15. package/src/app/api/chats/[id]/clear/route.ts +9 -9
  16. package/src/app/api/chats/[id]/devserver/route.ts +2 -1
  17. package/src/app/api/chats/[id]/edit-resend/route.ts +3 -4
  18. package/src/app/api/chats/[id]/fork/route.ts +3 -5
  19. package/src/app/api/chats/[id]/restore/route.ts +6 -7
  20. package/src/app/api/chats/[id]/retry/route.ts +3 -4
  21. package/src/app/api/chats/[id]/route.ts +61 -62
  22. package/src/app/api/chats/route.ts +7 -1
  23. package/src/app/api/connectors/[id]/route.ts +7 -8
  24. package/src/app/api/connectors/route.ts +5 -4
  25. package/src/app/api/eval/run/route.ts +2 -1
  26. package/src/app/api/eval/suite/route.ts +2 -1
  27. package/src/app/api/external-agents/route.test.ts +1 -1
  28. package/src/app/api/external-agents/route.ts +2 -2
  29. package/src/app/api/files/serve/route.ts +1 -1
  30. package/src/app/api/gateways/[id]/route.ts +7 -5
  31. package/src/app/api/gateways/route.ts +1 -1
  32. package/src/app/api/knowledge/upload/route.ts +1 -1
  33. package/src/app/api/logs/route.ts +5 -7
  34. package/src/app/api/memory-images/[filename]/route.ts +2 -3
  35. package/src/app/api/openclaw/agent-files/route.ts +4 -3
  36. package/src/app/api/openclaw/approvals/route.ts +3 -4
  37. package/src/app/api/openclaw/config-sync/route.ts +3 -2
  38. package/src/app/api/openclaw/cron/route.ts +3 -2
  39. package/src/app/api/openclaw/dotenv-keys/route.ts +2 -1
  40. package/src/app/api/openclaw/exec-config/route.ts +3 -2
  41. package/src/app/api/openclaw/gateway/route.ts +5 -4
  42. package/src/app/api/openclaw/history/route.ts +3 -2
  43. package/src/app/api/openclaw/media/route.ts +2 -1
  44. package/src/app/api/openclaw/permissions/route.ts +3 -2
  45. package/src/app/api/openclaw/sandbox-env/route.ts +3 -2
  46. package/src/app/api/openclaw/skills/install/route.ts +2 -1
  47. package/src/app/api/openclaw/skills/remove/route.ts +2 -1
  48. package/src/app/api/openclaw/skills/route.ts +3 -2
  49. package/src/app/api/orchestrator/run/route.ts +5 -14
  50. package/src/app/api/perf/route.ts +43 -0
  51. package/src/app/api/plugins/dependencies/route.ts +2 -1
  52. package/src/app/api/plugins/install/route.ts +2 -1
  53. package/src/app/api/plugins/marketplace/route.ts +3 -2
  54. package/src/app/api/plugins/settings/route.ts +2 -1
  55. package/src/app/api/preview-server/route.ts +11 -10
  56. package/src/app/api/projects/[id]/route.ts +1 -1
  57. package/src/app/api/schedules/[id]/route.test.ts +128 -0
  58. package/src/app/api/schedules/[id]/route.ts +43 -43
  59. package/src/app/api/schedules/[id]/run/route.ts +11 -62
  60. package/src/app/api/schedules/route.ts +21 -87
  61. package/src/app/api/settings/route.ts +2 -0
  62. package/src/app/api/setup/doctor/route.ts +9 -8
  63. package/src/app/api/tasks/[id]/approve/route.ts +33 -30
  64. package/src/app/api/tasks/[id]/route.ts +12 -35
  65. package/src/app/api/tasks/import/github/route.ts +2 -1
  66. package/src/app/api/tasks/route.ts +79 -91
  67. package/src/app/api/wallets/[id]/approve/route.ts +2 -1
  68. package/src/app/api/wallets/[id]/route.ts +13 -19
  69. package/src/app/api/wallets/[id]/send/route.ts +2 -1
  70. package/src/app/api/wallets/route.ts +2 -1
  71. package/src/app/api/webhooks/[id]/route.ts +2 -1
  72. package/src/app/api/webhooks/route.test.ts +3 -1
  73. package/src/app/page.tsx +23 -331
  74. package/src/cli/index.js +19 -0
  75. package/src/cli/index.ts +38 -7
  76. package/src/cli/spec.js +9 -0
  77. package/src/components/activity/activity-feed.tsx +7 -4
  78. package/src/components/agents/agent-card.tsx +32 -6
  79. package/src/components/agents/agent-chat-list.tsx +55 -22
  80. package/src/components/agents/agent-files-editor.tsx +3 -2
  81. package/src/components/agents/agent-sheet.tsx +123 -22
  82. package/src/components/agents/inspector-panel.tsx +1 -1
  83. package/src/components/agents/openclaw-skills-panel.tsx +2 -1
  84. package/src/components/agents/trash-list.tsx +1 -1
  85. package/src/components/auth/access-key-gate.tsx +8 -2
  86. package/src/components/auth/setup-wizard.tsx +10 -9
  87. package/src/components/auth/user-picker.tsx +3 -2
  88. package/src/components/chat/chat-area.tsx +20 -1
  89. package/src/components/chat/chat-card.tsx +18 -3
  90. package/src/components/chat/chat-header.tsx +24 -4
  91. package/src/components/chat/chat-list.tsx +2 -11
  92. package/src/components/chat/heartbeat-history-panel.tsx +2 -1
  93. package/src/components/chat/message-bubble.tsx +45 -6
  94. package/src/components/chat/message-list.tsx +280 -145
  95. package/src/components/chat/streaming-bubble.tsx +217 -60
  96. package/src/components/chat/swarm-panel.test.ts +274 -0
  97. package/src/components/chat/swarm-panel.tsx +410 -0
  98. package/src/components/chat/swarm-status-card.tsx +346 -0
  99. package/src/components/chat/tool-call-bubble.tsx +48 -23
  100. package/src/components/chatrooms/chatroom-list.tsx +8 -5
  101. package/src/components/chatrooms/chatroom-message.tsx +10 -7
  102. package/src/components/chatrooms/chatroom-view.tsx +12 -9
  103. package/src/components/connectors/connector-health.tsx +6 -4
  104. package/src/components/connectors/connector-list.tsx +16 -11
  105. package/src/components/connectors/connector-sheet.tsx +12 -6
  106. package/src/components/home/home-view.tsx +38 -24
  107. package/src/components/input/chat-input.tsx +10 -1
  108. package/src/components/layout/app-layout.tsx +2 -38
  109. package/src/components/layout/sheet-layer.tsx +50 -0
  110. package/src/components/mcp-servers/mcp-server-list.tsx +37 -5
  111. package/src/components/mcp-servers/mcp-server-sheet.tsx +12 -2
  112. package/src/components/plugins/plugin-list.tsx +8 -4
  113. package/src/components/plugins/plugin-sheet.tsx +2 -1
  114. package/src/components/providers/provider-list.tsx +3 -2
  115. package/src/components/providers/provider-sheet.tsx +2 -1
  116. package/src/components/runs/run-list.tsx +11 -7
  117. package/src/components/schedules/schedule-card.tsx +5 -3
  118. package/src/components/shared/agent-switch-dialog.tsx +1 -1
  119. package/src/components/shared/attachment-chip.tsx +19 -3
  120. package/src/components/shared/notification-center.tsx +6 -3
  121. package/src/components/shared/settings/plugin-manager.tsx +3 -2
  122. package/src/components/shared/settings/section-embedding.tsx +2 -1
  123. package/src/components/shared/settings/section-orchestrator.tsx +2 -1
  124. package/src/components/shared/settings/section-user-preferences.tsx +107 -0
  125. package/src/components/shared/settings/settings-page.tsx +13 -9
  126. package/src/components/skills/clawhub-browser.tsx +15 -4
  127. package/src/components/skills/skill-list.tsx +15 -4
  128. package/src/components/tasks/approvals-panel.tsx +2 -1
  129. package/src/components/tasks/task-board.tsx +35 -37
  130. package/src/components/tasks/task-sheet.tsx +4 -3
  131. package/src/components/ui/full-screen-loader.tsx +164 -0
  132. package/src/components/wallets/wallet-approval-dialog.tsx +2 -1
  133. package/src/components/wallets/wallet-panel.tsx +6 -5
  134. package/src/components/wallets/wallet-section.tsx +3 -2
  135. package/src/components/webhooks/webhook-list.tsx +4 -5
  136. package/src/components/webhooks/webhook-sheet.tsx +6 -6
  137. package/src/hooks/use-app-bootstrap.ts +202 -0
  138. package/src/hooks/use-mounted-ref.ts +14 -0
  139. package/src/hooks/use-now.ts +31 -0
  140. package/src/hooks/use-openclaw-gateway.ts +2 -1
  141. package/src/instrumentation.ts +20 -8
  142. package/src/lib/agent-default-tools.test.ts +52 -0
  143. package/src/lib/agent-default-tools.ts +40 -0
  144. package/src/lib/api-client.test.ts +21 -0
  145. package/src/lib/api-client.ts +6 -11
  146. package/src/lib/canvas-content.test.ts +360 -0
  147. package/src/lib/chat-streaming-state.test.ts +49 -2
  148. package/src/lib/chat-streaming-state.ts +26 -10
  149. package/src/lib/fetch-timeout.test.ts +54 -0
  150. package/src/lib/fetch-timeout.ts +60 -3
  151. package/src/lib/live-tool-events.test.ts +77 -0
  152. package/src/lib/live-tool-events.ts +73 -0
  153. package/src/lib/local-observability.test.ts +2 -2
  154. package/src/lib/openclaw-endpoint.test.ts +1 -1
  155. package/src/lib/providers/anthropic.ts +12 -16
  156. package/src/lib/providers/index.ts +4 -2
  157. package/src/lib/providers/ollama.ts +9 -6
  158. package/src/lib/providers/openai.ts +11 -14
  159. package/src/lib/runtime-env.test.ts +8 -8
  160. package/src/lib/schedule-dedupe-advanced.test.ts +2 -2
  161. package/src/lib/schedule-dedupe.test.ts +1 -1
  162. package/src/lib/schedule-dedupe.ts +3 -2
  163. package/src/lib/server/agent-thread-session.test.ts +6 -6
  164. package/src/lib/server/agent-thread-session.ts +6 -9
  165. package/src/lib/server/alert-dispatch.ts +2 -1
  166. package/src/lib/server/api-routes.test.ts +6 -6
  167. package/src/lib/server/approval-connector-notify.test.ts +4 -4
  168. package/src/lib/server/approvals-auto-approve.test.ts +29 -29
  169. package/src/lib/server/approvals.test.ts +317 -0
  170. package/src/lib/server/approvals.ts +5 -4
  171. package/src/lib/server/autonomy-runtime.test.ts +11 -11
  172. package/src/lib/server/browser-state.ts +2 -2
  173. package/src/lib/server/capability-router.test.ts +1 -1
  174. package/src/lib/server/capability-router.ts +3 -2
  175. package/src/lib/server/chat-execution-advanced.test.ts +15 -2
  176. package/src/lib/server/chat-execution-connector-delivery.ts +67 -0
  177. package/src/lib/server/chat-execution-disabled.test.ts +3 -3
  178. package/src/lib/server/chat-execution-eval-history.test.ts +3 -3
  179. package/src/lib/server/chat-execution-heartbeat.test.ts +42 -1
  180. package/src/lib/server/chat-execution-session-sync.test.ts +119 -0
  181. package/src/lib/server/chat-execution-tool-events.ts +116 -0
  182. package/src/lib/server/chat-execution-utils.test.ts +479 -0
  183. package/src/lib/server/chat-execution-utils.ts +533 -0
  184. package/src/lib/server/chat-execution.ts +153 -748
  185. package/src/lib/server/chat-streaming-utils.ts +174 -0
  186. package/src/lib/server/chat-turn-tool-routing.ts +310 -0
  187. package/src/lib/server/chatroom-session-persistence.test.ts +2 -2
  188. package/src/lib/server/clawhub-client.ts +2 -1
  189. package/src/lib/server/collection-helpers.test.ts +92 -0
  190. package/src/lib/server/collection-helpers.ts +25 -3
  191. package/src/lib/server/connectors/access.ts +146 -0
  192. package/src/lib/server/connectors/bluebubbles.test.ts +1 -1
  193. package/src/lib/server/connectors/bluebubbles.ts +4 -4
  194. package/src/lib/server/connectors/commands.ts +367 -0
  195. package/src/lib/server/connectors/connector-routing.test.ts +4 -4
  196. package/src/lib/server/connectors/delivery.ts +142 -0
  197. package/src/lib/server/connectors/discord.ts +37 -40
  198. package/src/lib/server/connectors/email.ts +11 -10
  199. package/src/lib/server/connectors/googlechat.ts +4 -4
  200. package/src/lib/server/connectors/inbound-audio-transcription.ts +2 -1
  201. package/src/lib/server/connectors/ingress-delivery.ts +23 -0
  202. package/src/lib/server/connectors/manager-roundtrip.test.ts +300 -0
  203. package/src/lib/server/connectors/manager.test.ts +352 -77
  204. package/src/lib/server/connectors/manager.ts +134 -673
  205. package/src/lib/server/connectors/matrix.ts +4 -4
  206. package/src/lib/server/connectors/message-sentinel.ts +7 -0
  207. package/src/lib/server/connectors/openclaw.test.ts +1 -1
  208. package/src/lib/server/connectors/openclaw.ts +8 -10
  209. package/src/lib/server/connectors/outbox.test.ts +192 -0
  210. package/src/lib/server/connectors/outbox.ts +369 -0
  211. package/src/lib/server/connectors/pairing.test.ts +18 -1
  212. package/src/lib/server/connectors/pairing.ts +49 -4
  213. package/src/lib/server/connectors/policy.ts +9 -3
  214. package/src/lib/server/connectors/reconnect-state.ts +71 -0
  215. package/src/lib/server/connectors/response-media.ts +256 -0
  216. package/src/lib/server/connectors/runtime-state.ts +67 -0
  217. package/src/lib/server/connectors/session.test.ts +357 -0
  218. package/src/lib/server/connectors/session.ts +422 -0
  219. package/src/lib/server/connectors/signal.ts +7 -7
  220. package/src/lib/server/connectors/slack.ts +43 -43
  221. package/src/lib/server/connectors/teams.ts +4 -4
  222. package/src/lib/server/connectors/telegram.ts +37 -43
  223. package/src/lib/server/connectors/types.ts +31 -1
  224. package/src/lib/server/connectors/whatsapp.test.ts +108 -0
  225. package/src/lib/server/connectors/whatsapp.ts +106 -34
  226. package/src/lib/server/context-manager.test.ts +409 -0
  227. package/src/lib/server/cost.test.ts +1 -1
  228. package/src/lib/server/daemon-policy.ts +78 -0
  229. package/src/lib/server/daemon-state-connectors.test.ts +167 -0
  230. package/src/lib/server/daemon-state.test.ts +283 -55
  231. package/src/lib/server/daemon-state.ts +106 -109
  232. package/src/lib/server/data-dir.test.ts +5 -5
  233. package/src/lib/server/data-dir.ts +4 -0
  234. package/src/lib/server/delegation-jobs-advanced.test.ts +1 -1
  235. package/src/lib/server/delegation-jobs.test.ts +87 -0
  236. package/src/lib/server/delegation-jobs.ts +42 -48
  237. package/src/lib/server/devserver-launch.ts +1 -1
  238. package/src/lib/server/document-utils.ts +7 -9
  239. package/src/lib/server/elevenlabs.ts +2 -1
  240. package/src/lib/server/embeddings.test.ts +105 -0
  241. package/src/lib/server/ethereum.ts +3 -2
  242. package/src/lib/server/eval/agent-regression.ts +3 -2
  243. package/src/lib/server/eval/runner.ts +2 -1
  244. package/src/lib/server/eval/scorer.ts +2 -1
  245. package/src/lib/server/evm-swap.ts +2 -1
  246. package/src/lib/server/gateway/protocol.test.ts +1 -1
  247. package/src/lib/server/guardian.ts +2 -1
  248. package/src/lib/server/heartbeat-blocked-suppression.test.ts +151 -0
  249. package/src/lib/server/heartbeat-service-timer.test.ts +6 -6
  250. package/src/lib/server/heartbeat-service.test.ts +406 -0
  251. package/src/lib/server/heartbeat-service.ts +54 -7
  252. package/src/lib/server/heartbeat-wake.test.ts +19 -0
  253. package/src/lib/server/heartbeat-wake.ts +17 -16
  254. package/src/lib/server/integrity-monitor.test.ts +149 -0
  255. package/src/lib/server/json-utils.ts +22 -0
  256. package/src/lib/server/knowledge-db.test.ts +13 -13
  257. package/src/lib/server/link-understanding.ts +2 -1
  258. package/src/lib/server/llm-response-cache.test.ts +1 -1
  259. package/src/lib/server/main-agent-loop-advanced.test.ts +65 -3
  260. package/src/lib/server/main-agent-loop.test.ts +6 -6
  261. package/src/lib/server/main-agent-loop.ts +21 -7
  262. package/src/lib/server/mcp-client.test.ts +1 -1
  263. package/src/lib/server/mcp-conformance.test.ts +1 -1
  264. package/src/lib/server/mcp-conformance.ts +3 -2
  265. package/src/lib/server/memory-consolidation.ts +2 -1
  266. package/src/lib/server/memory-db.test.ts +485 -0
  267. package/src/lib/server/memory-db.ts +39 -26
  268. package/src/lib/server/memory-graph.test.ts +2 -2
  269. package/src/lib/server/memory-policy.test.ts +7 -7
  270. package/src/lib/server/memory-retrieval.test.ts +1 -1
  271. package/src/lib/server/openclaw-config-sync.ts +2 -1
  272. package/src/lib/server/openclaw-deploy.test.ts +1 -1
  273. package/src/lib/server/openclaw-deploy.ts +8 -12
  274. package/src/lib/server/openclaw-exec-config.ts +2 -1
  275. package/src/lib/server/openclaw-gateway.ts +6 -7
  276. package/src/lib/server/openclaw-skills-normalize.ts +2 -1
  277. package/src/lib/server/openclaw-sync.ts +7 -5
  278. package/src/lib/server/orchestrator-lg-structure.test.ts +17 -0
  279. package/src/lib/server/orchestrator-lg.ts +199 -327
  280. package/src/lib/server/path-utils.ts +31 -0
  281. package/src/lib/server/perf.ts +161 -0
  282. package/src/lib/server/plugins-approval-guidance.ts +115 -0
  283. package/src/lib/server/plugins.test.ts +1 -1
  284. package/src/lib/server/plugins.ts +22 -132
  285. package/src/lib/server/process-manager.ts +5 -8
  286. package/src/lib/server/provider-health.test.ts +137 -0
  287. package/src/lib/server/provider-health.ts +3 -3
  288. package/src/lib/server/provider-model-discovery.ts +3 -12
  289. package/src/lib/server/queue-followups.test.ts +9 -9
  290. package/src/lib/server/queue-reconcile.test.ts +2 -2
  291. package/src/lib/server/queue-recovery.test.ts +269 -0
  292. package/src/lib/server/queue.test.ts +570 -0
  293. package/src/lib/server/queue.ts +62 -455
  294. package/src/lib/server/resolve-image.ts +30 -0
  295. package/src/lib/server/runtime-settings.test.ts +4 -4
  296. package/src/lib/server/runtime-storage-write-paths.test.ts +60 -0
  297. package/src/lib/server/schedule-normalization.test.ts +279 -0
  298. package/src/lib/server/schedule-service.ts +263 -0
  299. package/src/lib/server/scheduler.ts +17 -74
  300. package/src/lib/server/session-mailbox.test.ts +191 -0
  301. package/src/lib/server/session-run-manager.test.ts +640 -0
  302. package/src/lib/server/session-run-manager.ts +59 -15
  303. package/src/lib/server/session-tools/autonomy-tools.test.ts +20 -20
  304. package/src/lib/server/session-tools/calendar.ts +2 -1
  305. package/src/lib/server/session-tools/canvas.ts +2 -1
  306. package/src/lib/server/session-tools/chatroom.ts +2 -1
  307. package/src/lib/server/session-tools/connector.ts +26 -28
  308. package/src/lib/server/session-tools/context-mgmt.ts +3 -2
  309. package/src/lib/server/session-tools/crawl.ts +4 -3
  310. package/src/lib/server/session-tools/crud.ts +105 -324
  311. package/src/lib/server/session-tools/delegate-fallback.test.ts +9 -9
  312. package/src/lib/server/session-tools/delegate.ts +6 -8
  313. package/src/lib/server/session-tools/discovery-approvals.test.ts +15 -15
  314. package/src/lib/server/session-tools/discovery.ts +4 -3
  315. package/src/lib/server/session-tools/document.ts +2 -1
  316. package/src/lib/server/session-tools/email.ts +2 -1
  317. package/src/lib/server/session-tools/extract.ts +2 -1
  318. package/src/lib/server/session-tools/file.ts +4 -3
  319. package/src/lib/server/session-tools/http.ts +2 -1
  320. package/src/lib/server/session-tools/human-loop.ts +2 -1
  321. package/src/lib/server/session-tools/image-gen.ts +4 -3
  322. package/src/lib/server/session-tools/index.ts +26 -30
  323. package/src/lib/server/session-tools/mailbox.ts +2 -1
  324. package/src/lib/server/session-tools/manage-connectors.test.ts +4 -4
  325. package/src/lib/server/session-tools/manage-schedules.test.ts +12 -12
  326. package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +5 -5
  327. package/src/lib/server/session-tools/manage-tasks.test.ts +2 -2
  328. package/src/lib/server/session-tools/monitor.ts +2 -1
  329. package/src/lib/server/session-tools/platform.ts +2 -1
  330. package/src/lib/server/session-tools/plugin-creator.ts +2 -1
  331. package/src/lib/server/session-tools/replicate.ts +3 -2
  332. package/src/lib/server/session-tools/session-tools-wiring.test.ts +6 -6
  333. package/src/lib/server/session-tools/shell.ts +4 -9
  334. package/src/lib/server/session-tools/subagent.ts +322 -170
  335. package/src/lib/server/session-tools/table.ts +6 -5
  336. package/src/lib/server/session-tools/wallet-tool.test.ts +3 -3
  337. package/src/lib/server/session-tools/wallet.ts +7 -6
  338. package/src/lib/server/session-tools/web-browser-config.test.ts +1 -0
  339. package/src/lib/server/session-tools/web-utils.ts +317 -0
  340. package/src/lib/server/session-tools/web.ts +62 -328
  341. package/src/lib/server/skill-prompt-budget.test.ts +1 -1
  342. package/src/lib/server/skills-normalize.ts +2 -1
  343. package/src/lib/server/storage-item-access.test.ts +302 -0
  344. package/src/lib/server/storage.ts +366 -314
  345. package/src/lib/server/stream-agent-chat.test.ts +82 -3
  346. package/src/lib/server/stream-agent-chat.ts +146 -510
  347. package/src/lib/server/stream-continuation.ts +412 -0
  348. package/src/lib/server/subagent-lineage.test.ts +647 -0
  349. package/src/lib/server/subagent-lineage.ts +435 -0
  350. package/src/lib/server/subagent-runtime.test.ts +484 -0
  351. package/src/lib/server/subagent-runtime.ts +419 -0
  352. package/src/lib/server/subagent-swarm.test.ts +391 -0
  353. package/src/lib/server/subagent-swarm.ts +564 -0
  354. package/src/lib/server/system-events.ts +3 -3
  355. package/src/lib/server/task-followups.test.ts +491 -0
  356. package/src/lib/server/task-followups.ts +391 -0
  357. package/src/lib/server/task-lifecycle.test.ts +205 -0
  358. package/src/lib/server/task-lifecycle.ts +200 -0
  359. package/src/lib/server/task-quality-gate.test.ts +1 -1
  360. package/src/lib/server/task-resume.ts +208 -0
  361. package/src/lib/server/task-service.test.ts +108 -0
  362. package/src/lib/server/task-service.ts +264 -0
  363. package/src/lib/server/task-validation.test.ts +1 -1
  364. package/src/lib/server/test-utils/run-with-temp-data-dir.ts +42 -0
  365. package/src/lib/server/tool-capability-policy.test.ts +2 -2
  366. package/src/lib/server/tool-capability-policy.ts +3 -2
  367. package/src/lib/server/tool-planning.ts +2 -1
  368. package/src/lib/server/tool-retry.ts +2 -3
  369. package/src/lib/server/wake-dispatcher.test.ts +303 -0
  370. package/src/lib/server/wake-dispatcher.ts +318 -0
  371. package/src/lib/server/wake-mode.test.ts +161 -0
  372. package/src/lib/server/wake-mode.ts +174 -0
  373. package/src/lib/server/wallet-service.ts +8 -9
  374. package/src/lib/server/watch-jobs.ts +2 -1
  375. package/src/lib/server/workspace-context.ts +2 -2
  376. package/src/lib/shared-utils.test.ts +142 -0
  377. package/src/lib/shared-utils.ts +62 -0
  378. package/src/lib/tool-event-summary.ts +2 -1
  379. package/src/lib/view-routes.test.ts +100 -0
  380. package/src/lib/wallet.test.ts +322 -6
  381. package/src/proxy.test.ts +4 -4
  382. package/src/proxy.ts +2 -3
  383. package/src/stores/set-if-changed.ts +40 -0
  384. package/src/stores/slices/agent-slice.ts +111 -0
  385. package/src/stores/slices/auth-slice.ts +25 -0
  386. package/src/stores/slices/data-slice.ts +301 -0
  387. package/src/stores/slices/index.ts +7 -0
  388. package/src/stores/slices/session-slice.ts +112 -0
  389. package/src/stores/slices/task-slice.ts +63 -0
  390. package/src/stores/slices/ui-slice.ts +192 -0
  391. package/src/stores/use-app-store.ts +17 -822
  392. package/src/stores/use-approval-store.ts +2 -1
  393. package/src/stores/use-chat-store.ts +8 -1
  394. package/src/types/index.ts +10 -0
@@ -1,5 +1,6 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { detectConfigIssues, repairConfigIssue } from '@/lib/server/openclaw-config-sync'
3
+ import { errorMessage } from '@/lib/shared-utils'
3
4
 
4
5
  /** GET — detect configuration issues */
5
6
  export async function GET() {
@@ -7,7 +8,7 @@ export async function GET() {
7
8
  const issues = await detectConfigIssues()
8
9
  return NextResponse.json({ issues })
9
10
  } catch (err: unknown) {
10
- const message = err instanceof Error ? err.message : String(err)
11
+ const message = errorMessage(err)
11
12
  return NextResponse.json({ error: message }, { status: 502 })
12
13
  }
13
14
  }
@@ -27,7 +28,7 @@ export async function POST(req: Request) {
27
28
  }
28
29
  return NextResponse.json({ ok: true })
29
30
  } catch (err: unknown) {
30
- const message = err instanceof Error ? err.message : String(err)
31
+ const message = errorMessage(err)
31
32
  return NextResponse.json({ error: message }, { status: 502 })
32
33
  }
33
34
  }
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { ensureGatewayConnected } from '@/lib/server/openclaw-gateway'
3
3
  import type { GatewayCronJob } from '@/types'
4
+ import { errorMessage } from '@/lib/shared-utils'
4
5
 
5
6
  /** GET — list all cron jobs from gateway */
6
7
  export async function GET() {
@@ -13,7 +14,7 @@ export async function GET() {
13
14
  const result = await gw.rpc('cron.list', { includeDisabled: true }) as GatewayCronJob[] | undefined
14
15
  return NextResponse.json(result ?? [])
15
16
  } catch (err: unknown) {
16
- const message = err instanceof Error ? err.message : String(err)
17
+ const message = errorMessage(err)
17
18
  return NextResponse.json({ error: message }, { status: 502 })
18
19
  }
19
20
  }
@@ -46,7 +47,7 @@ export async function POST(req: Request) {
46
47
  return NextResponse.json({ error: `Unknown action: ${action}` }, { status: 400 })
47
48
  }
48
49
  } catch (err: unknown) {
49
- const message = err instanceof Error ? err.message : String(err)
50
+ const message = errorMessage(err)
50
51
  return NextResponse.json({ error: message }, { status: 502 })
51
52
  }
52
53
  }
@@ -1,5 +1,6 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { ensureGatewayConnected } from '@/lib/server/openclaw-gateway'
3
+ import { errorMessage } from '@/lib/shared-utils'
3
4
 
4
5
  /** GET — list env var keys from gateway .env */
5
6
  export async function GET() {
@@ -12,7 +13,7 @@ export async function GET() {
12
13
  const result = await gw.rpc('env.keys') as string[] | undefined
13
14
  return NextResponse.json(result ?? [])
14
15
  } catch (err: unknown) {
15
- const message = err instanceof Error ? err.message : String(err)
16
+ const message = errorMessage(err)
16
17
  return NextResponse.json({ error: message }, { status: 502 })
17
18
  }
18
19
  }
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { getExecConfig, setExecConfig } from '@/lib/server/openclaw-exec-config'
3
3
  import type { ExecApprovalConfig } from '@/types'
4
+ import { errorMessage } from '@/lib/shared-utils'
4
5
 
5
6
  /** GET ?agentId=X — fetch exec approval config */
6
7
  export async function GET(req: Request) {
@@ -14,7 +15,7 @@ export async function GET(req: Request) {
14
15
  const snapshot = await getExecConfig(agentId)
15
16
  return NextResponse.json(snapshot)
16
17
  } catch (err: unknown) {
17
- const message = err instanceof Error ? err.message : String(err)
18
+ const message = errorMessage(err)
18
19
  return NextResponse.json({ error: message }, { status: 502 })
19
20
  }
20
21
  }
@@ -35,7 +36,7 @@ export async function PUT(req: Request) {
35
36
  const result = await setExecConfig(agentId, config, baseHash ?? '')
36
37
  return NextResponse.json(result)
37
38
  } catch (err: unknown) {
38
- const message = err instanceof Error ? err.message : String(err)
39
+ const message = errorMessage(err)
39
40
  return NextResponse.json({ error: message }, { status: 502 })
40
41
  }
41
42
  }
@@ -1,5 +1,6 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { ensureGatewayConnected, getGateway, disconnectGateway, manualConnect } from '@/lib/server/openclaw-gateway'
3
+ import { errorMessage } from '@/lib/shared-utils'
3
4
 
4
5
  /** POST — proxy an RPC call or perform gateway actions */
5
6
  export async function POST(req: Request) {
@@ -18,7 +19,7 @@ export async function POST(req: Request) {
18
19
  const ok = await manualConnect(url, token, profileId)
19
20
  return NextResponse.json({ ok })
20
21
  } catch (err: unknown) {
21
- return NextResponse.json({ ok: false, error: err instanceof Error ? err.message : String(err) }, { status: 502 })
22
+ return NextResponse.json({ ok: false, error: errorMessage(err) }, { status: 502 })
22
23
  }
23
24
  }
24
25
 
@@ -36,7 +37,7 @@ export async function POST(req: Request) {
36
37
  const mode = (config as Record<string, unknown>)?.reloadMode ?? 'hot'
37
38
  return NextResponse.json({ ok: true, result: mode })
38
39
  } catch (err: unknown) {
39
- return NextResponse.json({ error: err instanceof Error ? err.message : String(err) }, { status: 502 })
40
+ return NextResponse.json({ error: errorMessage(err) }, { status: 502 })
40
41
  }
41
42
  }
42
43
 
@@ -47,7 +48,7 @@ export async function POST(req: Request) {
47
48
  await gw.rpc('config.set', { reloadMode: params?.mode })
48
49
  return NextResponse.json({ ok: true })
49
50
  } catch (err: unknown) {
50
- return NextResponse.json({ error: err instanceof Error ? err.message : String(err) }, { status: 502 })
51
+ return NextResponse.json({ error: errorMessage(err) }, { status: 502 })
51
52
  }
52
53
  }
53
54
 
@@ -61,7 +62,7 @@ export async function POST(req: Request) {
61
62
  const result = await gw.rpc(method, params)
62
63
  return NextResponse.json({ ok: true, result })
63
64
  } catch (err: unknown) {
64
- const message = err instanceof Error ? err.message : String(err)
65
+ const message = errorMessage(err)
65
66
  return NextResponse.json({ error: message }, { status: 502 })
66
67
  }
67
68
  }
@@ -4,6 +4,7 @@ import { mergeHistoryMessages, isValidSessionKey } from '@/lib/server/openclaw-h
4
4
  import { loadSessions, saveSessions } from '@/lib/server/storage'
5
5
  import { notify } from '@/lib/server/ws-hub'
6
6
  import type { GatewaySessionPreview } from '@/types'
7
+ import { errorMessage } from '@/lib/shared-utils'
7
8
 
8
9
  /**
9
10
  * Extract a single session preview from the gateway response.
@@ -58,7 +59,7 @@ export async function GET(req: Request) {
58
59
  const preview = extractPreview(raw, sessionKey)
59
60
  return NextResponse.json(preview ?? { sessionKey, epoch: 0, messages: [] })
60
61
  } catch (err: unknown) {
61
- const message = err instanceof Error ? err.message : String(err)
62
+ const message = errorMessage(err)
62
63
  return NextResponse.json({ error: message }, { status: 502 })
63
64
  }
64
65
  }
@@ -103,7 +104,7 @@ export async function POST(req: Request) {
103
104
 
104
105
  return NextResponse.json({ ok: true, merged: newCount })
105
106
  } catch (err: unknown) {
106
- const message = err instanceof Error ? err.message : String(err)
107
+ const message = errorMessage(err)
107
108
  return NextResponse.json({ error: message }, { status: 502 })
108
109
  }
109
110
  }
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { ensureGatewayConnected } from '@/lib/server/openclaw-gateway'
3
3
  import { lookup } from 'mime-types'
4
+ import { errorMessage } from '@/lib/shared-utils'
4
5
 
5
6
  const MAX_SIZE = 25 * 1024 * 1024 // 25MB
6
7
 
@@ -47,7 +48,7 @@ export async function GET(req: Request) {
47
48
  },
48
49
  })
49
50
  } catch (err: unknown) {
50
- const message = err instanceof Error ? err.message : String(err)
51
+ const message = errorMessage(err)
51
52
  return NextResponse.json({ error: message }, { status: 502 })
52
53
  }
53
54
  }
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
2
2
  import type { PermissionPreset } from '@/types'
3
3
  import { getExecConfig } from '@/lib/server/openclaw-exec-config'
4
4
  import { resolvePresetFromConfig, applyPreset } from '@/lib/server/openclaw-permission-presets'
5
+ import { errorMessage } from '@/lib/shared-utils'
5
6
 
6
7
  /** GET ?agentId=X — resolve current permission preset */
7
8
  export async function GET(req: Request) {
@@ -16,7 +17,7 @@ export async function GET(req: Request) {
16
17
  const preset = resolvePresetFromConfig(snap.file)
17
18
  return NextResponse.json({ preset, config: snap.file })
18
19
  } catch (err: unknown) {
19
- const message = err instanceof Error ? err.message : String(err)
20
+ const message = errorMessage(err)
20
21
  return NextResponse.json({ error: message }, { status: 502 })
21
22
  }
22
23
  }
@@ -33,7 +34,7 @@ export async function PUT(req: Request) {
33
34
  await applyPreset(agentId, preset)
34
35
  return NextResponse.json({ ok: true })
35
36
  } catch (err: unknown) {
36
- const message = err instanceof Error ? err.message : String(err)
37
+ const message = errorMessage(err)
37
38
  return NextResponse.json({ error: message }, { status: 502 })
38
39
  }
39
40
  }
@@ -1,5 +1,6 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { ensureGatewayConnected } from '@/lib/server/openclaw-gateway'
3
+ import { errorMessage } from '@/lib/shared-utils'
3
4
 
4
5
  /** GET — list available and allowed env keys for sandbox */
5
6
  export async function GET() {
@@ -29,7 +30,7 @@ export async function GET() {
29
30
 
30
31
  return NextResponse.json({ available: available ?? [], allowed })
31
32
  } catch (err: unknown) {
32
- const message = err instanceof Error ? err.message : String(err)
33
+ const message = errorMessage(err)
33
34
  return NextResponse.json({ error: message }, { status: 502 })
34
35
  }
35
36
  }
@@ -63,7 +64,7 @@ export async function PUT(req: Request) {
63
64
 
64
65
  return NextResponse.json({ ok: true })
65
66
  } catch (err: unknown) {
66
- const message = err instanceof Error ? err.message : String(err)
67
+ const message = errorMessage(err)
67
68
  return NextResponse.json({ error: message }, { status: 502 })
68
69
  }
69
70
  }
@@ -1,5 +1,6 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { ensureGatewayConnected } from '@/lib/server/openclaw-gateway'
3
+ import { errorMessage } from '@/lib/shared-utils'
3
4
 
4
5
  /** POST { name, installId, timeoutMs? } — install a skill via gateway */
5
6
  export async function POST(req: Request) {
@@ -26,7 +27,7 @@ export async function POST(req: Request) {
26
27
  }, (timeoutMs ?? 120_000) + 5_000)
27
28
  return NextResponse.json({ ok: true, result })
28
29
  } catch (err: unknown) {
29
- const message = err instanceof Error ? err.message : String(err)
30
+ const message = errorMessage(err)
30
31
  return NextResponse.json({ error: message }, { status: 502 })
31
32
  }
32
33
  }
@@ -1,5 +1,6 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { ensureGatewayConnected } from '@/lib/server/openclaw-gateway'
3
+ import { errorMessage } from '@/lib/shared-utils'
3
4
 
4
5
  /** POST { skillKey, source } — remove a skill via gateway */
5
6
  export async function POST(req: Request) {
@@ -18,7 +19,7 @@ export async function POST(req: Request) {
18
19
  await gw.rpc('skills.remove', { skillKey, source })
19
20
  return NextResponse.json({ ok: true })
20
21
  } catch (err: unknown) {
21
- const message = err instanceof Error ? err.message : String(err)
22
+ const message = errorMessage(err)
22
23
  return NextResponse.json({ error: message }, { status: 502 })
23
24
  }
24
25
  }
@@ -5,6 +5,7 @@ import { normalizeOpenClawSkillsPayload } from '@/lib/server/openclaw-skills-nor
5
5
  import { loadAgents, saveAgents } from '@/lib/server/storage'
6
6
  import { notify } from '@/lib/server/ws-hub'
7
7
  import type { SkillAllowlistMode } from '@/types'
8
+ import { errorMessage } from '@/lib/shared-utils'
8
9
 
9
10
  /** GET ?agentId=X — fetch skills from gateway with eligibility */
10
11
  export async function GET(req: Request) {
@@ -24,7 +25,7 @@ export async function GET(req: Request) {
24
25
  const result = await gw.rpc('skills.status', { agentId: gatewayAgentId }) as unknown
25
26
  return NextResponse.json(normalizeOpenClawSkillsPayload(result))
26
27
  } catch (err: unknown) {
27
- const message = err instanceof Error ? err.message : String(err)
28
+ const message = errorMessage(err)
28
29
  const status = message.includes('not an OpenClaw agent')
29
30
  ? 400
30
31
  : message.includes('not found')
@@ -55,7 +56,7 @@ export async function PATCH(req: Request) {
55
56
  await gw.rpc('skills.update', { skillKey, enabled, apiKey })
56
57
  return NextResponse.json({ ok: true })
57
58
  } catch (err: unknown) {
58
- const message = err instanceof Error ? err.message : String(err)
59
+ const message = errorMessage(err)
59
60
  return NextResponse.json({ error: message }, { status: 502 })
60
61
  }
61
62
  }
@@ -1,7 +1,8 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { genId } from '@/lib/id'
3
- import { loadAgents, loadTasks, saveTasks } from '@/lib/server/storage'
3
+ import { loadAgents, upsertTask } from '@/lib/server/storage'
4
4
  import { enqueueTask } from '@/lib/server/queue'
5
+ import { buildBoardTask } from '@/lib/server/task-lifecycle'
5
6
 
6
7
  export async function POST(req: Request) {
7
8
  const { agentId, task } = await req.json().catch(() => ({}))
@@ -18,23 +19,13 @@ export async function POST(req: Request) {
18
19
  // Create a board task and enqueue it
19
20
  const taskId = genId()
20
21
  const now = Date.now()
21
- const tasks = loadTasks()
22
- tasks[taskId] = {
22
+ upsertTask(taskId, buildBoardTask({
23
23
  id: taskId,
24
24
  title: task.slice(0, 80),
25
25
  description: task,
26
- status: 'backlog',
27
26
  agentId,
28
- sessionId: null,
29
- result: null,
30
- error: null,
31
- createdAt: now,
32
- updatedAt: now,
33
- queuedAt: null,
34
- startedAt: null,
35
- completedAt: null,
36
- }
37
- saveTasks(tasks)
27
+ now,
28
+ }))
38
29
 
39
30
  // Enqueue — this sets status to queued and kicks the worker
40
31
  enqueueTask(taskId)
@@ -0,0 +1,43 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { perf } from '@/lib/server/perf'
3
+
4
+ export const dynamic = 'force-dynamic'
5
+
6
+ /**
7
+ * GET /api/perf — Returns recent perf entries and current state.
8
+ * POST /api/perf — Enable/disable perf tracing or clear entries.
9
+ *
10
+ * Only active when SWARMCLAW_PERF=1 or after POST {action:'enable'}.
11
+ * Workbench tests use this to activate tracing and collect results.
12
+ */
13
+
14
+ export function GET() {
15
+ return NextResponse.json({
16
+ enabled: perf.isEnabled(),
17
+ entries: perf.getRecentEntries(),
18
+ count: perf.getRecentEntries().length,
19
+ })
20
+ }
21
+
22
+ export async function POST(req: Request) {
23
+ const body = await req.json().catch(() => ({})) as Record<string, unknown>
24
+ const action = body.action
25
+
26
+ if (action === 'enable') {
27
+ perf.setEnabled(true)
28
+ perf.clearRecentEntries()
29
+ return NextResponse.json({ enabled: true })
30
+ }
31
+
32
+ if (action === 'disable') {
33
+ perf.setEnabled(false)
34
+ return NextResponse.json({ enabled: false })
35
+ }
36
+
37
+ if (action === 'clear') {
38
+ perf.clearRecentEntries()
39
+ return NextResponse.json({ cleared: true, enabled: perf.isEnabled() })
40
+ }
41
+
42
+ return NextResponse.json({ error: 'Invalid action. Use "enable", "disable", or "clear".' }, { status: 400 })
43
+ }
@@ -1,5 +1,6 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { getPluginManager } from '@/lib/server/plugins'
3
+ import { errorMessage } from '@/lib/shared-utils'
3
4
 
4
5
  export async function POST(req: Request) {
5
6
  const body = await req.json()
@@ -17,7 +18,7 @@ export async function POST(req: Request) {
17
18
  return NextResponse.json({ ok: true, dependencyInfo: result })
18
19
  } catch (err: unknown) {
19
20
  return NextResponse.json(
20
- { error: err instanceof Error ? err.message : String(err) },
21
+ { error: errorMessage(err) },
21
22
  { status: 400 },
22
23
  )
23
24
  }
@@ -1,5 +1,6 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { getPluginManager, sanitizePluginFilename } from '@/lib/server/plugins'
3
+ import { errorMessage } from '@/lib/shared-utils'
3
4
  import {
4
5
  inferPluginInstallSourceFromUrl,
5
6
  inferPluginPublisherSourceFromUrl,
@@ -57,7 +58,7 @@ export async function POST(req: Request) {
57
58
  })
58
59
  return json({ ok: true, filename: installed.filename, hash: installed.sourceHash }, 200, origin)
59
60
  } catch (err: unknown) {
60
- const msg = err instanceof Error ? err.message : String(err)
61
+ const msg = errorMessage(err)
61
62
  const isTimeout = /abort|timeout/i.test(msg)
62
63
  const status = /valid HTTPS URL|Filename|Invalid filename|HTML page|too large/i.test(msg)
63
64
  ? 400
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
2
2
  import { inferPluginPublisherSourceFromUrl } from '@/lib/plugin-sources'
3
3
  import { searchClawHub } from '@/lib/server/clawhub-client'
4
4
  import type { PluginCatalogSource } from '@/types'
5
+ import { errorMessage } from '@/lib/shared-utils'
5
6
 
6
7
  export const dynamic = 'force-dynamic'
7
8
 
@@ -77,7 +78,7 @@ export async function GET(req: Request) {
77
78
  } catch (err: unknown) {
78
79
  console.warn('[marketplace] SC Registry failed:', {
79
80
  registryUrl: registry.url,
80
- error: err instanceof Error ? err.message : String(err),
81
+ error: errorMessage(err),
81
82
  })
82
83
  }
83
84
  }
@@ -98,7 +99,7 @@ export async function GET(req: Request) {
98
99
  catalogSource: 'clawhub',
99
100
  })))
100
101
  } catch (err: unknown) {
101
- console.warn('[marketplace] ClawHub failed:', err instanceof Error ? err.message : String(err))
102
+ console.warn('[marketplace] ClawHub failed:', errorMessage(err))
102
103
  }
103
104
 
104
105
  allPlugins.sort((a, b) => {
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { getPluginManager } from '@/lib/server/plugins'
3
3
  import '@/lib/server/builtin-plugins'
4
+ import { errorMessage } from '@/lib/shared-utils'
4
5
 
5
6
  export const dynamic = 'force-dynamic'
6
7
 
@@ -33,7 +34,7 @@ export async function PUT(req: Request) {
33
34
  })
34
35
  } catch (err: unknown) {
35
36
  return NextResponse.json(
36
- { error: err instanceof Error ? err.message : String(err) },
37
+ { error: errorMessage(err) },
37
38
  { status: 400 },
38
39
  )
39
40
  }
@@ -5,6 +5,8 @@ import fs from 'fs'
5
5
  import path from 'path'
6
6
  import { localIP } from '@/lib/server/storage'
7
7
  import { resolveDevServerLaunchDir } from '@/lib/server/devserver-launch'
8
+ import { resolvePathWithinBaseDir } from '@/lib/server/path-utils'
9
+ import { hmrSingleton, sleep } from '@/lib/shared-utils'
8
10
 
9
11
  // ---------------------------------------------------------------------------
10
12
  // MIME types for static server
@@ -35,9 +37,8 @@ interface PreviewServer {
35
37
  log: string
36
38
  }
37
39
 
38
- const globalKey = '__swarmclaw_preview_servers__' as const
39
- const servers: Map<string, PreviewServer> = (globalThis as unknown as Record<string, unknown>)[globalKey] as Map<string, PreviewServer>
40
- ?? ((globalThis as unknown as Record<string, unknown>)[globalKey] = new Map<string, PreviewServer>())
40
+ const servers: Map<string, PreviewServer> =
41
+ hmrSingleton('__swarmclaw_preview_servers__', () => new Map<string, PreviewServer>())
41
42
 
42
43
  // ---------------------------------------------------------------------------
43
44
  // Helpers
@@ -134,11 +135,11 @@ function createStaticServer(dir: string): http.Server {
134
135
 
135
136
  let reqPath = decodeURIComponent((req.url || '/').split('?')[0])
136
137
  if (reqPath === '/') reqPath = '/index.html'
137
-
138
- const filePath = path.join(dir, reqPath)
139
- const normalizedFile = path.resolve(filePath)
140
-
141
- if (!normalizedFile.startsWith(dir)) {
138
+ const relativeReqPath = reqPath.replace(/^\/+/, '')
139
+ let normalizedFile = ''
140
+ try {
141
+ normalizedFile = resolvePathWithinBaseDir(dir, relativeReqPath)
142
+ } catch {
142
143
  res.writeHead(403)
143
144
  res.end('Forbidden')
144
145
  return
@@ -147,7 +148,7 @@ function createStaticServer(dir: string): http.Server {
147
148
  const candidates = [
148
149
  normalizedFile,
149
150
  normalizedFile + '.html',
150
- path.join(normalizedFile, 'index.html'),
151
+ resolvePathWithinBaseDir(dir, `${relativeReqPath.replace(/\/+$/, '')}/index.html`),
151
152
  ]
152
153
 
153
154
  for (const candidate of candidates) {
@@ -241,7 +242,7 @@ async function startNpmServer(dir: string, command: string[], port: number, fram
241
242
  servers.set(dirKey(dir), entry)
242
243
 
243
244
  // Wait for the server to start and detect the actual port
244
- await new Promise((resolve) => setTimeout(resolve, 5000))
245
+ await sleep(5000)
245
246
  if (proc.exitCode !== null) {
246
247
  servers.delete(dirKey(dir))
247
248
  throw new Error(`npm dev server exited early with code ${proc.exitCode}\n${log.slice(-4000)}`)
@@ -50,7 +50,7 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
50
50
  }
51
51
 
52
52
  clearProjectId(loadAgents, saveAgents, 'agents')
53
- clearProjectId(loadTasks, saveTasks, 'tasks')
53
+ clearProjectId(loadTasks, saveTasks as any, 'tasks')
54
54
  clearProjectId(loadSchedules, saveSchedules, 'schedules')
55
55
  clearProjectId(loadSkills, saveSkills, 'skills')
56
56
  clearProjectId(loadSecrets, saveSecrets, 'secrets')
@@ -0,0 +1,128 @@
1
+ import assert from 'node:assert/strict'
2
+ import test, { afterEach } from 'node:test'
3
+
4
+ import { DELETE as deleteScheduleRoute, PUT as updateSchedule } from './route'
5
+ import { loadAgents, loadSchedules, saveAgents, saveSchedules } from '@/lib/server/storage'
6
+
7
+ const originalAgents = loadAgents()
8
+ const originalSchedules = loadSchedules()
9
+
10
+ function routeParams(id: string) {
11
+ return { params: Promise.resolve({ id }) }
12
+ }
13
+
14
+ function seedAgent(id: string, overrides: Record<string, unknown> = {}) {
15
+ const agents = loadAgents()
16
+ const now = Date.now()
17
+ agents[id] = {
18
+ id,
19
+ name: 'Schedule Route Agent',
20
+ description: 'Schedule route test agent',
21
+ systemPrompt: 'Handle schedules.',
22
+ provider: 'openai',
23
+ model: 'gpt-4o-mini',
24
+ credentialId: null,
25
+ fallbackCredentialIds: [],
26
+ apiEndpoint: null,
27
+ plugins: ['manage_schedules'],
28
+ createdAt: now,
29
+ updatedAt: now,
30
+ ...overrides,
31
+ }
32
+ saveAgents(agents)
33
+ }
34
+
35
+ afterEach(() => {
36
+ saveAgents(originalAgents)
37
+ saveSchedules(originalSchedules)
38
+ })
39
+
40
+ test('PUT /api/schedules/[id] pauses equivalent reminder schedules together', async () => {
41
+ seedAgent('schedule-route-agent')
42
+ const now = Date.now()
43
+ saveSchedules({
44
+ one: {
45
+ id: 'one',
46
+ name: 'Iran Update',
47
+ agentId: 'schedule-route-agent',
48
+ taskPrompt: 'Daily check for updates on US-Iran tensions',
49
+ scheduleType: 'cron',
50
+ cron: '0 9 * * *',
51
+ status: 'active',
52
+ createdByAgentId: 'schedule-route-agent',
53
+ createdInSessionId: 'session-reminder',
54
+ createdAt: now,
55
+ updatedAt: now,
56
+ },
57
+ two: {
58
+ id: 'two',
59
+ name: 'Iran Reminder',
60
+ agentId: 'schedule-route-agent',
61
+ taskPrompt: 'Periodic update check for US-Iran tensions',
62
+ scheduleType: 'interval',
63
+ intervalMs: 86_400_000,
64
+ status: 'active',
65
+ createdByAgentId: 'schedule-route-agent',
66
+ createdInSessionId: 'session-reminder',
67
+ createdAt: now + 1,
68
+ updatedAt: now + 1,
69
+ },
70
+ })
71
+
72
+ const response = await updateSchedule(new Request('http://local/api/schedules/one', {
73
+ method: 'PUT',
74
+ headers: { 'content-type': 'application/json' },
75
+ body: JSON.stringify({ status: 'paused' }),
76
+ }), routeParams('one'))
77
+
78
+ assert.equal(response.status, 200)
79
+ const payload = await response.json() as Record<string, unknown>
80
+ assert.deepEqual(new Set(payload.affectedScheduleIds as string[]), new Set(['one', 'two']))
81
+
82
+ const schedules = loadSchedules()
83
+ assert.equal(schedules.one.status, 'paused')
84
+ assert.equal(schedules.two.status, 'paused')
85
+ })
86
+
87
+ test('DELETE /api/schedules/[id] deletes equivalent reminder schedules together', async () => {
88
+ seedAgent('schedule-route-agent-delete')
89
+ const now = Date.now()
90
+ saveSchedules({
91
+ one: {
92
+ id: 'one',
93
+ name: 'Iran Update',
94
+ agentId: 'schedule-route-agent-delete',
95
+ taskPrompt: 'Daily check for updates on US-Iran tensions',
96
+ scheduleType: 'cron',
97
+ cron: '0 9 * * *',
98
+ status: 'active',
99
+ createdByAgentId: 'schedule-route-agent-delete',
100
+ createdInSessionId: 'session-reminder',
101
+ createdAt: now,
102
+ updatedAt: now,
103
+ },
104
+ two: {
105
+ id: 'two',
106
+ name: 'Iran Reminder',
107
+ agentId: 'schedule-route-agent-delete',
108
+ taskPrompt: 'Periodic update check for US-Iran tensions',
109
+ scheduleType: 'interval',
110
+ intervalMs: 86_400_000,
111
+ status: 'active',
112
+ createdByAgentId: 'schedule-route-agent-delete',
113
+ createdInSessionId: 'session-reminder',
114
+ createdAt: now + 1,
115
+ updatedAt: now + 1,
116
+ },
117
+ })
118
+
119
+ const response = await deleteScheduleRoute(
120
+ new Request('http://local/api/schedules/one', { method: 'DELETE' }),
121
+ routeParams('one'),
122
+ )
123
+
124
+ assert.equal(response.status, 200)
125
+ const payload = await response.json() as Record<string, unknown>
126
+ assert.deepEqual(new Set(payload.deletedIds as string[]), new Set(['one', 'two']))
127
+ assert.deepEqual(loadSchedules(), {})
128
+ })