@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
@@ -5,10 +5,13 @@ import os from 'os'
5
5
  import type { ChildProcess } from 'node:child_process'
6
6
  import Database from 'better-sqlite3'
7
7
 
8
+ import { perf } from './perf'
8
9
  import { DATA_DIR, IS_BUILD_BOOTSTRAP, WORKSPACE_DIR } from './data-dir'
10
+ import { safeJsonParseObject } from './json-utils'
9
11
  import { normalizeHeartbeatSettingFields } from '@/lib/heartbeat-defaults'
10
12
  import { normalizeRuntimeSettingFields } from '@/lib/runtime-loop'
11
- import type { AppNotification, ExternalAgentRuntime, GatewayProfile, Message } from '@/types'
13
+ import type { AppNotification, BoardTask, ExternalAgentRuntime, GatewayProfile, Message, Session } from '@/types'
14
+ import { dedup, hmrSingleton } from '@/lib/shared-utils'
12
15
  export const UPLOAD_DIR = path.join(DATA_DIR, 'uploads')
13
16
 
14
17
  // --- TTL Cache (read-through with write-through invalidation) ---
@@ -44,23 +47,13 @@ class TTLCache<T> {
44
47
  }
45
48
  }
46
49
 
47
- const ttlCacheKey = '__swarmclaw_ttl_caches__' as const
48
50
  type TTLCacheStore = {
49
51
  settings?: TTLCache<Record<string, unknown>>
50
- credentials?: TTLCache<Record<string, unknown>>
51
- connectors?: TTLCache<Record<string, unknown>>
52
- gatewayProfiles?: TTLCache<Record<string, unknown>>
53
52
  agents?: TTLCache<Record<string, unknown>>
54
53
  }
55
- type TTLGlobals = typeof globalThis & { [ttlCacheKey]?: TTLCacheStore }
56
- const ttlGlobals = globalThis as TTLGlobals
57
- const ttlCaches: TTLCacheStore = ttlGlobals[ttlCacheKey] ?? (ttlGlobals[ttlCacheKey] = {})
54
+ const ttlCaches: TTLCacheStore = hmrSingleton<TTLCacheStore>('__swarmclaw_ttl_caches__', () => ({}))
58
55
 
59
- // Lazily initialize each cache with its TTL
60
56
  function getSettingsCache() { return ttlCaches.settings ?? (ttlCaches.settings = new TTLCache(60_000)) }
61
- function getCredentialsCache() { return ttlCaches.credentials ?? (ttlCaches.credentials = new TTLCache(90_000)) }
62
- function getConnectorsCache() { return ttlCaches.connectors ?? (ttlCaches.connectors = new TTLCache(30_000)) }
63
- function getGatewayProfilesCache() { return ttlCaches.gatewayProfiles ?? (ttlCaches.gatewayProfiles = new TTLCache(300_000)) }
64
57
  function getAgentsCache() { return ttlCaches.agents ?? (ttlCaches.agents = new TTLCache(15_000)) }
65
58
 
66
59
  // --- LRU Cache ---
