@swarmclawai/swarmclaw 0.8.4 → 0.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (394) hide show
  1. package/README.md +9 -9
  2. package/bin/swarmclaw.js +5 -1
  3. package/bin/worker-cmd.js +73 -0
  4. package/package.json +2 -1
  5. package/src/app/api/agents/[id]/route.ts +17 -7
  6. package/src/app/api/agents/route.ts +21 -8
  7. package/src/app/api/approvals/route.test.ts +6 -6
  8. package/src/app/api/approvals/route.ts +2 -1
  9. package/src/app/api/auth/route.ts +2 -3
  10. package/src/app/api/chatrooms/[id]/chat/route.test.ts +299 -0
  11. package/src/app/api/chatrooms/[id]/chat/route.ts +3 -2
  12. package/src/app/api/chatrooms/[id]/route.ts +7 -6
  13. package/src/app/api/chats/[id]/chat/route.test.ts +496 -0
  14. package/src/app/api/chats/[id]/chat/route.ts +7 -3
  15. package/src/app/api/chats/[id]/clear/route.ts +9 -9
  16. package/src/app/api/chats/[id]/devserver/route.ts +2 -1
  17. package/src/app/api/chats/[id]/edit-resend/route.ts +3 -4
  18. package/src/app/api/chats/[id]/fork/route.ts +3 -5
  19. package/src/app/api/chats/[id]/restore/route.ts +6 -7
  20. package/src/app/api/chats/[id]/retry/route.ts +3 -4
  21. package/src/app/api/chats/[id]/route.ts +61 -62
  22. package/src/app/api/chats/route.ts +7 -1
  23. package/src/app/api/connectors/[id]/route.ts +7 -8
  24. package/src/app/api/connectors/route.ts +5 -4
  25. package/src/app/api/eval/run/route.ts +2 -1
  26. package/src/app/api/eval/suite/route.ts +2 -1
  27. package/src/app/api/external-agents/route.test.ts +1 -1
  28. package/src/app/api/external-agents/route.ts +2 -2
  29. package/src/app/api/files/serve/route.ts +1 -1
  30. package/src/app/api/gateways/[id]/route.ts +7 -5
  31. package/src/app/api/gateways/route.ts +1 -1
  32. package/src/app/api/knowledge/upload/route.ts +1 -1
  33. package/src/app/api/logs/route.ts +5 -7
  34. package/src/app/api/memory-images/[filename]/route.ts +2 -3
  35. package/src/app/api/openclaw/agent-files/route.ts +4 -3
  36. package/src/app/api/openclaw/approvals/route.ts +3 -4
  37. package/src/app/api/openclaw/config-sync/route.ts +3 -2
  38. package/src/app/api/openclaw/cron/route.ts +3 -2
  39. package/src/app/api/openclaw/dotenv-keys/route.ts +2 -1
  40. package/src/app/api/openclaw/exec-config/route.ts +3 -2
  41. package/src/app/api/openclaw/gateway/route.ts +5 -4
  42. package/src/app/api/openclaw/history/route.ts +3 -2
  43. package/src/app/api/openclaw/media/route.ts +2 -1
  44. package/src/app/api/openclaw/permissions/route.ts +3 -2
  45. package/src/app/api/openclaw/sandbox-env/route.ts +3 -2
  46. package/src/app/api/openclaw/skills/install/route.ts +2 -1
  47. package/src/app/api/openclaw/skills/remove/route.ts +2 -1
  48. package/src/app/api/openclaw/skills/route.ts +3 -2
  49. package/src/app/api/orchestrator/run/route.ts +5 -14
  50. package/src/app/api/perf/route.ts +43 -0
  51. package/src/app/api/plugins/dependencies/route.ts +2 -1
  52. package/src/app/api/plugins/install/route.ts +2 -1
  53. package/src/app/api/plugins/marketplace/route.ts +3 -2
  54. package/src/app/api/plugins/settings/route.ts +2 -1
  55. package/src/app/api/preview-server/route.ts +11 -10
  56. package/src/app/api/projects/[id]/route.ts +1 -1
  57. package/src/app/api/schedules/[id]/route.test.ts +128 -0
  58. package/src/app/api/schedules/[id]/route.ts +43 -43
  59. package/src/app/api/schedules/[id]/run/route.ts +11 -62
  60. package/src/app/api/schedules/route.ts +21 -87
  61. package/src/app/api/settings/route.ts +2 -0
  62. package/src/app/api/setup/doctor/route.ts +9 -8
  63. package/src/app/api/tasks/[id]/approve/route.ts +33 -30
  64. package/src/app/api/tasks/[id]/route.ts +12 -35
  65. package/src/app/api/tasks/import/github/route.ts +2 -1
  66. package/src/app/api/tasks/route.ts +79 -91
  67. package/src/app/api/wallets/[id]/approve/route.ts +2 -1
  68. package/src/app/api/wallets/[id]/route.ts +13 -19
  69. package/src/app/api/wallets/[id]/send/route.ts +2 -1
  70. package/src/app/api/wallets/route.ts +2 -1
  71. package/src/app/api/webhooks/[id]/route.ts +2 -1
  72. package/src/app/api/webhooks/route.test.ts +3 -1
  73. package/src/app/page.tsx +23 -331
  74. package/src/cli/index.js +19 -0
  75. package/src/cli/index.ts +38 -7
  76. package/src/cli/spec.js +9 -0
  77. package/src/components/activity/activity-feed.tsx +7 -4
  78. package/src/components/agents/agent-card.tsx +32 -6
  79. package/src/components/agents/agent-chat-list.tsx +55 -22
  80. package/src/components/agents/agent-files-editor.tsx +3 -2
  81. package/src/components/agents/agent-sheet.tsx +123 -22
  82. package/src/components/agents/inspector-panel.tsx +1 -1
  83. package/src/components/agents/openclaw-skills-panel.tsx +2 -1
  84. package/src/components/agents/trash-list.tsx +1 -1
  85. package/src/components/auth/access-key-gate.tsx +8 -2
  86. package/src/components/auth/setup-wizard.tsx +10 -9
  87. package/src/components/auth/user-picker.tsx +3 -2
  88. package/src/components/chat/chat-area.tsx +20 -1
  89. package/src/components/chat/chat-card.tsx +18 -3
  90. package/src/components/chat/chat-header.tsx +24 -4
  91. package/src/components/chat/chat-list.tsx +2 -11
  92. package/src/components/chat/heartbeat-history-panel.tsx +2 -1
  93. package/src/components/chat/message-bubble.tsx +45 -6
  94. package/src/components/chat/message-list.tsx +280 -145
  95. package/src/components/chat/streaming-bubble.tsx +217 -60
  96. package/src/components/chat/swarm-panel.test.ts +274 -0
  97. package/src/components/chat/swarm-panel.tsx +410 -0
  98. package/src/components/chat/swarm-status-card.tsx +346 -0
  99. package/src/components/chat/tool-call-bubble.tsx +48 -23
  100. package/src/components/chatrooms/chatroom-list.tsx +8 -5
  101. package/src/components/chatrooms/chatroom-message.tsx +10 -7
  102. package/src/components/chatrooms/chatroom-view.tsx +12 -9
  103. package/src/components/connectors/connector-health.tsx +6 -4
  104. package/src/components/connectors/connector-list.tsx +16 -11
  105. package/src/components/connectors/connector-sheet.tsx +12 -6
  106. package/src/components/home/home-view.tsx +38 -24
  107. package/src/components/input/chat-input.tsx +10 -1
  108. package/src/components/layout/app-layout.tsx +2 -38
  109. package/src/components/layout/sheet-layer.tsx +50 -0
  110. package/src/components/mcp-servers/mcp-server-list.tsx +37 -5
  111. package/src/components/mcp-servers/mcp-server-sheet.tsx +12 -2
  112. package/src/components/plugins/plugin-list.tsx +8 -4
  113. package/src/components/plugins/plugin-sheet.tsx +2 -1
  114. package/src/components/providers/provider-list.tsx +3 -2
  115. package/src/components/providers/provider-sheet.tsx +2 -1
  116. package/src/components/runs/run-list.tsx +11 -7
  117. package/src/components/schedules/schedule-card.tsx +5 -3
  118. package/src/components/shared/agent-switch-dialog.tsx +1 -1
  119. package/src/components/shared/attachment-chip.tsx +19 -3
  120. package/src/components/shared/notification-center.tsx +6 -3
  121. package/src/components/shared/settings/plugin-manager.tsx +3 -2
  122. package/src/components/shared/settings/section-embedding.tsx +2 -1
  123. package/src/components/shared/settings/section-orchestrator.tsx +2 -1
  124. package/src/components/shared/settings/section-user-preferences.tsx +107 -0
  125. package/src/components/shared/settings/settings-page.tsx +13 -9
  126. package/src/components/skills/clawhub-browser.tsx +15 -4
  127. package/src/components/skills/skill-list.tsx +15 -4
  128. package/src/components/tasks/approvals-panel.tsx +2 -1
  129. package/src/components/tasks/task-board.tsx +35 -37
  130. package/src/components/tasks/task-sheet.tsx +4 -3
  131. package/src/components/ui/full-screen-loader.tsx +164 -0
  132. package/src/components/wallets/wallet-approval-dialog.tsx +2 -1
  133. package/src/components/wallets/wallet-panel.tsx +6 -5
  134. package/src/components/wallets/wallet-section.tsx +3 -2
  135. package/src/components/webhooks/webhook-list.tsx +4 -5
  136. package/src/components/webhooks/webhook-sheet.tsx +6 -6
  137. package/src/hooks/use-app-bootstrap.ts +202 -0
  138. package/src/hooks/use-mounted-ref.ts +14 -0
  139. package/src/hooks/use-now.ts +31 -0
  140. package/src/hooks/use-openclaw-gateway.ts +2 -1
  141. package/src/instrumentation.ts +20 -8
  142. package/src/lib/agent-default-tools.test.ts +52 -0
  143. package/src/lib/agent-default-tools.ts +40 -0
  144. package/src/lib/api-client.test.ts +21 -0
  145. package/src/lib/api-client.ts +6 -11
  146. package/src/lib/canvas-content.test.ts +360 -0
  147. package/src/lib/chat-streaming-state.test.ts +49 -2
  148. package/src/lib/chat-streaming-state.ts +26 -10
  149. package/src/lib/fetch-timeout.test.ts +54 -0
  150. package/src/lib/fetch-timeout.ts +60 -3
  151. package/src/lib/live-tool-events.test.ts +77 -0
  152. package/src/lib/live-tool-events.ts +73 -0
  153. package/src/lib/local-observability.test.ts +2 -2
  154. package/src/lib/openclaw-endpoint.test.ts +1 -1
  155. package/src/lib/providers/anthropic.ts +12 -16
  156. package/src/lib/providers/index.ts +4 -2
  157. package/src/lib/providers/ollama.ts +9 -6
  158. package/src/lib/providers/openai.ts +11 -14
  159. package/src/lib/runtime-env.test.ts +8 -8
  160. package/src/lib/schedule-dedupe-advanced.test.ts +2 -2
  161. package/src/lib/schedule-dedupe.test.ts +1 -1
  162. package/src/lib/schedule-dedupe.ts +3 -2
  163. package/src/lib/server/agent-thread-session.test.ts +6 -6
  164. package/src/lib/server/agent-thread-session.ts +6 -9
  165. package/src/lib/server/alert-dispatch.ts +2 -1
  166. package/src/lib/server/api-routes.test.ts +6 -6
  167. package/src/lib/server/approval-connector-notify.test.ts +4 -4
  168. package/src/lib/server/approvals-auto-approve.test.ts +29 -29
  169. package/src/lib/server/approvals.test.ts +317 -0
  170. package/src/lib/server/approvals.ts +5 -4
  171. package/src/lib/server/autonomy-runtime.test.ts +11 -11
  172. package/src/lib/server/browser-state.ts +2 -2
  173. package/src/lib/server/capability-router.test.ts +1 -1
  174. package/src/lib/server/capability-router.ts +3 -2
  175. package/src/lib/server/chat-execution-advanced.test.ts +15 -2
  176. package/src/lib/server/chat-execution-connector-delivery.ts +67 -0
  177. package/src/lib/server/chat-execution-disabled.test.ts +3 -3
  178. package/src/lib/server/chat-execution-eval-history.test.ts +3 -3
  179. package/src/lib/server/chat-execution-heartbeat.test.ts +42 -1
  180. package/src/lib/server/chat-execution-session-sync.test.ts +119 -0
  181. package/src/lib/server/chat-execution-tool-events.ts +116 -0
  182. package/src/lib/server/chat-execution-utils.test.ts +479 -0
  183. package/src/lib/server/chat-execution-utils.ts +533 -0
  184. package/src/lib/server/chat-execution.ts +153 -748
  185. package/src/lib/server/chat-streaming-utils.ts +174 -0
  186. package/src/lib/server/chat-turn-tool-routing.ts +310 -0
  187. package/src/lib/server/chatroom-session-persistence.test.ts +2 -2
  188. package/src/lib/server/clawhub-client.ts +2 -1
  189. package/src/lib/server/collection-helpers.test.ts +92 -0
  190. package/src/lib/server/collection-helpers.ts +25 -3
  191. package/src/lib/server/connectors/access.ts +146 -0
  192. package/src/lib/server/connectors/bluebubbles.test.ts +1 -1
  193. package/src/lib/server/connectors/bluebubbles.ts +4 -4
  194. package/src/lib/server/connectors/commands.ts +367 -0
  195. package/src/lib/server/connectors/connector-routing.test.ts +4 -4
  196. package/src/lib/server/connectors/delivery.ts +142 -0
  197. package/src/lib/server/connectors/discord.ts +37 -40
  198. package/src/lib/server/connectors/email.ts +11 -10
  199. package/src/lib/server/connectors/googlechat.ts +4 -4
  200. package/src/lib/server/connectors/inbound-audio-transcription.ts +2 -1
  201. package/src/lib/server/connectors/ingress-delivery.ts +23 -0
  202. package/src/lib/server/connectors/manager-roundtrip.test.ts +300 -0
  203. package/src/lib/server/connectors/manager.test.ts +352 -77
  204. package/src/lib/server/connectors/manager.ts +134 -673
  205. package/src/lib/server/connectors/matrix.ts +4 -4
  206. package/src/lib/server/connectors/message-sentinel.ts +7 -0
  207. package/src/lib/server/connectors/openclaw.test.ts +1 -1
  208. package/src/lib/server/connectors/openclaw.ts +8 -10
  209. package/src/lib/server/connectors/outbox.test.ts +192 -0
  210. package/src/lib/server/connectors/outbox.ts +369 -0
  211. package/src/lib/server/connectors/pairing.test.ts +18 -1
  212. package/src/lib/server/connectors/pairing.ts +49 -4
  213. package/src/lib/server/connectors/policy.ts +9 -3
  214. package/src/lib/server/connectors/reconnect-state.ts +71 -0
  215. package/src/lib/server/connectors/response-media.ts +256 -0
  216. package/src/lib/server/connectors/runtime-state.ts +67 -0
  217. package/src/lib/server/connectors/session.test.ts +357 -0
  218. package/src/lib/server/connectors/session.ts +422 -0
  219. package/src/lib/server/connectors/signal.ts +7 -7
  220. package/src/lib/server/connectors/slack.ts +43 -43
  221. package/src/lib/server/connectors/teams.ts +4 -4
  222. package/src/lib/server/connectors/telegram.ts +37 -43
  223. package/src/lib/server/connectors/types.ts +31 -1
  224. package/src/lib/server/connectors/whatsapp.test.ts +108 -0
  225. package/src/lib/server/connectors/whatsapp.ts +106 -34
  226. package/src/lib/server/context-manager.test.ts +409 -0
  227. package/src/lib/server/cost.test.ts +1 -1
  228. package/src/lib/server/daemon-policy.ts +78 -0
  229. package/src/lib/server/daemon-state-connectors.test.ts +167 -0
  230. package/src/lib/server/daemon-state.test.ts +283 -55
  231. package/src/lib/server/daemon-state.ts +106 -109
  232. package/src/lib/server/data-dir.test.ts +5 -5
  233. package/src/lib/server/data-dir.ts +4 -0
  234. package/src/lib/server/delegation-jobs-advanced.test.ts +1 -1
  235. package/src/lib/server/delegation-jobs.test.ts +87 -0
  236. package/src/lib/server/delegation-jobs.ts +42 -48
  237. package/src/lib/server/devserver-launch.ts +1 -1
  238. package/src/lib/server/document-utils.ts +7 -9
  239. package/src/lib/server/elevenlabs.ts +2 -1
  240. package/src/lib/server/embeddings.test.ts +105 -0
  241. package/src/lib/server/ethereum.ts +3 -2
  242. package/src/lib/server/eval/agent-regression.ts +3 -2
  243. package/src/lib/server/eval/runner.ts +2 -1
  244. package/src/lib/server/eval/scorer.ts +2 -1
  245. package/src/lib/server/evm-swap.ts +2 -1
  246. package/src/lib/server/gateway/protocol.test.ts +1 -1
  247. package/src/lib/server/guardian.ts +2 -1
  248. package/src/lib/server/heartbeat-blocked-suppression.test.ts +151 -0
  249. package/src/lib/server/heartbeat-service-timer.test.ts +6 -6
  250. package/src/lib/server/heartbeat-service.test.ts +406 -0
  251. package/src/lib/server/heartbeat-service.ts +54 -7
  252. package/src/lib/server/heartbeat-wake.test.ts +19 -0
  253. package/src/lib/server/heartbeat-wake.ts +17 -16
  254. package/src/lib/server/integrity-monitor.test.ts +149 -0
  255. package/src/lib/server/json-utils.ts +22 -0
  256. package/src/lib/server/knowledge-db.test.ts +13 -13
  257. package/src/lib/server/link-understanding.ts +2 -1
  258. package/src/lib/server/llm-response-cache.test.ts +1 -1
  259. package/src/lib/server/main-agent-loop-advanced.test.ts +65 -3
  260. package/src/lib/server/main-agent-loop.test.ts +6 -6
  261. package/src/lib/server/main-agent-loop.ts +21 -7
  262. package/src/lib/server/mcp-client.test.ts +1 -1
  263. package/src/lib/server/mcp-conformance.test.ts +1 -1
  264. package/src/lib/server/mcp-conformance.ts +3 -2
  265. package/src/lib/server/memory-consolidation.ts +2 -1
  266. package/src/lib/server/memory-db.test.ts +485 -0
  267. package/src/lib/server/memory-db.ts +39 -26
  268. package/src/lib/server/memory-graph.test.ts +2 -2
  269. package/src/lib/server/memory-policy.test.ts +7 -7
  270. package/src/lib/server/memory-retrieval.test.ts +1 -1
  271. package/src/lib/server/openclaw-config-sync.ts +2 -1
  272. package/src/lib/server/openclaw-deploy.test.ts +1 -1
  273. package/src/lib/server/openclaw-deploy.ts +8 -12
  274. package/src/lib/server/openclaw-exec-config.ts +2 -1
  275. package/src/lib/server/openclaw-gateway.ts +6 -7
  276. package/src/lib/server/openclaw-skills-normalize.ts +2 -1
  277. package/src/lib/server/openclaw-sync.ts +7 -5
  278. package/src/lib/server/orchestrator-lg-structure.test.ts +17 -0
  279. package/src/lib/server/orchestrator-lg.ts +199 -327
  280. package/src/lib/server/path-utils.ts +31 -0
  281. package/src/lib/server/perf.ts +161 -0
  282. package/src/lib/server/plugins-approval-guidance.ts +115 -0
  283. package/src/lib/server/plugins.test.ts +1 -1
  284. package/src/lib/server/plugins.ts +22 -132
  285. package/src/lib/server/process-manager.ts +5 -8
  286. package/src/lib/server/provider-health.test.ts +137 -0
  287. package/src/lib/server/provider-health.ts +3 -3
  288. package/src/lib/server/provider-model-discovery.ts +3 -12
  289. package/src/lib/server/queue-followups.test.ts +9 -9
  290. package/src/lib/server/queue-reconcile.test.ts +2 -2
  291. package/src/lib/server/queue-recovery.test.ts +269 -0
  292. package/src/lib/server/queue.test.ts +570 -0
  293. package/src/lib/server/queue.ts +62 -455
  294. package/src/lib/server/resolve-image.ts +30 -0
  295. package/src/lib/server/runtime-settings.test.ts +4 -4
  296. package/src/lib/server/runtime-storage-write-paths.test.ts +60 -0
  297. package/src/lib/server/schedule-normalization.test.ts +279 -0
  298. package/src/lib/server/schedule-service.ts +263 -0
  299. package/src/lib/server/scheduler.ts +17 -74
  300. package/src/lib/server/session-mailbox.test.ts +191 -0
  301. package/src/lib/server/session-run-manager.test.ts +640 -0
  302. package/src/lib/server/session-run-manager.ts +59 -15
  303. package/src/lib/server/session-tools/autonomy-tools.test.ts +20 -20
  304. package/src/lib/server/session-tools/calendar.ts +2 -1
  305. package/src/lib/server/session-tools/canvas.ts +2 -1
  306. package/src/lib/server/session-tools/chatroom.ts +2 -1
  307. package/src/lib/server/session-tools/connector.ts +26 -28
  308. package/src/lib/server/session-tools/context-mgmt.ts +3 -2
  309. package/src/lib/server/session-tools/crawl.ts +4 -3
  310. package/src/lib/server/session-tools/crud.ts +105 -324
  311. package/src/lib/server/session-tools/delegate-fallback.test.ts +9 -9
  312. package/src/lib/server/session-tools/delegate.ts +6 -8
  313. package/src/lib/server/session-tools/discovery-approvals.test.ts +15 -15
  314. package/src/lib/server/session-tools/discovery.ts +4 -3
  315. package/src/lib/server/session-tools/document.ts +2 -1
  316. package/src/lib/server/session-tools/email.ts +2 -1
  317. package/src/lib/server/session-tools/extract.ts +2 -1
  318. package/src/lib/server/session-tools/file.ts +4 -3
  319. package/src/lib/server/session-tools/http.ts +2 -1
  320. package/src/lib/server/session-tools/human-loop.ts +2 -1
  321. package/src/lib/server/session-tools/image-gen.ts +4 -3
  322. package/src/lib/server/session-tools/index.ts +26 -30
  323. package/src/lib/server/session-tools/mailbox.ts +2 -1
  324. package/src/lib/server/session-tools/manage-connectors.test.ts +4 -4
  325. package/src/lib/server/session-tools/manage-schedules.test.ts +12 -12
  326. package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +5 -5
  327. package/src/lib/server/session-tools/manage-tasks.test.ts +2 -2
  328. package/src/lib/server/session-tools/monitor.ts +2 -1
  329. package/src/lib/server/session-tools/platform.ts +2 -1
  330. package/src/lib/server/session-tools/plugin-creator.ts +2 -1
  331. package/src/lib/server/session-tools/replicate.ts +3 -2
  332. package/src/lib/server/session-tools/session-tools-wiring.test.ts +6 -6
  333. package/src/lib/server/session-tools/shell.ts +4 -9
  334. package/src/lib/server/session-tools/subagent.ts +322 -170
  335. package/src/lib/server/session-tools/table.ts +6 -5
  336. package/src/lib/server/session-tools/wallet-tool.test.ts +3 -3
  337. package/src/lib/server/session-tools/wallet.ts +7 -6
  338. package/src/lib/server/session-tools/web-browser-config.test.ts +1 -0
  339. package/src/lib/server/session-tools/web-utils.ts +317 -0
  340. package/src/lib/server/session-tools/web.ts +62 -328
  341. package/src/lib/server/skill-prompt-budget.test.ts +1 -1
  342. package/src/lib/server/skills-normalize.ts +2 -1
  343. package/src/lib/server/storage-item-access.test.ts +302 -0
  344. package/src/lib/server/storage.ts +366 -314
  345. package/src/lib/server/stream-agent-chat.test.ts +82 -3
  346. package/src/lib/server/stream-agent-chat.ts +146 -510
  347. package/src/lib/server/stream-continuation.ts +412 -0
  348. package/src/lib/server/subagent-lineage.test.ts +647 -0
  349. package/src/lib/server/subagent-lineage.ts +435 -0
  350. package/src/lib/server/subagent-runtime.test.ts +484 -0
  351. package/src/lib/server/subagent-runtime.ts +419 -0
  352. package/src/lib/server/subagent-swarm.test.ts +391 -0
  353. package/src/lib/server/subagent-swarm.ts +564 -0
  354. package/src/lib/server/system-events.ts +3 -3
  355. package/src/lib/server/task-followups.test.ts +491 -0
  356. package/src/lib/server/task-followups.ts +391 -0
  357. package/src/lib/server/task-lifecycle.test.ts +205 -0
  358. package/src/lib/server/task-lifecycle.ts +200 -0
  359. package/src/lib/server/task-quality-gate.test.ts +1 -1
  360. package/src/lib/server/task-resume.ts +208 -0
  361. package/src/lib/server/task-service.test.ts +108 -0
  362. package/src/lib/server/task-service.ts +264 -0
  363. package/src/lib/server/task-validation.test.ts +1 -1
  364. package/src/lib/server/test-utils/run-with-temp-data-dir.ts +42 -0
  365. package/src/lib/server/tool-capability-policy.test.ts +2 -2
  366. package/src/lib/server/tool-capability-policy.ts +3 -2
  367. package/src/lib/server/tool-planning.ts +2 -1
  368. package/src/lib/server/tool-retry.ts +2 -3
  369. package/src/lib/server/wake-dispatcher.test.ts +303 -0
  370. package/src/lib/server/wake-dispatcher.ts +318 -0
  371. package/src/lib/server/wake-mode.test.ts +161 -0
  372. package/src/lib/server/wake-mode.ts +174 -0
  373. package/src/lib/server/wallet-service.ts +8 -9
  374. package/src/lib/server/watch-jobs.ts +2 -1
  375. package/src/lib/server/workspace-context.ts +2 -2
  376. package/src/lib/shared-utils.test.ts +142 -0
  377. package/src/lib/shared-utils.ts +62 -0
  378. package/src/lib/tool-event-summary.ts +2 -1
  379. package/src/lib/view-routes.test.ts +100 -0
  380. package/src/lib/wallet.test.ts +322 -6
  381. package/src/proxy.test.ts +4 -4
  382. package/src/proxy.ts +2 -3
  383. package/src/stores/set-if-changed.ts +40 -0
  384. package/src/stores/slices/agent-slice.ts +111 -0
  385. package/src/stores/slices/auth-slice.ts +25 -0
  386. package/src/stores/slices/data-slice.ts +301 -0
  387. package/src/stores/slices/index.ts +7 -0
  388. package/src/stores/slices/session-slice.ts +112 -0
  389. package/src/stores/slices/task-slice.ts +63 -0
  390. package/src/stores/slices/ui-slice.ts +192 -0
  391. package/src/stores/use-app-store.ts +17 -822
  392. package/src/stores/use-approval-store.ts +2 -1
  393. package/src/stores/use-chat-store.ts +8 -1
  394. package/src/types/index.ts +10 -0
