@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,6 +1,6 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { getCheckpointSaver } from '@/lib/server/langgraph-checkpoint'
3
- import { loadSessions, saveSessions } from '@/lib/server/storage'
3
+ import { loadSession, upsertSession } from '@/lib/server/storage'
4
4
  import { notify } from '@/lib/server/ws-hub'
5
5
 
6
6
  export const dynamic = 'force-dynamic'
@@ -9,28 +9,27 @@ export const dynamic = 'force-dynamic'
9
9
  export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
10
10
  const { id: sessionId } = await params
11
11
  const { checkpointId, timestamp } = await req.json()
12
-
12
+
13
13
  if (!checkpointId || !timestamp) {
14
14
  return NextResponse.json({ error: 'checkpointId and timestamp are required' }, { status: 400 })
15
15
  }
16
16
 
17
17
  const saver = getCheckpointSaver()
18
-
18
+
19
19
  // 1. Delete all checkpoints after the target one
20
20
  await saver.deleteCheckpointsAfter(sessionId, timestamp)
21
21
 
22
22
  // 2. Truncate messages in the session to match the timestamp
23
23
  // Both timestamp (from checkpoint.ts → getTime()) and Message.time use epoch milliseconds
24
- const sessions = loadSessions()
25
- const session = sessions[sessionId]
24
+ const session = loadSession(sessionId)
26
25
  if (session) {
27
26
  session.messages = session.messages.filter((m: { time: number }) => m.time <= timestamp)
28
27
  session.lastActiveAt = Date.now()
29
- saveSessions(sessions)
28
+ upsertSession(sessionId, session)
30
29
  }
31
30
 
32
31
  notify(`messages:${sessionId}`)
33
32
  notify('sessions')
34
-
33
+
35
34
  return NextResponse.json({ ok: true, restoredTo: checkpointId })
36
35
  }
@@ -1,11 +1,10 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { loadSessions, saveSessions } from '@/lib/server/storage'
2
+ import { loadSession, upsertSession } from '@/lib/server/storage'
3
3
  import { notFound } from '@/lib/server/collection-helpers'
4
4
 