@@ -71,17 +64,13 @@ const DEFAULT_LRU_CAPACITY = 5000
71
64
  function parseCacheLimits(): Record<string, number> {
72
65
  const raw = process.env.COLLECTION_CACHE_LIMITS
73
66
  if (!raw) return {}
74
- try {
75
- const parsed: unknown = JSON.parse(raw)
76
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return {}
77
- const result: Record<string, number> = {}
78
- for (const [k, v] of Object.entries(parsed as Record<string, unknown>)) {
79
- if (typeof v === 'number' && v > 0) result[k] = v
80
- }
81
- return result
82
- } catch {
83
- return {}
67
+ const parsed = safeJsonParseObject(raw)
68
+ if (!parsed) return {}
69
+ const result: Record<string, number> = {}
70
+ for (const [k, v] of Object.entries(parsed)) {
71
+ if (typeof v === 'number' && v > 0) result[k] = v
84
72
  }
73
+ return result
85
74
  }
86
75
 
87
76
  const cacheLimits = parseCacheLimits()
@@ -171,20 +160,14 @@ if (!IS_BUILD_BOOTSTRAP) {
171
160
  }
172
161
  db.pragma('foreign_keys = ON')
173
162
 
174
- const collectionCacheKey = '__swarmclaw_storage_collection_cache__' as const
175
163
  type StoredObject = Record<string, unknown>
176
164
  type ActiveProcess = ChildProcess | {
177
165
  runId?: string | null
178
166
  source?: string
179
167
  kill: (signal?: NodeJS.Signals | number) => boolean | void
180
168
  }
181
- type StorageGlobals = typeof globalThis & {
182
- [collectionCacheKey]?: Map<string, LRUMap<string, string>>
183
- }
184
- const storageGlobals = globalThis as StorageGlobals
185
169
  const collectionCache: Map<string, LRUMap<string, string>> =
186
- storageGlobals[collectionCacheKey]
187
- ?? (storageGlobals[collectionCacheKey] = new Map<string, LRUMap<string, string>>())
170
+ hmrSingleton('__swarmclaw_storage_collection_cache__', () => new Map<string, LRUMap<string, string>>())
188
171
 
189
172
  // Collection tables (id → JSON blob)
190
173
  const COLLECTIONS = [
@@ -214,6 +197,7 @@ const COLLECTIONS = [
214
197
  'wallet_balance_history',
215
198
  'moderation_logs',
216
199
  'connector_health',
200
+ 'connector_outbox',
217
201
  'souls',
218
202
  'benchmarks',
219
203
  'approvals',
@@ -234,6 +218,12 @@ db.exec(`CREATE TABLE IF NOT EXISTS settings (id INTEGER PRIMARY KEY CHECK (id =
234
218
  db.exec(`CREATE TABLE IF NOT EXISTS queue (id INTEGER PRIMARY KEY CHECK (id = 1), data TEXT NOT NULL)`)
235
219
  db.exec(`CREATE TABLE IF NOT EXISTS usage (session_id TEXT NOT NULL, data TEXT NOT NULL)`)
236
220
  db.exec(`CREATE INDEX IF NOT EXISTS idx_usage_session ON usage(session_id)`)
221
+ db.exec(`CREATE TABLE IF NOT EXISTS runtime_locks (
222
+ name TEXT PRIMARY KEY,
223
+ owner TEXT NOT NULL,
224
+ expires_at INTEGER NOT NULL,
225
+ updated_at INTEGER NOT NULL
226
+ )`)
237
227
 
238
228
  function readCollectionRaw(table: string): LRUMap<string, string> {
239
229
  const rows = db.prepare(`SELECT id, data FROM ${table}`).all() as { id: string; data: string }[]
@@ -270,11 +260,15 @@ function normalizeStoredRecord(table: string, value: unknown): unknown {
270
260
  ) {
271
261
  session.shortcutForAgentId = session.agentId
272
262
  }
263
+ if (Array.isArray(session.tools) && !Array.isArray(session.plugins)) {
264
+ session.plugins = [...session.tools]
265
+ }
273
266
  if ('mainLoopState' in session) delete session.mainLoopState
274
267
  return session
275
268
  }
276
269
 
277
270
  function loadCollection(table: string): Record<string, any> {
271
+ const endPerf = perf.start('storage', 'loadCollection', { table })
278
272
  const raw = getCollectionRawCache(table)
279
273
  const result: Record<string, any> = {}
280
274
  for (const [id, data] of raw.entries()) {
@@ -284,10 +278,12 @@ function loadCollection(table: string): Record<string, any> {
284
278
  // Ignore malformed records instead of crashing list endpoints.
285
279
  }
286
280
  }
281
+ endPerf({ count: raw.size })
287
282
  return result
288
283
  }
289
284
 
290
285
  function saveCollection(table: string, data: Record<string, any>) {
286
+ const endPerf = perf.start('storage', 'saveCollection', { table })
291
287
  const current = getCollectionRawCache(table)
292
288
  const next = new Map<string, string>()
293
289
  const toUpsert: Array<[string, string]> = []
@@ -307,7 +303,10 @@ function saveCollection(table: string, data: Record<string, any>) {
307
303
  if (!next.has(id)) toDelete.push(id)
308
304
  }
309
305
 
310
- if (!toUpsert.length && !toDelete.length) return
306
+ if (!toUpsert.length && !toDelete.length) {
307
+ endPerf({ upserts: 0, deletes: 0 })
308
+ return
309
+ }
311
310
 
312
311
  const transaction = db.transaction(() => {
313
312
  if (toDelete.length) {
@@ -320,6 +319,7 @@ function saveCollection(table: string, data: Record<string, any>) {
320
319
  }
321
320
  })
322
321
  transaction()
322
+ endPerf({ upserts: toUpsert.length, deletes: toDelete.length })
323
323
 
324
324
  for (const id of toDelete) {
325
325
  current.delete(id)
@@ -395,6 +395,101 @@ export function upsertStoredItems(table: StorageCollection, entries: Array<[stri
395
395
  upsertCollectionItems(table, entries)
396
396
  }
397
397
 
398
+ export function patchStoredItem<T>(
399
+ table: StorageCollection,
400
+ id: string,
401
+ updater: (current: T | null) => T | null,
402
+ ): T | null {
403
+ let nextValue: T | null = null
404
+ const transaction = db.transaction(() => {
405
+ const current = loadCollectionItem(table, id) as T | null
406
+ nextValue = updater(current)
407
+ if (nextValue === null) {
408
+ deleteCollectionItem(table, id)
409
+ return
410
+ }
411
+ upsertCollectionItem(table, id, nextValue)
412
+ })
413
+ transaction()
414
+ return nextValue
415
+ }
416
+
417
+ export function deleteStoredItem(table: StorageCollection, id: string): void {
418
+ db.prepare(`DELETE FROM ${table} WHERE id = ?`).run(id)
419
+ const cached = collectionCache.get(table)
420
+ if (cached) cached.delete(id)
421
+ }
422
+
423
+ // --- Collection Store Factory ---
424
+ // Generates typed CRUD operations for any collection table, with optional TTL caching.
425
+
426
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- backward-compatible default; typed stores override with concrete types
427
+ interface CollectionStore<T = any> {
428
+ load(): Record<string, T>
429
+ save(data: Record<string, T>): void
430
+ loadItem(id: string): T | null
431
+ upsert(id: string, value: unknown): void
432
+ upsertMany(entries: Array<[string, unknown]>): void
433
+ patch(id: string, updater: (current: T | null) => T | null): T | null
434
+ deleteItem(id: string): void
435
+ }
436
+
437
+ const factoryTtlCaches = hmrSingleton('__swarmclaw_factory_ttl__', () => new Map<string, TTLCache<Record<string, unknown>>>())
438
+
439
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- see CollectionStore
440
+ function createCollectionStore<T = any>(
441
+ table: StorageCollection,
442
+ opts?: { ttlMs?: number },
443
+ ): CollectionStore<T> {
444
+ let ttlCache: TTLCache<Record<string, unknown>> | null = null
445
+ if (opts?.ttlMs) {
446
+ ttlCache = factoryTtlCaches.get(table) ?? null
447
+ if (!ttlCache) {
448
+ ttlCache = new TTLCache(opts.ttlMs)
449
+ factoryTtlCaches.set(table, ttlCache)
450
+ }
451
+ }
452
+
453
+ return {
454
+ load(): Record<string, T> {
455
+ if (ttlCache) {
456
+ const cached = ttlCache.get()
457
+ if (cached) return structuredClone(cached) as Record<string, T>
458
+ }
459
+ const result = loadCollection(table)
460
+ if (ttlCache) {
461
+ ttlCache.set(result)
462
+ return structuredClone(result) as Record<string, T>
463
+ }
464
+ return result as Record<string, T>
465
+ },
466
+ save(data: Record<string, T>): void {
467
+ saveCollection(table, data as Record<string, unknown>)
468
+ ttlCache?.invalidate()
469
+ },
470
+ loadItem(id: string): T | null {
471
+ return loadCollectionItem(table, id) as T | null
472
+ },
473
+ upsert(id: string, value: unknown): void {
474
+ upsertCollectionItem(table, id, value)
475
+ ttlCache?.invalidate()
476
+ },
477
+ upsertMany(entries: Array<[string, unknown]>): void {
478
+ upsertCollectionItems(table, entries)
479
+ ttlCache?.invalidate()
480
+ },
481
+ patch(id: string, updater: (current: T | null) => T | null): T | null {
482
+ const result = patchStoredItem<T>(table, id, updater)
483
+ ttlCache?.invalidate()
484
+ return result
485
+ },
486
+ deleteItem(id: string): void {
487
+ deleteCollectionItem(table, id)
488
+ ttlCache?.invalidate()
489
+ },
490
+ }
491
+ }
492
+
398
493
  function loadSingleton<T>(table: string, fallback: T): T {
399
494
  const row = db.prepare(`SELECT data FROM ${table} WHERE id = 1`).get() as { data: string } | undefined
400
495
  return row ? JSON.parse(row.data) as T : fallback
@@ -404,6 +499,58 @@ function saveSingleton(table: string, data: unknown) {
404
499
  db.prepare(`INSERT OR REPLACE INTO ${table} (id, data) VALUES (1, ?)`).run(JSON.stringify(data))
405
500
  }
406
501
 
502
+ function normalizeLockTtlMs(ttlMs: number): number {
503
+ if (!Number.isFinite(ttlMs)) return 1_000
504
+ return Math.max(1_000, Math.trunc(ttlMs))
505
+ }
506
+
507
+ export function patchQueue<T>(updater: (queue: string[]) => T): T {
508
+ let result!: T
509
+ const transaction = db.transaction(() => {
510
+ const current = loadSingleton('queue', [])
511
+ const queue = Array.isArray(current) ? [...current] : []
512
+ result = updater(queue)
513
+ saveSingleton('queue', queue)
514
+ })
515
+ transaction()
516
+ return result
517
+ }
518
+
519
+ export function tryAcquireRuntimeLock(name: string, owner: string, ttlMs: number): boolean {
520
+ let acquired = false
521
+ const now = Date.now()
522
+ const expiresAt = now + normalizeLockTtlMs(ttlMs)
523
+ const transaction = db.transaction(() => {
524
+ const row = db.prepare('SELECT owner, expires_at FROM runtime_locks WHERE name = ?').get(name) as
525
+ | { owner: string; expires_at: number }
526
+ | undefined
527
+ if (!row || row.owner === owner || row.expires_at <= now) {
528
+ db.prepare(`
529
+ INSERT OR REPLACE INTO runtime_locks (name, owner, expires_at, updated_at)
530
+ VALUES (?, ?, ?, ?)
531
+ `).run(name, owner, expiresAt, now)
532
+ acquired = true
533
+ }
534
+ })
535
+ transaction()
536
+ return acquired
537
+ }
538
+
539
+ export function renewRuntimeLock(name: string, owner: string, ttlMs: number): boolean {
540
+ const now = Date.now()
541
+ const expiresAt = now + normalizeLockTtlMs(ttlMs)
542
+ const result = db.prepare(`
543
+ UPDATE runtime_locks
544
+ SET expires_at = ?, updated_at = ?
545
+ WHERE name = ? AND owner = ?
546
+ `).run(expiresAt, now, name, owner)
547
+ return result.changes > 0
548
+ }
549
+
550
+ export function releaseRuntimeLock(name: string, owner: string): void {
551
+ db.prepare('DELETE FROM runtime_locks WHERE name = ? AND owner = ?').run(name, owner)
552
+ }
553
+
407
554
  // --- JSON Migration ---
408
555
  // Auto-import from JSON files on first run, then leave them as backup
409
556
  const JSON_FILES: Record<string, string> = {
@@ -572,7 +719,7 @@ Be concise but not curt. Warmth doesn't require verbosity. When someone asks "ho
572
719
  try {
573
720
  const existing = JSON.parse(row.data) as Record<string, unknown>
574
721
  const existingPlugins = Array.isArray(existing.plugins) ? existing.plugins : Array.isArray(existing.tools) ? existing.tools : []
575
- const mergedPlugins = Array.from(new Set([...existingPlugins, ...defaultStarterTools])).filter((t) => t !== 'delete_file')
722
+ const mergedPlugins = dedup([...existingPlugins, ...defaultStarterTools]).filter((t) => t !== 'delete_file')
576
723
  if (JSON.stringify(existingPlugins) !== JSON.stringify(mergedPlugins)) {
577
724
  existing.plugins = mergedPlugins
578
725
  delete existing.tools
@@ -691,6 +838,18 @@ export function saveSessions(s: Record<string, any>) {
691
838
  if (entries.length > 0) upsertCollectionItems('sessions', entries)
692
839
  }
693
840
 
841
+ export function loadSession(id: string): Session | null {
842
+ return loadCollectionItem('sessions', id) as Session | null
843
+ }
844
+
845
+ export function upsertSession(id: string, session: Session | Record<string, unknown>) {
846
+ upsertCollectionItem('sessions', id, session)
847
+ }
848
+
849
+ export function patchSession(id: string, updater: (current: Session | null) => Session | null): Session | null {
850
+ return patchStoredItem<Session>('sessions', id, updater)
851
+ }
852
+
694
853
  export function disableAllSessionHeartbeats(): number {
695
854
  const rows = db.prepare('SELECT id, data FROM sessions').all() as Array<{ id: string; data: string }>
696
855
  if (!rows.length) return 0
@@ -721,23 +880,18 @@ export function disableAllSessionHeartbeats(): number {
721
880
  }
722
881
 
723
882
  // --- Credentials ---
724
- export function loadCredentials(): Record<string, any> {
725
- const cache = getCredentialsCache()
726
- const cached = cache.get()
727
- if (cached) return structuredClone(cached) as Record<string, unknown>
883
+ const credentialsStore = createCollectionStore('credentials', { ttlMs: 90_000 })
884
+ export const loadCredentials = credentialsStore.load
885
+ export const saveCredentials = credentialsStore.save
728
886
 
729
- const result = loadCollection('credentials')
730
- cache.set(result)
731
- return result
732
- }
733
-
734
- export function saveCredentials(c: Record<string, any>) {
735
- saveCollection('credentials', c)
736
- getCredentialsCache().invalidate()
887
+ function requireCredentialSecret(): Buffer {
888
+ const secret = process.env.CREDENTIAL_SECRET
889
+ if (!secret) throw new Error('CREDENTIAL_SECRET environment variable is not set. Cannot encrypt/decrypt credentials.')
890
+ return Buffer.from(secret, 'hex')
737
891
  }
738
892
 
739
893
  export function encryptKey(plaintext: string): string {
740
- const key = Buffer.from(process.env.CREDENTIAL_SECRET!, 'hex')
894
+ const key = requireCredentialSecret()
741
895
  const iv = crypto.randomBytes(12)
742
896
  const cipher = crypto.createCipheriv('aes-256-gcm', key, iv)
743
897
  let encrypted = cipher.update(plaintext, 'utf8', 'hex')
@@ -747,7 +901,7 @@ export function encryptKey(plaintext: string): string {
747
901
  }
748
902
 
749
903
  export function decryptKey(encrypted: string): string {
750
- const key = Buffer.from(process.env.CREDENTIAL_SECRET!, 'hex')
904
+ const key = requireCredentialSecret()
751
905
  const [ivHex, tagHex, data] = encrypted.split(':')
752
906
  const iv = Buffer.from(ivHex, 'hex')
753
907
  const tag = Buffer.from(tagHex, 'hex')
@@ -810,41 +964,60 @@ export function saveAgents(p: Record<string, any>) {
810
964
  getAgentsCache().invalidate()
811
965
  }
812
966
 
813
- // --- Schedules ---
814
- export function loadSchedules(): Record<string, any> {
815
- return loadCollection('schedules')
967
+ export function loadAgent(id: string, opts?: { includeTrashed?: boolean }): Record<string, any> | null {
968
+ const agent = loadCollectionItem('agents', id) as Record<string, any> | null
969
+ if (!agent) return null
970
+ if (!opts?.includeTrashed && agent.trashedAt) return null
971
+ return agent
816
972
  }
817
973
 
818
- export function saveSchedules(s: Record<string, any>) {
819
- saveCollection('schedules', s)
974
+ export function upsertAgent(id: string, agent: unknown) {
975
+ upsertCollectionItem('agents', id, agent)
976
+ getAgentsCache().invalidate()
820
977
  }
821
978
 
979
+ export function patchAgent(
980
+ id: string,
981
+ updater: (current: Record<string, any> | null) => Record<string, any> | null,
982
+ ): Record<string, any> | null {
983
+ const next = patchStoredItem<Record<string, any>>('agents', id, updater)
984
+ getAgentsCache().invalidate()
985
+ return next
986
+ }
987
+
988
+ // --- Schedules ---
989
+ const schedulesStore = createCollectionStore('schedules')
990
+ export const loadSchedules = schedulesStore.load
991
+ export const saveSchedules = schedulesStore.save
992
+ export const loadSchedule = schedulesStore.loadItem
993
+ export const upsertSchedule = schedulesStore.upsert
994
+ export const upsertSchedules = schedulesStore.upsertMany
995
+
822
996
  // --- Souls ---
823
- export const loadSouls = () => loadCollection('souls')
824
- export const saveSouls = (s: Parameters<typeof saveCollection>[1]) => saveCollection('souls', s)
825
- export const deleteSoul = (id: string) => deleteCollectionItem('souls', id)
997
+ const soulsStore = createCollectionStore('souls')
998
+ export const loadSouls = soulsStore.load
999
+ export const saveSouls = soulsStore.save
1000
+ export const deleteSoul = soulsStore.deleteItem
826
1001
 
827
1002
  // --- Benchmarks ---
828
- export const loadBenchmarks = () => loadCollection('benchmarks')
829
- export const saveBenchmarks = (b: Parameters<typeof saveCollection>[1]) => saveCollection('benchmarks', b)
830
- export const deleteBenchmark = (id: string) => deleteCollectionItem('benchmarks', id)
1003
+ const benchmarksStore = createCollectionStore('benchmarks')
1004
+ export const loadBenchmarks = benchmarksStore.load
1005
+ export const saveBenchmarks = benchmarksStore.save
1006
+ export const deleteBenchmark = benchmarksStore.deleteItem
831
1007
 
832
1008
  // --- Tasks ---
833
- export function loadTasks(): Record<string, any> {
834
- return loadCollection('tasks')
835
- }
836
-
837
- export function saveTasks(t: Record<string, any>) {
838
- saveCollection('tasks', t)
839
- }
840
- export function upsertTask(id: string, task: unknown) {
841
- upsertCollectionItem('tasks', id, task)
842
- }
843
- export function deleteTask(id: string) { deleteCollectionItem('tasks', id) }
1009
+ const tasksStore = createCollectionStore('tasks')
1010
+ export const loadTasks = tasksStore.load
1011
+ export const saveTasks = tasksStore.save
1012
+ export const loadTask = tasksStore.loadItem as (id: string) => BoardTask | null
1013
+ export const upsertTask = tasksStore.upsert
1014
+ export const upsertTasks = tasksStore.upsertMany
1015
+ export const patchTask = tasksStore.patch as (id: string, updater: (current: BoardTask | null) => BoardTask | null) => BoardTask | null
1016
+ export const deleteTask = tasksStore.deleteItem
844
1017
  export function deleteSession(id: string) { deleteCollectionItem('sessions', id) }
845
1018
  export function deleteAgent(id: string) { deleteCollectionItem('agents', id); getAgentsCache().invalidate() }
846
- export function deleteSchedule(id: string) { deleteCollectionItem('schedules', id) }
847
- export function deleteSkill(id: string) { deleteCollectionItem('skills', id) }
1019
+ export const deleteSchedule = schedulesStore.deleteItem
1020
+ export function deleteSkill(id: string) { skillsStore.deleteItem(id) }
848
1021
 
849
1022
  // --- Queue ---
850
1023
  export function loadQueue(): string[] {
@@ -971,13 +1144,9 @@ export function loadPublicSettings(): Record<string, any> {
971
1144
  }
972
1145
 
973
1146
  // --- Secrets (service keys for orchestrators) ---
974
- export function loadSecrets(): Record<string, any> {
975
- return loadCollection('secrets')
976
- }
977
-
978
- export function saveSecrets(s: Record<string, any>) {
979
- saveCollection('secrets', s)
980
- }
1147
+ const secretsStore = createCollectionStore('secrets')
1148
+ export const loadSecrets = secretsStore.load
1149
+ export const saveSecrets = secretsStore.save
981
1150
 
982
1151
  export async function getSecret(key: string): Promise<{
983
1152
  id: string
@@ -1034,67 +1203,35 @@ export async function getSecret(key: string): Promise<{
1034
1203
  }
1035
1204
 
1036
1205
  // --- Provider Configs (custom providers) ---
1037
- export function loadProviderConfigs(): Record<string, any> {
1038
- return loadCollection('provider_configs')
1039
- }
1040
-
1041
- export function saveProviderConfigs(p: Record<string, any>) {
1042
- saveCollection('provider_configs', p)
1043
- }
1206
+ const providerConfigsStore = createCollectionStore('provider_configs')
1207
+ export const loadProviderConfigs = providerConfigsStore.load
1208
+ export const saveProviderConfigs = providerConfigsStore.save
1044
1209
 
1045
1210
  // --- Gateway Profiles ---
1046
- export function loadGatewayProfiles(): Record<string, any> {
1047
- const cache = getGatewayProfilesCache()
1048
- const cached = cache.get()
1049
- if (cached) return structuredClone(cached) as Record<string, unknown>
1050
-
1051
- const result = loadCollection('gateway_profiles') as Record<string, GatewayProfile>
1052
- cache.set(result)
1053
- return result
1054
- }
1055
-
1056
- export function saveGatewayProfiles(g: Record<string, GatewayProfile>) {
1057
- saveCollection('gateway_profiles', g)
1058
- getGatewayProfilesCache().invalidate()
1059
- }
1211
+ const gatewayProfilesStore = createCollectionStore('gateway_profiles', { ttlMs: 300_000 })
1212
+ export const loadGatewayProfiles = gatewayProfilesStore.load
1213
+ export const saveGatewayProfiles = gatewayProfilesStore.save as (g: Record<string, GatewayProfile>) => void
1060
1214
 
1061
1215
  // --- Model Overrides (user-added models for built-in providers) ---
1062
- export function loadModelOverrides(): Record<string, string[]> {
1063
- return loadCollection('model_overrides') as Record<string, string[]>
1064
- }
1065
-
1066
- export function saveModelOverrides(m: Record<string, string[]>) {
1067
- saveCollection('model_overrides', m)
1068
- }
1216
+ const modelOverridesStore = createCollectionStore('model_overrides')
1217
+ export const loadModelOverrides = modelOverridesStore.load as () => Record<string, string[]>
1218
+ export const saveModelOverrides = modelOverridesStore.save as (m: Record<string, string[]>) => void
1069
1219
 
1070
1220
  // --- Projects ---
1071
- export function loadProjects(): Record<string, any> {
1072
- return loadCollection('projects')
1073
- }
1074
-
1075
- export function saveProjects(s: Record<string, any>) {
1076
- saveCollection('projects', s)
1077
- }
1078
-
1079
- export function deleteProject(id: string) { deleteCollectionItem('projects', id) }
1221
+ const projectsStore = createCollectionStore('projects')
1222
+ export const loadProjects = projectsStore.load
1223
+ export const saveProjects = projectsStore.save
1224
+ export const deleteProject = projectsStore.deleteItem
1080
1225
 
1081
1226
  // --- Skills ---
1082
- export function loadSkills(): Record<string, any> {
1083
- return loadCollection('skills')
1084
- }
1085
-
1086
- export function saveSkills(s: Record<string, any>) {
1087
- saveCollection('skills', s)
1088
- }
1227
+ const skillsStore = createCollectionStore('skills')
1228
+ export const loadSkills = skillsStore.load
1229
+ export const saveSkills = skillsStore.save
1089
1230
 
1090
1231
  // --- External Agent Runtimes ---
1091
- export function loadExternalAgents(): Record<string, ExternalAgentRuntime> {
1092
- return loadCollection('external_agents') as Record<string, ExternalAgentRuntime>
1093
- }
1094
-
1095
- export function saveExternalAgents(items: Record<string, ExternalAgentRuntime>) {
1096
- saveCollection('external_agents', items)
1097
- }
1232
+ const externalAgentsStore = createCollectionStore('external_agents')
1233
+ export const loadExternalAgents = externalAgentsStore.load as () => Record<string, ExternalAgentRuntime>
1234
+ export const saveExternalAgents = externalAgentsStore.save as (items: Record<string, ExternalAgentRuntime>) => void
1098
1235
 
1099
1236
  // --- Usage ---
1100
1237
  export function loadUsage(): Record<string, any[]> {
@@ -1108,6 +1245,31 @@ export function loadUsage(): Record<string, any[]> {
1108
1245
  return result
1109
1246
  }
1110
1247
 
1248
+ export function getUsageSpendSince(minTimestamp: number): number {
1249
+ try {
1250
+ const row = db.prepare(`
1251
+ SELECT COALESCE(SUM(CAST(json_extract(data, '$.estimatedCost') AS REAL)), 0) AS total
1252
+ FROM usage
1253
+ WHERE CAST(COALESCE(json_extract(data, '$.timestamp'), 0) AS INTEGER) >= ?
1254
+ `).get(minTimestamp) as { total?: number | null } | undefined
1255
+ const total = Number(row?.total ?? 0)
1256
+ return Number.isFinite(total) ? total : 0
1257
+ } catch {
1258
+ let total = 0
1259
+ const usage = loadUsage()
1260
+ for (const records of Object.values(usage)) {
1261
+ for (const record of records || []) {
1262
+ const rec = record as Record<string, unknown>
1263
+ const ts = typeof rec?.timestamp === 'number' ? rec.timestamp : 0
1264
+ if (ts < minTimestamp) continue
1265
+ const cost = typeof rec?.estimatedCost === 'number' ? rec.estimatedCost : 0
1266
+ if (Number.isFinite(cost) && cost > 0) total += cost
1267
+ }
1268
+ }
1269
+ return total
1270
+ }
1271
+ }
1272
+
1111
1273
  export function saveUsage(u: Record<string, any[]>) {
1112
1274
  const del = db.prepare('DELETE FROM usage')
1113
1275
  const ins = db.prepare('INSERT INTO usage (session_id, data) VALUES (?, ?)')
@@ -1128,47 +1290,24 @@ export function appendUsage(sessionId: string, record: unknown) {
1128
1290
  }
1129
1291
 
1130
1292
  // --- Connectors ---
1131
- export function loadConnectors(): Record<string, any> {
1132
- const cache = getConnectorsCache()
1133
- const cached = cache.get()
1134
- if (cached) return structuredClone(cached) as Record<string, unknown>
1135
-
1136
- const result = loadCollection('connectors')
1137
- cache.set(result)
1138
- return result
1139
- }
1140
-
1141
- export function saveConnectors(c: Record<string, any>) {
1142
- saveCollection('connectors', c)
1143
- getConnectorsCache().invalidate()
1144
- }
1293
+ const connectorsStore = createCollectionStore('connectors', { ttlMs: 30_000 })
1294
+ export const loadConnectors = connectorsStore.load
1295
+ export const saveConnectors = connectorsStore.save
1145
1296
 
1146
1297
  // --- Chatrooms ---
1147
- export function loadChatrooms(): Record<string, any> {
1148
- return loadCollection('chatrooms')
1149
- }
1150
-
1151
- export function saveChatrooms(c: Record<string, any>) {
1152
- saveCollection('chatrooms', c)
1153
- }
1298
+ const chatroomsStore = createCollectionStore('chatrooms')
1299
+ export const loadChatrooms = chatroomsStore.load
1300
+ export const saveChatrooms = chatroomsStore.save
1154
1301
 
1155
1302
  // --- Documents ---
1156
- export function loadDocuments(): Record<string, any> {
1157
- return loadCollection('documents')
1158
- }
1159
-
1160
- export function saveDocuments(d: Record<string, any>) {
1161
- saveCollection('documents', d)
1162
- }
1303
+ const documentsStore = createCollectionStore('documents')
1304
+ export const loadDocuments = documentsStore.load
1305
+ export const saveDocuments = documentsStore.save
1163
1306
 
1164
1307
  // --- Webhooks ---
1165
- export function loadWebhooks(): Record<string, any> {
1166
- return loadCollection('webhooks')
1167
- }
1168
-
1169
- export function saveWebhooks(w: Record<string, any>) {
1170
- saveCollection('webhooks', w)
1171
- }
1308
+ const webhooksStore = createCollectionStore('webhooks')
1309
+ export const loadWebhooks = webhooksStore.load
1310
+ export const saveWebhooks = webhooksStore.save
1172
1311
 
1173
1312
  // --- Active processes ---
1174
1313
  export const active = new Map<string, ActiveProcess>()
@@ -1186,42 +1325,25 @@ export function localIP(): string {
1186
1325
  }
1187
1326
 
1188
1327
  // --- MCP Servers ---
1189
- export function loadMcpServers(): Record<string, any> {
1190
- return loadCollection('mcp_servers')
1191
- }
1192
-
1193
- export function saveMcpServers(m: Record<string, any>) {
1194
- saveCollection('mcp_servers', m)
1195
- }
1196
-
1197
- export function deleteMcpServer(id: string) { deleteCollectionItem('mcp_servers', id) }
1328
+ const mcpServersStore = createCollectionStore('mcp_servers')
1329
+ export const loadMcpServers = mcpServersStore.load
1330
+ export const saveMcpServers = mcpServersStore.save
1331
+ export const deleteMcpServer = mcpServersStore.deleteItem
1198
1332
 
1199
1333
  // --- Integrity Monitor Baselines ---
1200
- export function loadIntegrityBaselines(): Record<string, any> {
1201
- return loadCollection('integrity_baselines')
1202
- }
1203
-
1204
- export function saveIntegrityBaselines(entries: Record<string, any>) {
1205
- saveCollection('integrity_baselines', entries)
1206
- }
1334
+ const integrityBaselinesStore = createCollectionStore('integrity_baselines')
1335
+ export const loadIntegrityBaselines = integrityBaselinesStore.load
1336
+ export const saveIntegrityBaselines = integrityBaselinesStore.save
1207
1337
 
1208
1338
  // --- Webhook Logs ---
1209
- export function loadWebhookLogs(): Record<string, unknown> {
1210
- return loadCollection('webhook_logs')
1211
- }
1212
-
1213
- export function saveWebhookLogs(entries: Record<string, unknown>) {
1214
- saveCollection('webhook_logs', entries)
1215
- }
1216
-
1217
- export function appendWebhookLog(id: string, entry: unknown) {
1218
- upsertCollectionItem('webhook_logs', id, entry)
1219
- }
1339
+ const webhookLogsStore = createCollectionStore('webhook_logs')
1340
+ export const loadWebhookLogs = webhookLogsStore.load
1341
+ export const saveWebhookLogs = webhookLogsStore.save
1342
+ export const appendWebhookLog = webhookLogsStore.upsert
1220
1343
 
1221
1344
  // --- Activity / Audit Trail ---
1222
- export function loadActivity(): Record<string, unknown> {
1223
- return loadCollection('activity')
1224
- }
1345
+ const activityStore = createCollectionStore('activity')
1346
+ export const loadActivity = activityStore.load
1225
1347
 
1226
1348
  export function logActivity(entry: {
1227
1349
  entityType: string
@@ -1234,38 +1356,21 @@ export function logActivity(entry: {
1234
1356
  }) {
1235
1357
  const id = crypto.randomBytes(8).toString('hex')
1236
1358
  const record = { id, ...entry, timestamp: Date.now() }
1237
- upsertCollectionItem('activity', id, record)
1359
+ activityStore.upsert(id, record)
1238
1360
  }
1239
1361
 
1240
1362
  // --- Webhook Retry Queue ---
1241
- export function loadWebhookRetryQueue(): Record<string, unknown> {
1242
- return loadCollection('webhook_retry_queue')
1243
- }
1244
-
1245
- export function saveWebhookRetryQueue(entries: Record<string, unknown>) {
1246
- saveCollection('webhook_retry_queue', entries)
1247
- }
1248
-
1249
- export function upsertWebhookRetry(id: string, entry: unknown) {
1250
- upsertCollectionItem('webhook_retry_queue', id, entry)
1251
- }
1252
-
1253
- export function deleteWebhookRetry(id: string) {
1254
- deleteCollectionItem('webhook_retry_queue', id)
1255
- }
1363
+ const webhookRetryQueueStore = createCollectionStore('webhook_retry_queue')
1364
+ export const loadWebhookRetryQueue = webhookRetryQueueStore.load
1365
+ export const saveWebhookRetryQueue = webhookRetryQueueStore.save
1366
+ export const upsertWebhookRetry = webhookRetryQueueStore.upsert
1367
+ export const deleteWebhookRetry = webhookRetryQueueStore.deleteItem
1256
1368
 
1257
1369
  // --- Notifications ---
1258
- export function loadNotifications(): Record<string, unknown> {
1259
- return loadCollection('notifications')
1260
- }
1261
-
1262
- export function saveNotification(id: string, data: unknown) {
1263
- upsertCollectionItem('notifications', id, data)
1264
- }
1265
-
1266
- export function deleteNotification(id: string) {
1267
- deleteCollectionItem('notifications', id)
1268
- }
1370
+ const notificationsStore = createCollectionStore('notifications')
1371
+ export const loadNotifications = notificationsStore.load
1372
+ export const saveNotification = notificationsStore.upsert
1373
+ export const deleteNotification = notificationsStore.deleteItem
1269
1374
 
1270
1375
  export function findNotificationByDedupKey(dedupKey: string): AppNotification | null {
1271
1376
  const raw = getCollectionRawCache('notifications')
@@ -1305,118 +1410,65 @@ export function markNotificationRead(id: string) {
1305
1410
  }
1306
1411
 
1307
1412
  // --- Wallets ---
1308
- export function loadWallets(): Record<string, unknown> {
1309
- return loadCollection('wallets')
1310
- }
1311
-
1312
- export function upsertWallet(id: string, wallet: unknown) {
1313
- upsertCollectionItem('wallets', id, wallet)
1314
- }
1315
-
1316
- export function deleteWallet(id: string) {
1317
- deleteCollectionItem('wallets', id)
1318
- }
1413
+ const walletsStore = createCollectionStore('wallets')
1414
+ export const loadWallets = walletsStore.load
1415
+ export const upsertWallet = walletsStore.upsert
1416
+ export const deleteWallet = walletsStore.deleteItem
1319
1417
 
1320
1418
  // --- Wallet Transactions ---
1321
- export function loadWalletTransactions(): Record<string, unknown> {
1322
- return loadCollection('wallet_transactions')
1323
- }
1324
-
1325
- export function upsertWalletTransaction(id: string, tx: unknown) {
1326
- upsertCollectionItem('wallet_transactions', id, tx)
1327
- }
1328
-
1329
- export function deleteWalletTransaction(id: string) {
1330
- deleteCollectionItem('wallet_transactions', id)
1331
- }
1419
+ const walletTransactionsStore = createCollectionStore('wallet_transactions')
1420
+ export const loadWalletTransactions = walletTransactionsStore.load
1421
+ export const upsertWalletTransaction = walletTransactionsStore.upsert
1422
+ export const deleteWalletTransaction = walletTransactionsStore.deleteItem
1332
1423
 
1333
1424
  // --- Wallet Balance History ---
1334
- export function loadWalletBalanceHistory(): Record<string, unknown> {
1335
- return loadCollection('wallet_balance_history')
1336
- }
1337
-
1338
- export function upsertWalletBalanceSnapshot(id: string, snapshot: unknown) {
1339
- upsertCollectionItem('wallet_balance_history', id, snapshot)
1340
- }
1425
+ const walletBalanceHistoryStore = createCollectionStore('wallet_balance_history')
1426
+ export const loadWalletBalanceHistory = walletBalanceHistoryStore.load
1427
+ export const upsertWalletBalanceSnapshot = walletBalanceHistoryStore.upsert
1341
1428
 
1342
1429
  // --- Moderation Logs ---
1343
- export function loadModerationLogs(): Record<string, unknown> {
1344
- return loadCollection('moderation_logs')
1345
- }
1346
-
1347
- export function appendModerationLog(id: string, entry: unknown) {
1348
- upsertCollectionItem('moderation_logs', id, entry)
1349
- }
1430
+ const moderationLogsStore = createCollectionStore('moderation_logs')
1431
+ export const loadModerationLogs = moderationLogsStore.load
1432
+ export const appendModerationLog = moderationLogsStore.upsert
1350
1433
 
1351
1434
  // --- Connector Health ---
1352
- export function loadConnectorHealth(): Record<string, unknown> {
1353
- return loadCollection('connector_health')
1354
- }
1435
+ const connectorHealthStore = createCollectionStore('connector_health')
1436
+ export const loadConnectorHealth = connectorHealthStore.load
1437
+ export const upsertConnectorHealthEvent = connectorHealthStore.upsert
1355
1438
 
1356
- export function upsertConnectorHealthEvent(id: string, event: unknown) {
1357
- upsertCollectionItem('connector_health', id, event)
1358
- }
1439
+ // --- Connector Outbox ---
1440
+ const connectorOutboxStore = createCollectionStore('connector_outbox')
1441
+ export const loadConnectorOutbox = connectorOutboxStore.load
1442
+ export const upsertConnectorOutboxItem = connectorOutboxStore.upsert
1443
+ export const deleteConnectorOutboxItem = connectorOutboxStore.deleteItem
1359
1444
 
1360
1445
  // --- Approvals ---
1361
- export function loadApprovals(): Record<string, unknown> {
1362
- return loadCollection('approvals')
1363
- }
1446
+ const approvalsStore = createCollectionStore('approvals')
1447
+ export const loadApprovals = approvalsStore.load
1448
+ export const upsertApproval = approvalsStore.upsert
1449
+ export const deleteApproval = approvalsStore.deleteItem
1364
1450
 
1365
1451
  // --- Browser Sessions ---
1366
- export function loadBrowserSessions(): Record<string, unknown> {
1367
- return loadCollection('browser_sessions')
1368
- }
1369
-
1370
- export function upsertBrowserSession(id: string, data: unknown) {
1371
- upsertCollectionItem('browser_sessions', id, data)
1372
- }
1373
-
1374
- export function deleteBrowserSession(id: string) {
1375
- deleteCollectionItem('browser_sessions', id)
1376
- }
1452
+ const browserSessionsStore = createCollectionStore('browser_sessions')
1453
+ export const loadBrowserSessions = browserSessionsStore.load
1454
+ export const upsertBrowserSession = browserSessionsStore.upsert
1455
+ export const deleteBrowserSession = browserSessionsStore.deleteItem
1377
1456
 
1378
1457
  // --- Watch Jobs ---
1379
- export function loadWatchJobs(): Record<string, unknown> {
1380
- return loadCollection('watch_jobs')
1381
- }
1382
-
1383
- export function upsertWatchJob(id: string, data: unknown) {
1384
- upsertCollectionItem('watch_jobs', id, data)
1385
- }
1386
-
1387
- export function upsertWatchJobs(entries: Array<[string, unknown]>) {
1388
- upsertCollectionItems('watch_jobs', entries)
1389
- }
1390
-
1391
- export function deleteWatchJob(id: string) {
1392
- deleteCollectionItem('watch_jobs', id)
1393
- }
1458
+ const watchJobsStore = createCollectionStore('watch_jobs')
1459
+ export const loadWatchJobs = watchJobsStore.load
1460
+ export const upsertWatchJob = watchJobsStore.upsert
1461
+ export const upsertWatchJobs = watchJobsStore.upsertMany
1462
+ export const deleteWatchJob = watchJobsStore.deleteItem
1394
1463
 
1395
1464
  // --- Delegation Jobs ---
1396
- export function loadDelegationJobs(): Record<string, unknown> {
1397
- return loadCollection('delegation_jobs')
1398
- }
1399
-
1400
- export function upsertDelegationJob(id: string, data: unknown) {
1401
- upsertCollectionItem('delegation_jobs', id, data)
1402
- }
1403
-
1404
- export function deleteDelegationJob(id: string) {
1405
- deleteCollectionItem('delegation_jobs', id)
1406
- }
1407
-
1408
- export function upsertApproval(id: string, approval: unknown) {
1409
- upsertCollectionItem('approvals', id, approval)
1410
- }
1411
-
1412
- export function deleteApproval(id: string) {
1413
- deleteCollectionItem('approvals', id)
1414
- }
1465
+ const delegationJobsStore = createCollectionStore('delegation_jobs')
1466
+ export const loadDelegationJobs = delegationJobsStore.load
1467
+ export const upsertDelegationJob = delegationJobsStore.upsert
1468
+ export const { patch: patchDelegationJob } = delegationJobsStore
1469
+ export const deleteDelegationJob = delegationJobsStore.deleteItem
1415
1470
 
1416
1471
  export function getSessionMessages(sessionId: string): Message[] {
1417
- const stmt = db.prepare('SELECT data FROM sessions WHERE id = ?')
1418
- const row = stmt.get(sessionId) as { data: string } | undefined
1419
- if (!row) return []
1420
- const session = JSON.parse(row.data)
1421
- return session?.messages || []
1472
+ const session = loadSession(sessionId)
1473
+ return Array.isArray(session?.messages) ? session.messages : []
1422
1474
  }