@@ -0,0 +1,485 @@
1
+ import assert from 'node:assert/strict'
2
+ import fs from 'node:fs'
3
+ import os from 'node:os'
4
+ import path from 'node:path'
5
+ import { after, before, describe, it } from 'node:test'
6
+
7
+ const originalEnv = {
8
+ DATA_DIR: process.env.DATA_DIR,
9
+ WORKSPACE_DIR: process.env.WORKSPACE_DIR,
10
+ SWARMCLAW_BUILD_MODE: process.env.SWARMCLAW_BUILD_MODE,
11
+ }
12
+
13
+ let tempDir = ''
14
+ let memDb: typeof import('./memory-db')
15
+
16
+ before(async () => {
17
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-memory-db-'))
18
+ process.env.DATA_DIR = path.join(tempDir, 'data')
19
+ process.env.WORKSPACE_DIR = path.join(tempDir, 'workspace')
20
+ process.env.SWARMCLAW_BUILD_MODE = '1'
21
+ memDb = await import('./memory-db')
22
+ })
23
+
24
+ after(() => {
25
+ if (originalEnv.DATA_DIR === undefined) delete process.env.DATA_DIR
26
+ else process.env.DATA_DIR = originalEnv.DATA_DIR
27
+ if (originalEnv.WORKSPACE_DIR === undefined) delete process.env.WORKSPACE_DIR
28
+ else process.env.WORKSPACE_DIR = originalEnv.WORKSPACE_DIR
29
+ if (originalEnv.SWARMCLAW_BUILD_MODE === undefined) delete process.env.SWARMCLAW_BUILD_MODE
30
+ else process.env.SWARMCLAW_BUILD_MODE = originalEnv.SWARMCLAW_BUILD_MODE
31
+ fs.rmSync(tempDir, { recursive: true, force: true })
32
+ })
33
+
34
+ describe('memory-db', () => {
35
+ // --- Basic CRUD ---
36
+
37
+ describe('add and get', () => {
38
+ it('stores a memory and retrieves it by ID', () => {
39
+ const db = memDb.getMemoryDb()
40
+ const entry = db.add({
41
+ agentId: 'agent-1',
42
+ sessionId: 'session-1',
43
+ category: 'note',
44
+ title: 'Test Memory',
45
+ content: 'This is a test memory entry.',
46
+ })
47
+ assert.ok(entry.id)
48
+ assert.equal(entry.title, 'Test Memory')
49
+ assert.equal(entry.content, 'This is a test memory entry.')
50
+ assert.equal(entry.category, 'note')
51
+ assert.equal(entry.agentId, 'agent-1')
52
+
53
+ const retrieved = db.get(entry.id)
54
+ assert.ok(retrieved)
55
+ assert.equal(retrieved!.id, entry.id)
56
+ assert.equal(retrieved!.title, 'Test Memory')
57
+ })
58
+
59
+ it('generates unique IDs for each entry', () => {
60
+ const db = memDb.getMemoryDb()
61
+ const e1 = db.add({ agentId: null, category: 'note', title: 'A', content: 'a-content' })
62
+ const e2 = db.add({ agentId: null, category: 'note', title: 'B', content: 'b-content' })
63
+ assert.notEqual(e1.id, e2.id)
64
+ })
65
+
66
+ it('returns null for non-existent ID', () => {
67
+ const db = memDb.getMemoryDb()
68
+ assert.equal(db.get('nonexistent-id-xyz'), null)
69
+ })
70
+ })
71
+
72
+ // --- Update ---
73
+
74
+ describe('update', () => {
75
+ it('updates a memory entry', () => {
76
+ const db = memDb.getMemoryDb()
77
+ const entry = db.add({
78
+ agentId: 'agent-up',
79
+ category: 'note',
80
+ title: 'Original Title',
81
+ content: 'Original content.',
82
+ })
83
+ const updated = db.update(entry.id, { title: 'Updated Title', content: 'Updated content.' })
84
+ assert.ok(updated)
85
+ assert.equal(updated!.title, 'Updated Title')
86
+ assert.equal(updated!.content, 'Updated content.')
87
+ assert.equal(updated!.agentId, 'agent-up')
88
+ })
89
+
90
+ it('returns null when updating non-existent entry', () => {
91
+ const db = memDb.getMemoryDb()
92
+ assert.equal(db.update('nonexistent-id', { title: 'Nope' }), null)
93
+ })
94
+ })
95
+
96
+ // --- Delete ---
97
+
98
+ describe('delete', () => {
99
+ it('removes a memory entry', () => {
100
+ const db = memDb.getMemoryDb()
101
+ const entry = db.add({
102
+ agentId: 'agent-del',
103
+ category: 'note',
104
+ title: 'To Delete',
105
+ content: 'This will be deleted.',
106
+ })
107
+ assert.ok(db.get(entry.id))
108
+ db.delete(entry.id)
109
+ assert.equal(db.get(entry.id), null)
110
+ })
111
+ })
112
+
113
+ // --- List ---
114
+
115
+ describe('list', () => {
116
+ it('lists memories for an agent', () => {
117
+ const db = memDb.getMemoryDb()
118
+ const agentId = `agent-list-${Date.now()}`
119
+ db.add({ agentId, category: 'note', title: 'List 1', content: 'Content 1' })
120
+ db.add({ agentId, category: 'note', title: 'List 2', content: 'Content 2' })
121
+ db.add({ agentId: 'other-agent', category: 'note', title: 'Other', content: 'Other content' })
122
+
123
+ const agentMemories = db.list(agentId)
124
+ assert.ok(agentMemories.length >= 2, `Expected at least 2 agent memories, got ${agentMemories.length}`)
125
+ const titles = agentMemories.map((m) => m.title)
126
+ assert.ok(titles.includes('List 1'))
127
+ assert.ok(titles.includes('List 2'))
128
+ })
129
+
130
+ it('respects limit parameter', () => {
131
+ const db = memDb.getMemoryDb()
132
+ const agentId = `agent-limit-${Date.now()}`
133
+ for (let i = 0; i < 10; i++) {
134
+ db.add({ agentId, category: 'note', title: `Mem ${i}`, content: `Content ${i}` })
135
+ }
136
+ const limited = db.list(agentId, 3)
137
+ assert.equal(limited.length, 3)
138
+ })
139
+ })
140
+
141
+ // --- FTS5 Search ---
142
+
143
+ describe('search (FTS5)', () => {
144
+ it('finds memories by content keyword', () => {
145
+ const db = memDb.getMemoryDb()
146
+ const agentId = `agent-fts-${Date.now()}`
147
+ db.add({
148
+ agentId,
149
+ category: 'note',
150
+ title: 'Kubernetes Deployment',
151
+ content: 'Deployed the application to a Kubernetes cluster using Helm charts.',
152
+ })
153
+ db.add({
154
+ agentId,
155
+ category: 'note',
156
+ title: 'Database Migration',
157
+ content: 'Ran the PostgreSQL migration scripts successfully.',
158
+ })
159
+
160
+ const results = db.search('kubernetes deployment helm', agentId)
161
+ assert.ok(results.length >= 1, `Expected FTS results for kubernetes, got ${results.length}`)
162
+ const titles = results.map((r) => r.title)
163
+ assert.ok(titles.includes('Kubernetes Deployment'))
164
+ })
165
+
166
+ it('returns empty for skip-query patterns', () => {
167
+ const db = memDb.getMemoryDb()
168
+ assert.deepEqual(db.search(''), [])
169
+ assert.deepEqual(db.search('swarm_heartbeat_check'), [])
170
+ })
171
+
172
+ it('returns empty for very long queries', () => {
173
+ const db = memDb.getMemoryDb()
174
+ const longQuery = 'x'.repeat(1300)
175
+ assert.deepEqual(db.search(longQuery), [])
176
+ })
177
+ })
178
+
179
+ // --- buildFtsQuery ---
180
+
181
+ describe('buildFtsQuery', () => {
182
+ it('removes stop words', () => {
183
+ const query = memDb.buildFtsQuery('what is the purpose of this')
184
+ // 'what', 'is', 'the', 'of', 'this' are stop words; 'purpose' should remain
185
+ assert.ok(query.includes('purpose'))
186
+ assert.ok(!query.includes('"the"'))
187
+ })
188
+
189
+ it('returns empty for all stop words', () => {
190
+ const query = memDb.buildFtsQuery('the is a an')
191
+ assert.equal(query, '')
192
+ })
193
+
194
+ it('limits to MAX_FTS_QUERY_TERMS', () => {
195
+ const query = memDb.buildFtsQuery('alpha bravo charlie delta echo foxtrot golf hotel india juliet')
196
+ // Should have at most 4 terms (slice 0..4)
197
+ const termCount = (query.match(/AND/g) || []).length + 1
198
+ assert.ok(termCount <= 4, `Expected at most 4 terms, got ${termCount}`)
199
+ })
200
+
201
+ it('handles empty input', () => {
202
+ assert.equal(memDb.buildFtsQuery(''), '')
203
+ })
204
+
205
+ it('deduplicates terms', () => {
206
+ const query = memDb.buildFtsQuery('kubernetes kubernetes kubernetes')
207
+ // Should only have one kubernetes
208
+ const occurrences = (query.match(/kubernetes/g) || []).length
209
+ assert.equal(occurrences, 1)
210
+ })
211
+
212
+ it('skips very short terms', () => {
213
+ const query = memDb.buildFtsQuery('go is ok no')
214
+ // All terms are <3 chars or stop words
215
+ assert.equal(query, '')
216
+ })
217
+ })
218
+
219
+ // --- Content hash dedup ---
220
+
221
+ describe('content hash dedup', () => {
222
+ it('reinforces instead of duplicating same content for same agent', () => {
223
+ const db = memDb.getMemoryDb()
224
+ const agentId = `agent-dedup-${Date.now()}`
225
+ const first = db.add({
226
+ agentId,
227
+ category: 'fact',
228
+ title: 'Dedup Test',
229
+ content: 'Identical content for dedup testing.',
230
+ })
231
+ const second = db.add({
232
+ agentId,
233
+ category: 'fact',
234
+ title: 'Dedup Test Different Title',
235
+ content: 'Identical content for dedup testing.',
236
+ })
237
+ // Should return the same ID (reinforced, not duplicated)
238
+ assert.equal(second.id, first.id)
239
+ assert.ok((second.reinforcementCount || 0) >= 1, 'Expected reinforcement count to increase')
240
+ })
241
+ })
242
+
243
+ // --- Memory linking ---
244
+
245
+ describe('link and unlink', () => {
246
+ it('links two memories bidirectionally', () => {
247
+ const db = memDb.getMemoryDb()
248
+ const a = db.add({ agentId: 'agent-link', category: 'note', title: 'Memory A', content: 'Content A' })
249
+ const b = db.add({ agentId: 'agent-link', category: 'note', title: 'Memory B', content: 'Content B' })
250
+
251
+ db.link(a.id, [b.id])
252
+
253
+ const aAfter = db.get(a.id)
254
+ const bAfter = db.get(b.id)
255
+ assert.ok(aAfter!.linkedMemoryIds?.includes(b.id), 'A should link to B')
256
+ assert.ok(bAfter!.linkedMemoryIds?.includes(a.id), 'B should link back to A')
257
+ })
258
+
259
+ it('unlinks memories bidirectionally', () => {
260
+ const db = memDb.getMemoryDb()
261
+ const a = db.add({ agentId: 'agent-unlink', category: 'note', title: 'Unlink A', content: 'Unlink Content A' })
262
+ const b = db.add({ agentId: 'agent-unlink', category: 'note', title: 'Unlink B', content: 'Unlink Content B' })
263
+
264
+ db.link(a.id, [b.id])
265
+ db.unlink(a.id, [b.id])
266
+
267
+ const aAfter = db.get(a.id)
268
+ const bAfter = db.get(b.id)
269
+ const aLinks = aAfter?.linkedMemoryIds || []
270
+ const bLinks = bAfter?.linkedMemoryIds || []
271
+ assert.ok(!aLinks.includes(b.id), 'A should no longer link to B')
272
+ assert.ok(!bLinks.includes(a.id), 'B should no longer link to A')
273
+ })
274
+
275
+ it('link returns null for non-existent source', () => {
276
+ const db = memDb.getMemoryDb()
277
+ assert.equal(db.link('nonexistent', ['also-nonexistent']), null)
278
+ })
279
+ })
280
+
281
+ // --- Pinned memories ---
282
+
283
+ describe('pinned memories', () => {
284
+ it('lists pinned memories for an agent', () => {
285
+ const db = memDb.getMemoryDb()
286
+ const agentId = `agent-pinned-${Date.now()}`
287
+ db.add({ agentId, category: 'note', title: 'Regular', content: 'Not pinned' })
288
+ db.add({ agentId, category: 'note', title: 'Pinned One', content: 'This is pinned', pinned: true })
289
+
290
+ const pinned = db.listPinned(agentId)
291
+ assert.ok(pinned.length >= 1)
292
+ assert.ok(pinned.some((m) => m.title === 'Pinned One'))
293
+ assert.ok(pinned.every((m) => m.pinned === true))
294
+ })
295
+ })
296
+
297
+ // --- Scope filtering ---
298
+
299
+ describe('filterMemoriesByScope', () => {
300
+ it('returns all entries with mode=all', () => {
301
+ const entries = [
302
+ { id: '1', agentId: 'a1', category: 'note', title: 'x', content: 'y', createdAt: 0, updatedAt: 0 },
303
+ { id: '2', agentId: null, category: 'note', title: 'x', content: 'y', createdAt: 0, updatedAt: 0 },
304
+ ]
305
+ const result = memDb.filterMemoriesByScope(entries, { mode: 'all' })
306
+ assert.equal(result.length, 2)
307
+ })
308
+
309
+ it('filters to global-only with mode=global', () => {
310
+ const entries = [
311
+ { id: '1', agentId: 'a1', category: 'note', title: 'x', content: 'y', createdAt: 0, updatedAt: 0 },
312
+ { id: '2', agentId: null, category: 'note', title: 'x', content: 'y', createdAt: 0, updatedAt: 0 },
313
+ ]
314
+ const result = memDb.filterMemoriesByScope(entries, { mode: 'global' })
315
+ assert.equal(result.length, 1)
316
+ assert.equal(result[0].id, '2')
317
+ })
318
+
319
+ it('filters by agent with mode=agent', () => {
320
+ const entries = [
321
+ { id: '1', agentId: 'a1', category: 'note', title: 'x', content: 'y', createdAt: 0, updatedAt: 0 },
322
+ { id: '2', agentId: 'a2', category: 'note', title: 'x', content: 'y', createdAt: 0, updatedAt: 0 },
323
+ ]
324
+ const result = memDb.filterMemoriesByScope(entries, { mode: 'agent', agentId: 'a1' })
325
+ assert.equal(result.length, 1)
326
+ assert.equal(result[0].agentId, 'a1')
327
+ })
328
+
329
+ it('includes shared-with entries in agent mode', () => {
330
+ const entries = [
331
+ { id: '1', agentId: 'a2', sharedWith: ['a1'], category: 'note', title: 'x', content: 'y', createdAt: 0, updatedAt: 0 },
332
+ ]
333
+ const result = memDb.filterMemoriesByScope(entries, { mode: 'agent', agentId: 'a1' })
334
+ assert.equal(result.length, 1)
335
+ })
336
+
337
+ it('returns empty for agent mode without agentId', () => {
338
+ const entries = [
339
+ { id: '1', agentId: 'a1', category: 'note', title: 'x', content: 'y', createdAt: 0, updatedAt: 0 },
340
+ ]
341
+ const result = memDb.filterMemoriesByScope(entries, { mode: 'agent' })
342
+ assert.equal(result.length, 0)
343
+ })
344
+
345
+ it('filters by session with mode=session', () => {
346
+ const entries = [
347
+ { id: '1', agentId: 'a1', sessionId: 's1', category: 'note', title: 'x', content: 'y', createdAt: 0, updatedAt: 0 },
348
+ { id: '2', agentId: 'a1', sessionId: 's2', category: 'note', title: 'x', content: 'y', createdAt: 0, updatedAt: 0 },
349
+ ]
350
+ const result = memDb.filterMemoriesByScope(entries, { mode: 'session', sessionId: 's1' })
351
+ assert.equal(result.length, 1)
352
+ assert.equal(result[0].sessionId, 's1')
353
+ })
354
+ })
355
+
356
+ // --- normalizeMemoryScopeMode ---
357
+
358
+ describe('normalizeMemoryScopeMode', () => {
359
+ it('normalizes known modes', () => {
360
+ assert.equal(memDb.normalizeMemoryScopeMode('all'), 'all')
361
+ assert.equal(memDb.normalizeMemoryScopeMode('global'), 'global')
362
+ assert.equal(memDb.normalizeMemoryScopeMode('agent'), 'agent')
363
+ assert.equal(memDb.normalizeMemoryScopeMode('session'), 'session')
364
+ assert.equal(memDb.normalizeMemoryScopeMode('project'), 'project')
365
+ })
366
+
367
+ it('maps shared to global', () => {
368
+ assert.equal(memDb.normalizeMemoryScopeMode('shared'), 'global')
369
+ })
370
+
371
+ it('defaults to auto for unknown', () => {
372
+ assert.equal(memDb.normalizeMemoryScopeMode('invalid'), 'auto')
373
+ assert.equal(memDb.normalizeMemoryScopeMode(''), 'auto')
374
+ assert.equal(memDb.normalizeMemoryScopeMode(null), 'auto')
375
+ assert.equal(memDb.normalizeMemoryScopeMode(undefined), 'auto')
376
+ })
377
+ })
378
+
379
+ // --- getLatestBySessionCategory ---
380
+
381
+ describe('getLatestBySessionCategory', () => {
382
+ it('returns a memory for a valid session+category', () => {
383
+ const db = memDb.getMemoryDb()
384
+ const sessionId = `sess-latest-${Date.now()}`
385
+ db.add({ agentId: 'a', sessionId, category: 'working/context', title: 'Entry A', content: 'content alpha unique' })
386
+ db.add({ agentId: 'a', sessionId, category: 'working/context', title: 'Entry B', content: 'content beta unique' })
387
+
388
+ const latest = db.getLatestBySessionCategory(sessionId, 'working/context')
389
+ assert.ok(latest, 'Should return a memory entry')
390
+ assert.equal(latest!.sessionId, sessionId)
391
+ assert.equal(latest!.category, 'working/context')
392
+ })
393
+
394
+ it('returns null for non-matching category', () => {
395
+ const db = memDb.getMemoryDb()
396
+ const sessionId = `sess-nomatch-${Date.now()}`
397
+ db.add({ agentId: 'a', sessionId, category: 'note', title: 'X', content: 'x content unique nomatch' })
398
+ assert.equal(db.getLatestBySessionCategory(sessionId, 'working/context'), null)
399
+ })
400
+
401
+ it('returns null for empty session/category', () => {
402
+ const db = memDb.getMemoryDb()
403
+ assert.equal(db.getLatestBySessionCategory('', 'note'), null)
404
+ assert.equal(db.getLatestBySessionCategory('valid', ''), null)
405
+ })
406
+ })
407
+
408
+ // --- countsByAgent ---
409
+
410
+ describe('countsByAgent', () => {
411
+ it('returns counts grouped by agent', () => {
412
+ const db = memDb.getMemoryDb()
413
+ // Data already exists from previous tests — just verify the shape
414
+ const counts = db.countsByAgent()
415
+ assert.equal(typeof counts, 'object')
416
+ // Should have at least one key
417
+ assert.ok(Object.keys(counts).length >= 1)
418
+ for (const [, val] of Object.entries(counts)) {
419
+ assert.equal(typeof val, 'number')
420
+ assert.ok(val > 0)
421
+ }
422
+ })
423
+ })
424
+
425
+ // --- Delete cleans up links ---
426
+
427
+ describe('delete cleans up linked references', () => {
428
+ it('removes deleted ID from other memories linkedMemoryIds', () => {
429
+ const db = memDb.getMemoryDb()
430
+ const a = db.add({ agentId: 'agent-cleanup', category: 'note', title: 'Cleanup A', content: 'Cleanup A content' })
431
+ const b = db.add({ agentId: 'agent-cleanup', category: 'note', title: 'Cleanup B', content: 'Cleanup B content' })
432
+ const c = db.add({ agentId: 'agent-cleanup', category: 'note', title: 'Cleanup C', content: 'Cleanup C content' })
433
+
434
+ db.link(a.id, [b.id, c.id])
435
+
436
+ // Verify links exist
437
+ const bBefore = db.get(b.id)
438
+ assert.ok(bBefore?.linkedMemoryIds?.includes(a.id))
439
+
440
+ // Delete A
441
+ db.delete(a.id)
442
+
443
+ // B and C should no longer reference A
444
+ const bAfter = db.get(b.id)
445
+ const cAfter = db.get(c.id)
446
+ const bLinks = bAfter?.linkedMemoryIds || []
447
+ const cLinks = cAfter?.linkedMemoryIds || []
448
+ assert.ok(!bLinks.includes(a.id), 'B should not reference deleted A')
449
+ assert.ok(!cLinks.includes(a.id), 'C should not reference deleted A')
450
+ })
451
+ })
452
+
453
+ // --- addKnowledge ---
454
+
455
+ describe('addKnowledge', () => {
456
+ it('creates a global knowledge entry', () => {
457
+ const entry = memDb.addKnowledge({
458
+ title: 'API Rate Limits',
459
+ content: 'The API has a rate limit of 100 requests per minute.',
460
+ tags: ['api', 'limits'],
461
+ })
462
+ assert.ok(entry.id)
463
+ assert.equal(entry.category, 'knowledge')
464
+ assert.equal(entry.agentId, null)
465
+ assert.equal(entry.title, 'API Rate Limits')
466
+ })
467
+ })
468
+
469
+ // --- searchKnowledge ---
470
+
471
+ describe('searchKnowledge', () => {
472
+ it('finds knowledge entries by query', () => {
473
+ // Add a knowledge entry with a unique term
474
+ memDb.addKnowledge({
475
+ title: 'Photosynthesis Process',
476
+ content: 'Chlorophyll absorbs sunlight to convert carbon dioxide into glucose.',
477
+ tags: ['biology', 'science'],
478
+ })
479
+
480
+ const results = memDb.searchKnowledge('chlorophyll photosynthesis glucose')
481
+ assert.ok(results.length >= 1, `Expected at least 1 result, got ${results.length}`)
482
+ assert.ok(results.every((r) => r.category === 'knowledge'))
483
+ })
484
+ })
485
+ })
@@ -16,10 +16,13 @@ import {
16
16
  } from './memory-graph'