5
5
  export async function POST(_req: Request, { params }: { params: Promise<{ id: string }> }) {
6
6
  const { id } = await params
7
- const sessions = loadSessions()
8
- const session = sessions[id]
7
+ const session = loadSession(id)
9
8
  if (!session) return notFound()
10
9
 
11
10
  const msgs = session.messages
@@ -23,7 +22,7 @@ export async function POST(_req: Request, { params }: { params: Promise<{ id: st
23
22
 
24
23
  // Remove the last user message too — it will be re-sent by the client
25
24
  msgs.pop()
26
- saveSessions(sessions)
25
+ upsertSession(id, session)
27
26
 
28
27
  return NextResponse.json({ message, imagePath })
29
28
  }
@@ -1,5 +1,5 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { loadSessions, saveSessions, deleteSession, active, loadAgents, loadStoredItem } from '@/lib/server/storage'
2
+ import { loadSession, upsertSession, deleteSession, active, loadAgents } from '@/lib/server/storage'
3
3
  import { notFound } from '@/lib/server/collection-helpers'
4
4
  import { normalizeProviderEndpoint } from '@/lib/openclaw-endpoint'
5
5
  import { resolvePrimaryAgentRoute } from '@/lib/server/agent-runtime-config'
@@ -8,7 +8,7 @@ import type { Session } from '@/types'
8
8
 
9
9
  export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
10
10
  const { id } = await params
11
- const session = loadStoredItem('sessions', id) as Session | null
11
+ const session = loadSession(id)
12
12
  if (!session) return notFound()
13
13
 
14
14
  const run = getSessionRunState(id)
@@ -22,109 +22,108 @@ export async function GET(_req: Request, { params }: { params: Promise<{ id: str
22
22
  export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
23
23
  const { id } = await params
24
24
  const updates = await req.json()
25
- const sessions = loadSessions()
26
- if (!sessions[id]) return notFound()
25
+ const session = loadSession(id) as Record<string, unknown> | null
26
+ if (!session) return notFound()
27
27
 
28
28
  const agentIdUpdateProvided = updates.agentId !== undefined
29
- let nextAgentId = sessions[id].agentId
29
+ let nextAgentId = session.agentId
30
30
  if (agentIdUpdateProvided) {
31
- sessions[id].agentId = updates.agentId
31
+ session.agentId = updates.agentId
32
32
  nextAgentId = updates.agentId
33
33
  }
34
34
 
35
- const linkedAgent = nextAgentId ? loadAgents()[nextAgentId] : null
35
+ const linkedAgent = nextAgentId ? loadAgents()[nextAgentId as string] : null
36
36
  const routePreferredGatewayTags = updates.routePreferredGatewayTags !== undefined
37
37
  ? (Array.isArray(updates.routePreferredGatewayTags)
38
38
  ? updates.routePreferredGatewayTags.filter((tag: unknown): tag is string => typeof tag === 'string' && tag.trim().length > 0)
39
39
  : [])
40
- : (sessions[id].routePreferredGatewayTags || [])
40
+ : ((session.routePreferredGatewayTags as string[]) || [])
41
41
  const routePreferredGatewayUseCase = updates.routePreferredGatewayUseCase !== undefined
42
42
  ? (typeof updates.routePreferredGatewayUseCase === 'string' && updates.routePreferredGatewayUseCase.trim()
43
43
  ? updates.routePreferredGatewayUseCase.trim()
44
44
  : null)
45
- : (sessions[id].routePreferredGatewayUseCase || null)
45
+ : ((session.routePreferredGatewayUseCase as string | null) || null)
46
46
  const linkedRoute = linkedAgent ? resolvePrimaryAgentRoute(linkedAgent, undefined, {
47
47
  preferredGatewayTags: routePreferredGatewayTags,
48
48
  preferredGatewayUseCase: routePreferredGatewayUseCase,
49
49
  }) : null
50
50
 
51
- if (updates.name !== undefined) sessions[id].name = updates.name
52
- if (updates.cwd !== undefined) sessions[id].cwd = updates.cwd
53
- if (updates.provider !== undefined) sessions[id].provider = updates.provider
54
- else if (agentIdUpdateProvided && linkedAgent?.provider) sessions[id].provider = linkedAgent.provider
51
+ if (updates.name !== undefined) session.name = updates.name
52
+ if (updates.cwd !== undefined) session.cwd = updates.cwd
53
+ if (updates.provider !== undefined) session.provider = updates.provider
54
+ else if (agentIdUpdateProvided && linkedAgent?.provider) session.provider = linkedAgent.provider
55
55
 
56
- if (updates.model !== undefined) sessions[id].model = updates.model
57
- else if (agentIdUpdateProvided && linkedRoute?.model) sessions[id].model = linkedRoute.model
58
- else if (agentIdUpdateProvided && linkedAgent?.model !== undefined) sessions[id].model = linkedAgent.model
56
+ if (updates.model !== undefined) session.model = updates.model
57
+ else if (agentIdUpdateProvided && linkedRoute?.model) session.model = linkedRoute.model
58
+ else if (agentIdUpdateProvided && linkedAgent?.model !== undefined) session.model = linkedAgent.model
59
59
 
60
- if (updates.credentialId !== undefined) sessions[id].credentialId = updates.credentialId
61
- else if (agentIdUpdateProvided && linkedRoute) sessions[id].credentialId = linkedRoute.credentialId ?? null
62
- else if (agentIdUpdateProvided && linkedAgent) sessions[id].credentialId = linkedAgent.credentialId ?? null
60
+ if (updates.credentialId !== undefined) session.credentialId = updates.credentialId
61
+ else if (agentIdUpdateProvided && linkedRoute) session.credentialId = linkedRoute.credentialId ?? null
62
+ else if (agentIdUpdateProvided && linkedAgent) session.credentialId = linkedAgent.credentialId ?? null
63
63
 
64
- if (updates.fallbackCredentialIds !== undefined) sessions[id].fallbackCredentialIds = updates.fallbackCredentialIds
65
- else if (agentIdUpdateProvided && linkedRoute) sessions[id].fallbackCredentialIds = [...linkedRoute.fallbackCredentialIds]
64
+ if (updates.fallbackCredentialIds !== undefined) session.fallbackCredentialIds = updates.fallbackCredentialIds
65
+ else if (agentIdUpdateProvided && linkedRoute) session.fallbackCredentialIds = [...linkedRoute.fallbackCredentialIds]
66
66
 
67
- if (updates.gatewayProfileId !== undefined) sessions[id].gatewayProfileId = updates.gatewayProfileId
68
- else if (agentIdUpdateProvided && linkedRoute) sessions[id].gatewayProfileId = linkedRoute.gatewayProfileId ?? null
67
+ if (updates.gatewayProfileId !== undefined) session.gatewayProfileId = updates.gatewayProfileId
68
+ else if (agentIdUpdateProvided && linkedRoute) session.gatewayProfileId = linkedRoute.gatewayProfileId ?? null
69
69
 
70
70
  if (updates.routePreferredGatewayTags !== undefined) {
71
- sessions[id].routePreferredGatewayTags = routePreferredGatewayTags
71
+ session.routePreferredGatewayTags = routePreferredGatewayTags
72
72
  }
73
73
  if (updates.routePreferredGatewayUseCase !== undefined) {
74
- sessions[id].routePreferredGatewayUseCase = routePreferredGatewayUseCase
74
+ session.routePreferredGatewayUseCase = routePreferredGatewayUseCase
75
75
  }
76
76
 
77
- if (updates.plugins !== undefined) sessions[id].plugins = updates.plugins
78
- else if (agentIdUpdateProvided && linkedAgent) sessions[id].plugins = Array.isArray(linkedAgent.plugins) ? linkedAgent.plugins : []
77
+ if (updates.plugins !== undefined) session.plugins = updates.plugins
78
+ else if (agentIdUpdateProvided && linkedAgent) session.plugins = Array.isArray(linkedAgent.plugins) ? linkedAgent.plugins : []
79
79
 
80
80
  if (updates.apiEndpoint !== undefined) {
81
- sessions[id].apiEndpoint = normalizeProviderEndpoint(
82
- updates.provider || sessions[id].provider,
81
+ session.apiEndpoint = normalizeProviderEndpoint(
82
+ (updates.provider || session.provider) as string,
83
83
  updates.apiEndpoint,
84
84
  )
85
85
  } else if (agentIdUpdateProvided && linkedRoute) {
86
- sessions[id].apiEndpoint = linkedRoute.apiEndpoint ?? null
86
+ session.apiEndpoint = linkedRoute.apiEndpoint ?? null
87
87
  } else if (agentIdUpdateProvided && linkedAgent) {
88
- sessions[id].apiEndpoint = normalizeProviderEndpoint(
88
+ session.apiEndpoint = normalizeProviderEndpoint(
89
89
  linkedAgent.provider,
90
90
  linkedAgent.apiEndpoint ?? null,
91
91
  )
92
92
  }
93
- if (updates.heartbeatEnabled !== undefined) sessions[id].heartbeatEnabled = updates.heartbeatEnabled
94
- if (updates.heartbeatIntervalSec !== undefined) sessions[id].heartbeatIntervalSec = updates.heartbeatIntervalSec
95
- if (updates.sessionResetMode !== undefined) sessions[id].sessionResetMode = updates.sessionResetMode
96
- if (updates.sessionIdleTimeoutSec !== undefined) sessions[id].sessionIdleTimeoutSec = updates.sessionIdleTimeoutSec
97
- if (updates.sessionMaxAgeSec !== undefined) sessions[id].sessionMaxAgeSec = updates.sessionMaxAgeSec
98
- if (updates.sessionDailyResetAt !== undefined) sessions[id].sessionDailyResetAt = updates.sessionDailyResetAt
99
- if (updates.sessionResetTimezone !== undefined) sessions[id].sessionResetTimezone = updates.sessionResetTimezone
100
- if (updates.thinkingLevel !== undefined) sessions[id].thinkingLevel = updates.thinkingLevel
101
- if (updates.connectorThinkLevel !== undefined) sessions[id].connectorThinkLevel = updates.connectorThinkLevel
102
- if (updates.connectorSessionScope !== undefined) sessions[id].connectorSessionScope = updates.connectorSessionScope
103
- if (updates.connectorReplyMode !== undefined) sessions[id].connectorReplyMode = updates.connectorReplyMode
104
- if (updates.connectorThreadBinding !== undefined) sessions[id].connectorThreadBinding = updates.connectorThreadBinding
105
- if (updates.connectorGroupPolicy !== undefined) sessions[id].connectorGroupPolicy = updates.connectorGroupPolicy
106
- if (updates.connectorIdleTimeoutSec !== undefined) sessions[id].connectorIdleTimeoutSec = updates.connectorIdleTimeoutSec
107
- if (updates.connectorMaxAgeSec !== undefined) sessions[id].connectorMaxAgeSec = updates.connectorMaxAgeSec
108
- if (updates.connectorContext !== undefined) sessions[id].connectorContext = updates.connectorContext
109
- if (updates.identityState !== undefined) sessions[id].identityState = updates.identityState
110
- if (updates.sessionArchiveState !== undefined) sessions[id].sessionArchiveState = updates.sessionArchiveState
111
- if (updates.lastSessionResetAt !== undefined) sessions[id].lastSessionResetAt = updates.lastSessionResetAt
112
- if (updates.lastSessionResetReason !== undefined) sessions[id].lastSessionResetReason = updates.lastSessionResetReason
113
- if (updates.pinned !== undefined) sessions[id].pinned = !!updates.pinned
114
- if (updates.claudeSessionId !== undefined) sessions[id].claudeSessionId = updates.claudeSessionId
115
- if (updates.codexThreadId !== undefined) sessions[id].codexThreadId = updates.codexThreadId
116
- if (updates.opencodeSessionId !== undefined) sessions[id].opencodeSessionId = updates.opencodeSessionId
117
- if (updates.delegateResumeIds !== undefined) sessions[id].delegateResumeIds = updates.delegateResumeIds
118
- if (!Array.isArray(sessions[id].messages)) sessions[id].messages = []
119
-
120
- saveSessions(sessions)
121
- return NextResponse.json(sessions[id])
93
+ if (updates.heartbeatEnabled !== undefined) session.heartbeatEnabled = updates.heartbeatEnabled
94
+ if (updates.heartbeatIntervalSec !== undefined) session.heartbeatIntervalSec = updates.heartbeatIntervalSec
95
+ if (updates.sessionResetMode !== undefined) session.sessionResetMode = updates.sessionResetMode
96
+ if (updates.sessionIdleTimeoutSec !== undefined) session.sessionIdleTimeoutSec = updates.sessionIdleTimeoutSec
97
+ if (updates.sessionMaxAgeSec !== undefined) session.sessionMaxAgeSec = updates.sessionMaxAgeSec
98
+ if (updates.sessionDailyResetAt !== undefined) session.sessionDailyResetAt = updates.sessionDailyResetAt
99
+ if (updates.sessionResetTimezone !== undefined) session.sessionResetTimezone = updates.sessionResetTimezone
100
+ if (updates.thinkingLevel !== undefined) session.thinkingLevel = updates.thinkingLevel
101
+ if (updates.connectorThinkLevel !== undefined) session.connectorThinkLevel = updates.connectorThinkLevel
102
+ if (updates.connectorSessionScope !== undefined) session.connectorSessionScope = updates.connectorSessionScope
103
+ if (updates.connectorReplyMode !== undefined) session.connectorReplyMode = updates.connectorReplyMode
104
+ if (updates.connectorThreadBinding !== undefined) session.connectorThreadBinding = updates.connectorThreadBinding
105
+ if (updates.connectorGroupPolicy !== undefined) session.connectorGroupPolicy = updates.connectorGroupPolicy
106
+ if (updates.connectorIdleTimeoutSec !== undefined) session.connectorIdleTimeoutSec = updates.connectorIdleTimeoutSec
107
+ if (updates.connectorMaxAgeSec !== undefined) session.connectorMaxAgeSec = updates.connectorMaxAgeSec
108
+ if (updates.connectorContext !== undefined) session.connectorContext = updates.connectorContext
109
+ if (updates.identityState !== undefined) session.identityState = updates.identityState
110
+ if (updates.sessionArchiveState !== undefined) session.sessionArchiveState = updates.sessionArchiveState
111
+ if (updates.lastSessionResetAt !== undefined) session.lastSessionResetAt = updates.lastSessionResetAt
112
+ if (updates.lastSessionResetReason !== undefined) session.lastSessionResetReason = updates.lastSessionResetReason
113
+ if (updates.pinned !== undefined) session.pinned = !!updates.pinned
114
+ if (updates.claudeSessionId !== undefined) session.claudeSessionId = updates.claudeSessionId
115
+ if (updates.codexThreadId !== undefined) session.codexThreadId = updates.codexThreadId
116
+ if (updates.opencodeSessionId !== undefined) session.opencodeSessionId = updates.opencodeSessionId
117
+ if (updates.delegateResumeIds !== undefined) session.delegateResumeIds = updates.delegateResumeIds
118
+ if (!Array.isArray(session.messages)) session.messages = []
119
+
120
+ upsertSession(id, session)
121
+ return NextResponse.json(session)
122
122
  }
123
123
 
124
124
  export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
125
125
  const { id } = await params
126
- const sessions = loadSessions()
127
- if (!sessions[id]) return notFound()
126
+ if (!loadSession(id)) return notFound()
128
127
  if (active.has(id)) {
129
128
  try { active.get(id)?.kill() } catch {}
130
129
  active.delete(id)
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
2
2
  import { genId } from '@/lib/id'
3
3
  import os from 'os'
4
4
  import path from 'path'
5
+ import { perf } from '@/lib/server/perf'
5
6
  import { loadSessions, saveSessions, deleteSession, active, loadAgents, upsertStoredItem } from '@/lib/server/storage'
6
7
  import { WORKSPACE_DIR } from '@/lib/server/data-dir'
7
8
  import { notify } from '@/lib/server/ws-hub'
@@ -20,6 +21,7 @@ async function ensureDaemonIfNeeded(source: string) {
20
21
 
21
22
 
22
23
  export async function GET(req: Request) {
24
+ const endPerf = perf.start('api', 'GET /api/chats')
23
25
  const sessions = loadSessions()
24
26
  const changedSessionIds: string[] = []
25
27
  for (const id of Object.keys(sessions)) {
@@ -45,12 +47,16 @@ export async function GET(req: Request) {
45
47
 
46
48
  const { searchParams } = new URL(req.url)
47
49
  const limitParam = searchParams.get('limit')
48
- if (!limitParam) return NextResponse.json(summarized)
50
+ if (!limitParam) {
51
+ endPerf({ count: Object.keys(summarized).length })
52
+ return NextResponse.json(summarized)
53
+ }
49
54
 
50
55
  const limit = Math.max(1, Number(limitParam) || 50)
51
56
  const offset = Math.max(0, Number(searchParams.get('offset')) || 0)
52
57
  const all = Object.values(summarized).sort((a, b) => (b.lastActiveAt ?? b.createdAt) - (a.lastActiveAt ?? a.createdAt))
53
58
  const items = all.slice(offset, offset + limit)
59
+ endPerf({ count: items.length, total: all.length })
54
60
  return NextResponse.json({ items, total: all.length, hasMore: offset + limit < all.length })
55
61
  }
56
62
 
@@ -1,8 +1,9 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { loadConnectors, saveConnectors, logActivity } from '@/lib/server/storage'
2
+ import { loadConnectors, logActivity, upsertStoredItem, deleteStoredItem } from '@/lib/server/storage'
3
3
  import { notify } from '@/lib/server/ws-hub'
4
4
  import { notFound } from '@/lib/server/collection-helpers'
5
5
  import { ensureDaemonStarted } from '@/lib/server/daemon-state'
6
+ import { errorMessage } from '@/lib/shared-utils'
6
7
 
7
8
  export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
8
9
  ensureDaemonStarted('api/connectors/[id]:get')
@@ -68,7 +69,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
68
69
  } catch (err: unknown) {
69
70
  // Re-read to get the error state saved by startConnector
70
71
  const fresh = loadConnectors()
71
- return NextResponse.json(fresh[id] || { error: err instanceof Error ? err.message : String(err) }, { status: 500 })
72
+ return NextResponse.json(fresh[id] || { error: errorMessage(err) }, { status: 500 })
72
73
  }
73
74
  // Re-read the connector after manager modified it
74
75
  const fresh = loadConnectors()
@@ -82,11 +83,10 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
82
83
  if (body.chatroomId !== undefined) connector.chatroomId = body.chatroomId
83
84
  if (body.credentialId !== undefined) connector.credentialId = body.credentialId
84
85
  if (body.config !== undefined) connector.config = body.config
85
- if (body.isEnabled !== undefined) connector.isEnabled = body.isEnabled
86
- connector.updatedAt = Date.now()
86
+ if (body.isEnabled !== undefined) (connector as any).isEnabled = body.isEnabled
87
+ (connector as any).updatedAt = Date.now()
87
88
 
88
- connectors[id] = connector
89
- saveConnectors(connectors)
89
+ upsertStoredItem('connectors', id, connector)
90
90
 
91
91
  try {
92
92
  const manager = await import('@/lib/server/connectors/manager')
@@ -133,8 +133,7 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
133
133
  clearConnectorPairingState(id)
134
134
  } catch { /* ignore */ }
135
135
 
136
- delete connectors[id]
137
- saveConnectors(connectors)
136
+ deleteStoredItem('connectors', id)
138
137
  notify('connectors')
139
138
  return NextResponse.json({ ok: true })
140
139
  }
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { genId } from '@/lib/id'
3
- import { loadConnectors, saveConnectors } from '@/lib/server/storage'
3
+ import { perf } from '@/lib/server/perf'
4
+ import { loadConnectors, upsertStoredItem } from '@/lib/server/storage'
4
5
  import { notify } from '@/lib/server/ws-hub'
5
6
  import { ensureDaemonStarted } from '@/lib/server/daemon-state'
6
7
  import { ConnectorCreateSchema, formatZodError } from '@/lib/validation/schemas'
@@ -10,6 +11,7 @@ export const dynamic = 'force-dynamic'
10
11
 
11
12
 
12
13
  export async function GET() {
14
+ const endPerf = perf.start('api', 'GET /api/connectors')
13
15
  ensureDaemonStarted('api/connectors:get')
14
16
  const connectors = loadConnectors()
15
17
  // Merge runtime status from manager
@@ -39,6 +41,7 @@ export async function GET() {
39
41
  }
40
42
  }
41
43
  } catch { /* manager not loaded yet */ }
44
+ endPerf({ count: Object.keys(connectors).length })
42
45
  return NextResponse.json(connectors)
43
46
  }
44
47
 
@@ -50,7 +53,6 @@ export async function POST(req: Request) {
50
53
  return NextResponse.json(formatZodError(parsed.error as z.ZodError), { status: 400 })
51
54
  }
52
55
  const body = parsed.data
53
- const connectors = loadConnectors()
54
56
  const id = genId()
55
57
 
56
58
  const connector: Connector = {
@@ -68,8 +70,7 @@ export async function POST(req: Request) {
68
70
  updatedAt: Date.now(),
69
71
  }
70
72
 
71
- connectors[id] = connector
72
- saveConnectors(connectors)
73
+ upsertStoredItem('connectors', id, connector)
73
74
  notify('connectors')
74
75
 
75
76
  // Auto-start if connector has credentials (or is WhatsApp which uses QR)
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
2
2
  import { z } from 'zod'
3
3
  import { runEvalScenario } from '@/lib/server/eval/runner'
4
4
  import { listEvalRuns } from '@/lib/server/eval/store'
5
+ import { errorMessage } from '@/lib/shared-utils'
5
6
 
6
7
  const RunSchema = z.object({
7
8
  scenarioId: z.string().min(1),
@@ -23,7 +24,7 @@ export async function POST(req: Request) {
23
24
  return NextResponse.json(result)
24
25
  } catch (err: unknown) {
25
26
  return NextResponse.json(
26
- { error: err instanceof Error ? err.message : String(err) },
27
+ { error: errorMessage(err) },
27
28
  { status: 500 },
28
29
  )
29
30
  }
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { z } from 'zod'
3
3
  import { runEvalSuite } from '@/lib/server/eval/runner'
4
+ import { errorMessage } from '@/lib/shared-utils'
4
5
 
5
6
  const SuiteSchema = z.object({
6
7
  agentId: z.string().min(1),
@@ -22,7 +23,7 @@ export async function POST(req: Request) {
22
23
  return NextResponse.json(result)
23
24
  } catch (err: unknown) {
24
25
  return NextResponse.json(
25
- { error: err instanceof Error ? err.message : String(err) },
26
+ { error: errorMessage(err) },
26
27
  { status: 500 },
27
28
  )
28
29
  }
@@ -42,7 +42,7 @@ test('external agent register + heartbeat derives gateway metadata in listing',
42
42
  discoveredPort: 19999,
43
43
  deployment: {
44
44
  method: 'imported',
45
- managedBy: 'external',
45
+ managedBy: "external" as any,
46
46
  useCase: 'single-vps',
47
47
  exposure: 'private-lan',
48
48
  targetHost: '127.0.0.1',
@@ -14,9 +14,9 @@ function withDerivedStatus(record: ExternalAgentRuntime): ExternalAgentRuntime {
14
14
  if (!lastSeenAt) return { ...record, status: record.status || 'offline' }
15
15
  if (record.status === 'offline') return record
16
16
  const gateways = loadGatewayProfiles()
17
- const gateway = record.gatewayProfileId ? gateways[record.gatewayProfileId] as Record<string, unknown> | undefined : undefined
17
+ const gateway = record.gatewayProfileId ? gateways[record.gatewayProfileId] as any : undefined
18
18
  const gatewayTags = Array.isArray(gateway?.tags)
19
- ? gateway?.tags.filter((tag): tag is string => typeof tag === 'string' && tag.trim().length > 0)
19
+ ? (gateway as any)?.tags?.filter((tag: any): tag is string => typeof tag === 'string' && tag.trim().length > 0)
20
20
  : []
21
21
  const gatewayUseCase = gateway?.deployment && typeof gateway.deployment === 'object' && typeof (gateway.deployment as Record<string, unknown>).useCase === 'string'
22
22
  ? (gateway.deployment as Record<string, unknown>).useCase as string
@@ -16,7 +16,7 @@ const MIME_MAP: Record<string, string> = {
16
16
  '.webp': 'image/webp',
17
17
  '.txt': 'text/plain',
18
18
  '.md': 'text/markdown',
19
- '.ts': 'text/plain',
19
+ '': 'text/plain',
20
20
  '.tsx': 'text/plain',
21
21
  '.jsx': 'text/plain',
22
22
  '.py': 'text/plain',
@@ -1,6 +1,6 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { normalizeOpenClawEndpoint } from '@/lib/openclaw-endpoint'
3
- import { loadAgents, loadGatewayProfiles, saveAgents, saveGatewayProfiles } from '@/lib/server/storage'
3
+ import { loadAgents, loadGatewayProfiles, saveGatewayProfiles, upsertAgent } from '@/lib/server/storage'
4
4
  import { mutateItem, notFound, type CollectionOps } from '@/lib/server/collection-helpers'
5
5
  import type { Agent, AgentRoutingTarget, GatewayProfile, OpenClawDeploymentConfig, OpenClawGatewayStats } from '@/types'
6
6
 
@@ -109,11 +109,12 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
109
109
  saveGatewayProfiles(gateways)
110
110
 
111
111
  const agents = loadAgents({ includeTrashed: true })
112
- let agentChanged = false
112
+ const changedAgents: Agent[] = []
113
113
  for (const agent of Object.values(agents) as Agent[]) {
114
+ let changed = false
114
115
  if (agent.gatewayProfileId === id) {
115
116
  agent.gatewayProfileId = null
116
- agentChanged = true
117
+ changed = true
117
118
  }
118
119
  if (Array.isArray(agent.routingTargets)) {
119
120
  const nextTargets = agent.routingTargets.map((target: AgentRoutingTarget) => (
@@ -123,11 +124,12 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
123
124
  ))
124
125
  if (JSON.stringify(nextTargets) !== JSON.stringify(agent.routingTargets)) {
125
126
  agent.routingTargets = nextTargets
126
- agentChanged = true
127
+ changed = true
127
128
  }
128
129
  }
130
+ if (changed) changedAgents.push(agent)
129
131
  }
130
- if (agentChanged) saveAgents(agents)
132
+ for (const agent of changedAgents) upsertAgent(agent.id, agent)
131
133
 
132
134
  return NextResponse.json({ ok: true })
133
135
  }
@@ -79,7 +79,7 @@ export async function POST(req: Request) {
79
79
  const isDefault = body.isDefault === true
80
80
 
81
81
  if (isDefault) {
82
- for (const gateway of Object.values(gateways) as Array<Record<string, unknown>>) {
82
+ for (const gateway of Object.values(gateways) as any[]) {
83
83
  gateway.isDefault = false
84
84
  }
85
85
  }
@@ -7,7 +7,7 @@ import { UPLOAD_DIR } from '@/lib/server/storage'
7
7
  const TEXT_EXTS = new Set([
8
8
  '.txt', '.md', '.markdown', '.csv', '.tsv', '.json', '.jsonl',
9
9
  '.html', '.htm', '.xml', '.yaml', '.yml', '.toml', '.ini', '.cfg',
10
- '.js', '.ts', '.tsx', '.jsx', '.py', '.go', '.rs', '.java', '.c', '.cpp', '.h',
10
+ '.js', '', '.tsx', '.jsx', '.py', '.go', '.rs', '.java', '.c', '.cpp', '.h',
11
11
  '.rb', '.php', '.sh', '.bash', '.zsh', '.sql', '.r', '.swift', '.kt',
12
12
  '.env', '.log', '.conf', '.properties', '.gitignore', '.dockerignore',
13
13
  ])
@@ -1,8 +1,6 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import fs from 'fs'
3
- import path from 'path'
4
-
5
- const LOG_FILE = path.join(process.cwd(), 'data', 'app.log')
3
+ import { APP_LOG_PATH } from '@/lib/server/data-dir'
6
4
 
7
5
  export async function GET(req: Request) {
8
6
  const { searchParams } = new URL(req.url)
@@ -11,11 +9,11 @@ export async function GET(req: Request) {
11
9
  const search = searchParams.get('search') || ''
12
10
 
13
11
  try {
14
- if (!fs.existsSync(LOG_FILE)) {
12
+ if (!fs.existsSync(APP_LOG_PATH)) {
15
13
  return NextResponse.json({ entries: [], total: 0 })
16
14
  }
17
15
 
18
- const content = fs.readFileSync(LOG_FILE, 'utf8')
16
+ const content = fs.readFileSync(APP_LOG_PATH, 'utf8')
19
17
  let allLines = content.split('\n').filter(Boolean)
20
18
 
21
19
  // Filter by level
@@ -42,8 +40,8 @@ export async function GET(req: Request) {
42
40
 
43
41
  export async function DELETE() {
44
42
  try {
45
- if (fs.existsSync(LOG_FILE)) {
46
- fs.writeFileSync(LOG_FILE, '')
43
+ if (fs.existsSync(APP_LOG_PATH)) {
44
+ fs.writeFileSync(APP_LOG_PATH, '')
47
45
  }
48
46
  return NextResponse.json({ ok: true })
49
47
  } catch (err: any) {
@@ -1,10 +1,9 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { notFound } from '@/lib/server/collection-helpers'
3
+ import { MEMORY_IMAGES_DIR } from '@/lib/server/data-dir'
3
4
  import fs from 'fs'
4
5
  import path from 'path'
5
6
 
6
- const IMAGES_DIR = path.join(process.cwd(), 'data', 'memory-images')
7
-
8
7
  const MIME_TYPES: Record<string, string> = {
9
8
  '.png': 'image/png',
10
9
  '.jpg': 'image/jpeg',
@@ -18,7 +17,7 @@ const MIME_TYPES: Record<string, string> = {
18
17
  export async function GET(_req: Request, { params }: { params: Promise<{ filename: string }> }) {
19
18
  const { filename } = await params
20
19
  const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, '')
21
- const filePath = path.join(IMAGES_DIR, safeName)
20
+ const filePath = path.join(MEMORY_IMAGES_DIR, safeName)
22
21
 
23
22
  if (!fs.existsSync(filePath)) {
24
23
  return notFound()
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { ensureGatewayConnected } from '@/lib/server/openclaw-gateway'
3
3
  import { resolveOpenClawGatewayAgentId } from '@/lib/server/openclaw-agent-resolver'
4
+ import { errorMessage } from '@/lib/shared-utils'
4
5
 
5
6
  const AGENT_FILES = ['SOUL.md', 'IDENTITY.md', 'USER.md', 'TOOLS.md', 'HEARTBEAT.md', 'MEMORY.md', 'AGENTS.md'] as const
6
7
 
@@ -21,7 +22,7 @@ export async function GET(req: Request) {
21
22
  try {
22
23
  gatewayAgentId = await resolveOpenClawGatewayAgentId(agentId, gw)
23
24
  } catch (err: unknown) {
24
- const message = err instanceof Error ? err.message : String(err)
25
+ const message = errorMessage(err)
25
26
  const status = message.includes('not an OpenClaw agent') ? 400 : 404
26
27
  return NextResponse.json({ error: message }, { status })
27
28
  }
@@ -36,7 +37,7 @@ export async function GET(req: Request) {
36
37
  }) as { file?: { content?: string } } | undefined
37
38
  files[filename] = { content: result?.file?.content ?? '' }
38
39
  } catch (err: unknown) {
39
- files[filename] = { content: '', error: err instanceof Error ? err.message : String(err) }
40
+ files[filename] = { content: '', error: errorMessage(err) }
40
41
  }
41
42
  }),
42
43
  )
@@ -69,7 +70,7 @@ export async function PUT(req: Request) {
69
70
  })
70
71
  return NextResponse.json({ ok: true })
71
72
  } catch (err: unknown) {
72
- const message = err instanceof Error ? err.message : String(err)
73
+ const message = errorMessage(err)
73
74
  const status = message.includes('not an OpenClaw agent')
74
75
  ? 400
75
76
  : message.includes('not found')
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { ensureGatewayConnected, getGateway } from '@/lib/server/openclaw-gateway'
3
3
  import type { PendingExecApproval, ExecApprovalDecision } from '@/types'
4
+ import { errorMessage, hmrSingleton } from '@/lib/shared-utils'
4
5
 
5
6
  /** GET — fetch pending execution approvals from gateway */
6
7
  export async function GET() {
@@ -18,9 +19,7 @@ export async function GET() {
18
19
  }
19
20
 
20
21
  /* ── Conflict-detection: track recently resolved approval IDs in-process ── */
21
- const resolvedKey = '__swarmclaw_resolved_approvals__'
22
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
- const resolved: Map<string, number> = (globalThis as any)[resolvedKey] ?? ((globalThis as any)[resolvedKey] = new Map<string, number>())
22
+ const resolved: Map<string, number> = hmrSingleton('__swarmclaw_resolved_approvals__', () => new Map<string, number>())
24
23
  const RESOLVED_TTL_MS = 5 * 60 * 1000
25
24
 
26
25
  function pruneResolved() {
@@ -60,7 +59,7 @@ export async function POST(req: Request) {
60
59
  resolved.set(id, Date.now())
61
60
  return NextResponse.json({ ok: true })
62
61
  } catch (err: unknown) {
63
- const message = err instanceof Error ? err.message : String(err)
62
+ const message = errorMessage(err)
64
63
  return NextResponse.json({ error: message }, { status: 502 })
65
64
  }
66
65
  }