17
17
  import { isWorkingMemoryCategory } from './memory-tiers'
18
18
 
19
- import { DATA_DIR } from './data-dir'
19
+ import { DATA_DIR, MEMORY_IMAGES_DIR, WORKSPACE_DIR } from './data-dir'
20
+ import { safeJsonParse } from './json-utils'
21
+ import { tryResolvePathWithinBaseDir } from './path-utils'
20
22
 
21
23
  const DB_PATH = path.join(DATA_DIR, 'memory.db')
22
- const IMAGES_DIR = path.join(DATA_DIR, 'memory-images')
24
+ const IMAGES_DIR = MEMORY_IMAGES_DIR
25
+ const APP_STATE_ROOT_DIR = path.dirname(DATA_DIR)
23
26
 
24
27
  const MAX_IMAGE_INPUT_BYTES = 10 * 1024 * 1024 // 10MB
25
28
  const IMAGE_EXT_WHITELIST = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.tiff'])
@@ -303,15 +306,6 @@ export function getMemoryLookupLimits(settingsOverride?: Record<string, unknown>
303
306
  return normalizeMemoryLookupLimits(settings)
304
307
  }
305
308
 
306
- function parseJsonSafe<T>(value: unknown, fallback: T): T {
307
- if (typeof value !== 'string' || !value.trim()) return fallback
308
- try {
309
- return JSON.parse(value) as T
310
- } catch {
311
- return fallback
312
- }
313
- }
314
-
315
309
  function normalizeReferencePath(raw: unknown): string | undefined {
316
310
  if (typeof raw !== 'string') return undefined
317
311
  const value = raw.trim()
@@ -354,9 +348,15 @@ export function buildFtsQuery(input: string): string {
354
348
 
355
349
  function resolveExists(pathValue: string | undefined): boolean | undefined {
356
350
  if (!pathValue) return undefined
357
- const absolute = path.isAbsolute(pathValue) ? pathValue : path.resolve(process.cwd(), pathValue)
351
+ const candidates = path.isAbsolute(pathValue)
352
+ ? [pathValue]
353
+ : [
354
+ tryResolvePathWithinBaseDir(APP_STATE_ROOT_DIR, pathValue),
355
+ tryResolvePathWithinBaseDir(WORKSPACE_DIR, pathValue),
356
+ ].filter((candidate): candidate is string => !!candidate)
357
+ if (candidates.length === 0) return undefined
358
358
  try {
359
- return fs.existsSync(absolute)
359
+ return candidates.some((candidate) => fs.existsSync(candidate))
360
360
  } catch {
361
361
  return undefined
362
362
  }
@@ -577,10 +577,10 @@ function initDb() {
577
577
  const migrateLegacyRows = db.transaction(() => {
578
578
  let migrated = 0
579
579
  for (const row of rowsForMigration) {
580
- const legacyFilePaths = parseJsonSafe<FileReference[]>(row.filePaths, [])
581
- const refs = normalizeReferences(parseJsonSafe<MemoryReference[]>(row.refs, []), legacyFilePaths)
582
- const image = normalizeImage(parseJsonSafe<MemoryImage | null>(row.image, null), row.imagePath)
583
- const linkedIds = normalizeLinkedMemoryIds(parseJsonSafe<string[]>(row.linkedMemoryIds, []), row.id)
580
+ const legacyFilePaths = safeJsonParse<FileReference[]>(row.filePaths, [])
581
+ const refs = normalizeReferences(safeJsonParse<MemoryReference[]>(row.refs, []), legacyFilePaths)
582
+ const image = normalizeImage(safeJsonParse<MemoryImage | null>(row.image, null), row.imagePath)
583
+ const linkedIds = normalizeLinkedMemoryIds(safeJsonParse<string[]>(row.linkedMemoryIds, []), row.id)
584
584
 
585
585
  const nextRefs = refs?.length ? JSON.stringify(refs) : null
586
586
  const nextImage = image ? JSON.stringify(image) : null
@@ -699,11 +699,11 @@ function initDb() {
699
699
  }
700
700
 
701
701
  function rowToEntry(row: Record<string, unknown>): MemoryEntry {
702
- const legacyFilePaths = parseJsonSafe<FileReference[]>(row.filePaths, [])
703
- const references = normalizeReferences(parseJsonSafe<MemoryReference[]>(row.references, []), legacyFilePaths)
704
- const image = normalizeImage(parseJsonSafe<MemoryImage | null>(row.image, null), typeof row.imagePath === 'string' ? row.imagePath : null)
702
+ const legacyFilePaths = safeJsonParse<FileReference[]>(row.filePaths, [])
703
+ const references = normalizeReferences(safeJsonParse<MemoryReference[]>(row.references, []), legacyFilePaths)
704
+ const image = normalizeImage(safeJsonParse<MemoryImage | null>(row.image, null), typeof row.imagePath === 'string' ? row.imagePath : null)
705
705
  const filePaths = referencesToLegacyFilePaths(references)
706
- const linkedMemoryIds = normalizeLinkedMemoryIds(parseJsonSafe<string[]>(row.linkedMemoryIds, []), typeof row.id === 'string' ? row.id : undefined)
706
+ const linkedMemoryIds = normalizeLinkedMemoryIds(safeJsonParse<string[]>(row.linkedMemoryIds, []), typeof row.id === 'string' ? row.id : undefined)
707
707
 
708
708
  return {
709
709
  id: String(row.id || ''),
@@ -712,14 +712,14 @@ function initDb() {
712
712
  category: typeof row.category === 'string' ? row.category : 'note',
713
713
  title: typeof row.title === 'string' ? row.title : 'Untitled',
714
714
  content: typeof row.content === 'string' ? row.content : '',
715
- metadata: parseJsonSafe<Record<string, unknown> | undefined>(row.metadata, undefined),
715
+ metadata: safeJsonParse<Record<string, unknown> | undefined>(row.metadata, undefined),
716
716
  references,
717
717
  filePaths,
718
718
  image,
719
719
  imagePath: image?.path || undefined,
720
720
  linkedMemoryIds: linkedMemoryIds.length ? linkedMemoryIds : undefined,
721
721
  pinned: row.pinned === 1,
722
- sharedWith: parseJsonSafe<string[]>(row.sharedWith, []).length ? parseJsonSafe<string[]>(row.sharedWith, []) : undefined,
722
+ sharedWith: safeJsonParse<string[]>(row.sharedWith, []).length ? safeJsonParse<string[]>(row.sharedWith, []) : undefined,
723
723
  accessCount: typeof row.accessCount === 'number' ? row.accessCount : 0,
724
724
  lastAccessedAt: typeof row.lastAccessedAt === 'number' ? row.lastAccessedAt : 0,
725
725
  contentHash: typeof row.contentHash === 'string' ? row.contentHash : undefined,
@@ -888,14 +888,27 @@ function initDb() {
888
888
  const row = stmts.getById.get(id) as Record<string, unknown> | undefined
889
889
  const entry = row ? rowToEntry(row) : null
890
890
  if (entry?.image?.path || entry?.imagePath) {
891
- const imgPath = path.join(process.cwd(), entry.image?.path || entry.imagePath || '')
892
- try { fs.unlinkSync(imgPath) } catch { /* file may not exist */ }
891
+ const imagePath = entry.image?.path || entry.imagePath || ''
892
+ const candidatePaths = path.isAbsolute(imagePath)
893
+ ? [imagePath]
894
+ : [
895
+ tryResolvePathWithinBaseDir(APP_STATE_ROOT_DIR, imagePath),
896
+ tryResolvePathWithinBaseDir(WORKSPACE_DIR, imagePath),
897
+ ].filter((candidate): candidate is string => !!candidate)
898
+ for (const imgPath of candidatePaths) {
899
+ try {
900
+ fs.unlinkSync(imgPath)
901
+ break
902
+ } catch {
903
+ // file may not exist
904
+ }
905
+ }
893
906
  }
894
907
  stmts.delete.run(id)
895
908
  // Remove this ID from any other memory's linkedMemoryIds
896
909
  const linking = stmts.findMemoriesLinkingTo.all(`%"${id}"%`) as any[]
897
910
  for (const row of linking) {
898
- const ids = normalizeLinkedMemoryIds(parseJsonSafe<string[]>(row.linkedMemoryIds, []), row.id)
911
+ const ids = normalizeLinkedMemoryIds(safeJsonParse<string[]>(row.linkedMemoryIds, []), row.id)
899
912
  const filtered = ids.filter((lid: string) => lid !== id)
900
913
  stmts.updateLinks.run(filtered.length ? JSON.stringify(filtered) : null, Date.now(), row.id)
901
914
  }
@@ -5,8 +5,8 @@ import {
5
5
  normalizeMemoryLookupLimits,
6
6
  resolveLookupRequest,
7
7
  traverseLinkedMemoryGraph,
8
- } from './memory-graph.ts'
9
- import type { MemoryLookupLimits, LinkedMemoryNode } from './memory-graph.ts'
8
+ } from './memory-graph'
9
+ import type { MemoryLookupLimits, LinkedMemoryNode } from './memory-graph'
10
10
 
11
11
  describe('normalizeLinkedMemoryIds', () => {
12
12
  it('filters empty strings and self-references', () => {
@@ -10,10 +10,10 @@ import {
10
10
  } from './memory-policy'
11
11
 
12
12
  test('normalizeMemoryCategory maps flat categories into hierarchical buckets', () => {
13
- assert.equal(normalizeMemoryCategory('preference', 'User prefers terse replies'), 'identity/preferences')
14
- assert.equal(normalizeMemoryCategory('decision', 'Ship the Docker path'), 'projects/decisions')
15
- assert.equal(normalizeMemoryCategory('error', 'Root cause found'), 'execution/errors')
16
- assert.equal(normalizeMemoryCategory('project', 'Repo setup'), 'projects/context')
13
+ assert.equal(normalizeMemoryCategory('preference', 'User prefers terse replies', null), 'identity/preferences')
14
+ assert.equal(normalizeMemoryCategory('decision', 'Ship the Docker path', null), 'projects/decisions')
15
+ assert.equal(normalizeMemoryCategory('error', 'Root cause found', null), 'execution/errors')
16
+ assert.equal(normalizeMemoryCategory('project', 'Repo setup', null), 'projects/context')
17
17
  })
18
18
 
19
19
  test('shouldInjectMemoryContext skips low-signal greetings and acknowledgements', () => {
@@ -54,12 +54,12 @@ test('isDirectMemoryWriteRequest detects remember-and-confirm turns without matc
54
54
  })
55
55
 
56
56
  test('shouldAutoCaptureMemory filters noisy turns', () => {
57
- assert.equal(shouldAutoCaptureMemory({ message: 'thanks', response: 'Happy to help with that.', source: 'chat' }), false)
58
- assert.equal(shouldAutoCaptureMemory({ message: 'Please save this to memory', response: 'Stored memory "note".', source: 'chat' }), false)
57
+ assert.equal(shouldAutoCaptureMemory({ message: 'thanks', response: 'Happy to help with that.' }), false)
58
+ assert.equal(shouldAutoCaptureMemory({ message: 'Please save this to memory', response: 'Stored memory "note".' }), false)
59
59
  assert.equal(shouldAutoCaptureMemory({
60
60
  message: 'We decided to use the shared staging environment and keep the worker count at 2 for now.',
61
61
  response: 'Decision captured: shared staging, worker count 2, and we will revisit after load testing next week.',
62
- source: 'chat',
62
+ // source: 'chat',
63
63
  }), true)
64
64
  })
65
65
 
@@ -1,7 +1,7 @@
1
1
  import assert from 'node:assert/strict'
2
2
  import { test } from 'node:test'
3
3
  import type { MemoryEntry } from '@/types'
4
- import { filterMemoriesByScope, normalizeMemoryScopeMode } from './memory-db.ts'
4
+ import { filterMemoriesByScope, normalizeMemoryScopeMode } from './memory-db'
5
5
 
6
6
  function makeEntry(id: string, patch: Partial<MemoryEntry> = {}): MemoryEntry {
7
7
  return {