@vellumai/assistant 0.6.0 → 0.6.2

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 (358) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +68 -15
  3. package/Dockerfile +2 -2
  4. package/bun.lock +6 -2
  5. package/docker-entrypoint.sh +42 -1
  6. package/docs/architecture/integrations.md +1 -1
  7. package/docs/architecture/memory.md +21 -24
  8. package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
  9. package/openapi.yaml +539 -4
  10. package/package.json +5 -1
  11. package/src/__tests__/anthropic-provider.test.ts +160 -95
  12. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  13. package/src/__tests__/app-executors.test.ts +47 -1
  14. package/src/__tests__/app-source-watcher.test.ts +159 -0
  15. package/src/__tests__/assistant-event-hub.test.ts +30 -0
  16. package/src/__tests__/checker.test.ts +138 -172
  17. package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
  18. package/src/__tests__/config-schema.test.ts +5 -0
  19. package/src/__tests__/context-overflow-approval.test.ts +5 -5
  20. package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
  21. package/src/__tests__/conversation-agent-loop.test.ts +4 -51
  22. package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
  23. package/src/__tests__/conversation-directories-parse.test.ts +105 -0
  24. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  25. package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
  26. package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
  27. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
  28. package/src/__tests__/conversation-wipe.test.ts +2 -6
  29. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
  30. package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
  31. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  32. package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
  33. package/src/__tests__/credential-execution-approval-bridge.test.ts +0 -2
  34. package/src/__tests__/date-context.test.ts +76 -210
  35. package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
  36. package/src/__tests__/file-list-tool.test.ts +219 -0
  37. package/src/__tests__/first-greeting.test.ts +1 -1
  38. package/src/__tests__/heartbeat-service.test.ts +180 -3
  39. package/src/__tests__/identity-routes.test.ts +328 -0
  40. package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
  41. package/src/__tests__/injection-block.test.ts +24 -0
  42. package/src/__tests__/inline-command-runner.test.ts +7 -5
  43. package/src/__tests__/install-skill-routing.test.ts +7 -6
  44. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
  45. package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
  46. package/src/__tests__/llm-context-normalization.test.ts +18 -18
  47. package/src/__tests__/llm-context-route-provider.test.ts +101 -0
  48. package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
  49. package/src/__tests__/log-export-workspace.test.ts +257 -100
  50. package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
  51. package/src/__tests__/mcp-abort-signal.test.ts +5 -0
  52. package/src/__tests__/mcp-client-auth.test.ts +5 -0
  53. package/src/__tests__/memory-recall-log-store.test.ts +132 -0
  54. package/src/__tests__/migration-export-streaming.test.ts +304 -0
  55. package/src/__tests__/migration-import-commit-http.test.ts +11 -10
  56. package/src/__tests__/mock-fetch.ts +87 -0
  57. package/src/__tests__/navigate-settings-tab.test.ts +14 -1
  58. package/src/__tests__/notification-broadcaster.test.ts +65 -0
  59. package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
  60. package/src/__tests__/onboarding-template-contract.test.ts +63 -14
  61. package/src/__tests__/parser.test.ts +32 -0
  62. package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
  63. package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
  64. package/src/__tests__/permission-mode-sse.test.ts +418 -0
  65. package/src/__tests__/permission-mode-store.test.ts +277 -0
  66. package/src/__tests__/permission-mode.test.ts +101 -0
  67. package/src/__tests__/pkb-autoinject.test.ts +96 -0
  68. package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
  69. package/src/__tests__/profiler-routes.test.ts +502 -0
  70. package/src/__tests__/profiler-run-store.test.ts +441 -0
  71. package/src/__tests__/proxy-approval-callback.test.ts +4 -75
  72. package/src/__tests__/registry.test.ts +1 -1
  73. package/src/__tests__/require-fresh-approval.test.ts +0 -2
  74. package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
  75. package/src/__tests__/sandbox-host-parity.test.ts +5 -4
  76. package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
  77. package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
  78. package/src/__tests__/search-skills-unified.test.ts +4 -3
  79. package/src/__tests__/send-endpoint-busy.test.ts +42 -3
  80. package/src/__tests__/set-permission-mode.test.ts +274 -0
  81. package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
  82. package/src/__tests__/skill-memory.test.ts +2 -783
  83. package/src/__tests__/strip-memory-injections.test.ts +187 -0
  84. package/src/__tests__/subagent-detail.test.ts +84 -0
  85. package/src/__tests__/subagent-disposal.test.ts +308 -0
  86. package/src/__tests__/subagent-manager-notify.test.ts +19 -10
  87. package/src/__tests__/subagent-notify-parent.test.ts +390 -0
  88. package/src/__tests__/subagent-role-registry.test.ts +108 -0
  89. package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
  90. package/src/__tests__/subagent-tools.test.ts +464 -4
  91. package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
  92. package/src/__tests__/task-memory-cleanup.test.ts +12 -12
  93. package/src/__tests__/terminal-sandbox.test.ts +1 -1
  94. package/src/__tests__/terminal-tools.test.ts +16 -29
  95. package/src/__tests__/test-preload.ts +18 -0
  96. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
  97. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
  98. package/src/__tests__/tool-executor.test.ts +4 -27
  99. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
  100. package/src/__tests__/top-level-renderer.test.ts +10 -13
  101. package/src/__tests__/transport-hints-queue.test.ts +77 -0
  102. package/src/__tests__/trust-store.test.ts +4 -4
  103. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
  104. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
  105. package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
  106. package/src/__tests__/workspace-policy.test.ts +2 -7
  107. package/src/agent/loop.ts +6 -29
  108. package/src/approvals/guardian-request-resolvers.ts +24 -0
  109. package/src/avatar/traits-png-sync.ts +3 -3
  110. package/src/channels/types.ts +5 -0
  111. package/src/cli/__tests__/run-assistant-command.ts +56 -0
  112. package/src/cli/__tests__/unknown-command.test.ts +33 -0
  113. package/src/cli/commands/__tests__/email-download.test.ts +245 -0
  114. package/src/cli/commands/__tests__/email-list.test.ts +192 -0
  115. package/src/cli/commands/__tests__/email-register.test.ts +186 -0
  116. package/src/cli/commands/__tests__/email-send.test.ts +291 -0
  117. package/src/cli/commands/__tests__/email-status.test.ts +181 -0
  118. package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
  119. package/src/cli/commands/__tests__/routes.test.ts +562 -0
  120. package/src/cli/commands/conversations.ts +1 -8
  121. package/src/cli/commands/default-action.ts +68 -1
  122. package/src/cli/commands/email.ts +584 -835
  123. package/src/cli/commands/memory.ts +1 -34
  124. package/src/cli/commands/notifications.ts +7 -2
  125. package/src/cli/commands/oauth/__tests__/connect.test.ts +27 -0
  126. package/src/cli/commands/oauth/connect.ts +25 -5
  127. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  128. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  129. package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
  130. package/src/cli/commands/routes.ts +396 -0
  131. package/src/cli/commands/skills.ts +130 -20
  132. package/src/cli/program.ts +11 -2
  133. package/src/cli.ts +1 -120
  134. package/src/config/assistant-feature-flags.ts +59 -55
  135. package/src/config/bundled-skills/app-builder/SKILL.md +91 -5
  136. package/src/config/bundled-skills/gmail/SKILL.md +13 -8
  137. package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
  138. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
  139. package/src/config/bundled-skills/messaging/SKILL.md +7 -0
  140. package/src/config/bundled-skills/schedule/SKILL.md +22 -2
  141. package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
  142. package/src/config/bundled-skills/settings/TOOLS.json +1 -1
  143. package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
  144. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
  145. package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
  146. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
  147. package/src/config/bundled-skills/slack/SKILL.md +2 -0
  148. package/src/config/bundled-skills/subagent/SKILL.md +43 -3
  149. package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
  150. package/src/config/env-registry.ts +63 -0
  151. package/src/config/feature-flag-registry.json +17 -1
  152. package/src/config/schema.ts +8 -0
  153. package/src/config/schemas/filing.ts +51 -0
  154. package/src/config/schemas/heartbeat.ts +15 -12
  155. package/src/config/schemas/memory-lifecycle.ts +12 -0
  156. package/src/config/schemas/security.ts +14 -0
  157. package/src/config/schemas/services.ts +8 -0
  158. package/src/credential-execution/approval-bridge.ts +0 -1
  159. package/src/credential-execution/managed-catalog.ts +3 -7
  160. package/src/daemon/app-source-watcher.ts +93 -0
  161. package/src/daemon/config-watcher.ts +85 -3
  162. package/src/daemon/context-overflow-approval.ts +0 -1
  163. package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
  164. package/src/daemon/conversation-agent-loop.ts +179 -65
  165. package/src/daemon/conversation-attachments.ts +0 -1
  166. package/src/daemon/conversation-history.ts +4 -19
  167. package/src/daemon/conversation-lifecycle.ts +8 -14
  168. package/src/daemon/conversation-messaging.ts +3 -0
  169. package/src/daemon/conversation-process.ts +30 -8
  170. package/src/daemon/conversation-queue-manager.ts +8 -0
  171. package/src/daemon/conversation-runtime-assembly.ts +359 -308
  172. package/src/daemon/conversation-surfaces.ts +65 -0
  173. package/src/daemon/conversation-tool-setup.ts +44 -17
  174. package/src/daemon/conversation-workspace.ts +1 -2
  175. package/src/daemon/conversation.ts +19 -3
  176. package/src/daemon/date-context.ts +26 -53
  177. package/src/daemon/first-greeting.ts +1 -1
  178. package/src/daemon/handlers/conversations.ts +5 -7
  179. package/src/daemon/handlers/shared.test.ts +143 -0
  180. package/src/daemon/handlers/shared.ts +70 -5
  181. package/src/daemon/handlers/skills.ts +11 -18
  182. package/src/daemon/lifecycle.ts +220 -158
  183. package/src/daemon/message-types/conversations.ts +29 -6
  184. package/src/daemon/message-types/messages.ts +9 -2
  185. package/src/daemon/message-types/notifications.ts +12 -0
  186. package/src/daemon/message-types/schedules.ts +1 -0
  187. package/src/daemon/message-types/settings.ts +18 -0
  188. package/src/daemon/profiler-run-store.ts +557 -0
  189. package/src/daemon/server.ts +87 -10
  190. package/src/daemon/shutdown-handlers.ts +5 -0
  191. package/src/daemon/tool-side-effects.ts +23 -3
  192. package/src/daemon/transport-hints.ts +33 -0
  193. package/src/export/transcript-formatter.ts +148 -0
  194. package/src/filing/filing-service.ts +228 -0
  195. package/src/heartbeat/heartbeat-service.ts +96 -7
  196. package/src/index.ts +1 -1
  197. package/src/mcp/client.ts +6 -0
  198. package/src/mcp/mcp-oauth-provider.ts +149 -27
  199. package/src/memory/admin.ts +33 -32
  200. package/src/memory/app-store.ts +69 -0
  201. package/src/memory/conversation-bootstrap.ts +1 -1
  202. package/src/memory/conversation-crud.ts +151 -117
  203. package/src/memory/conversation-directories.ts +39 -0
  204. package/src/memory/conversation-group-migration.ts +66 -6
  205. package/src/memory/conversation-queries.ts +58 -12
  206. package/src/memory/conversation-title-service.ts +1 -0
  207. package/src/memory/db-init.ts +182 -376
  208. package/src/memory/embedding-local.ts +1 -1
  209. package/src/memory/graph/bootstrap.ts +75 -66
  210. package/src/memory/graph/capability-seed.ts +167 -17
  211. package/src/memory/graph/consolidation.ts +38 -4
  212. package/src/memory/graph/conversation-graph-memory.ts +133 -104
  213. package/src/memory/graph/extraction-job.ts +9 -4
  214. package/src/memory/graph/extraction.ts +66 -23
  215. package/src/memory/graph/graph-memory-state-store.ts +37 -0
  216. package/src/memory/graph/graph-search.ts +29 -15
  217. package/src/memory/graph/injection.ts +38 -8
  218. package/src/memory/graph/inspect.ts +12 -3
  219. package/src/memory/graph/retriever.ts +365 -262
  220. package/src/memory/graph/store.test.ts +48 -0
  221. package/src/memory/graph/store.ts +150 -11
  222. package/src/memory/graph/tool-handlers.ts +84 -209
  223. package/src/memory/graph/tools.ts +8 -52
  224. package/src/memory/graph/types.ts +24 -0
  225. package/src/memory/group-crud.ts +25 -9
  226. package/src/memory/job-handlers/cleanup.ts +44 -1
  227. package/src/memory/jobs-store.ts +70 -60
  228. package/src/memory/jobs-worker.ts +44 -28
  229. package/src/memory/llm-request-log-store.ts +96 -12
  230. package/src/memory/memory-recall-log-store.ts +49 -5
  231. package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
  232. package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
  233. package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
  234. package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
  235. package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
  236. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
  237. package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
  238. package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
  239. package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
  240. package/src/memory/migrations/index.ts +8 -0
  241. package/src/memory/migrations/registry.ts +8 -0
  242. package/src/memory/schema/conversations.ts +14 -0
  243. package/src/memory/schema/infrastructure.ts +8 -1
  244. package/src/memory/schema/memory-core.ts +0 -51
  245. package/src/memory/schema/memory-graph.ts +15 -0
  246. package/src/memory/task-memory-cleanup.ts +30 -11
  247. package/src/messaging/provider.ts +1 -1
  248. package/src/notifications/broadcaster.ts +6 -0
  249. package/src/notifications/conversation-pairing.ts +12 -4
  250. package/src/notifications/copy-composer.ts +86 -0
  251. package/src/notifications/decision-engine.ts +35 -0
  252. package/src/notifications/emit-signal.ts +14 -0
  253. package/src/notifications/signal.ts +11 -0
  254. package/src/oauth/platform-connection.test.ts +2 -2
  255. package/src/oauth/seed-providers.ts +1 -0
  256. package/src/permissions/checker.ts +15 -4
  257. package/src/permissions/defaults.ts +7 -8
  258. package/src/permissions/permission-mode-store.ts +180 -0
  259. package/src/permissions/permission-mode.ts +31 -0
  260. package/src/permissions/prompter.ts +0 -2
  261. package/src/permissions/workspace-policy.ts +9 -0
  262. package/src/platform/client.ts +1 -1
  263. package/src/prompts/system-prompt.ts +59 -7
  264. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
  265. package/src/prompts/templates/BOOTSTRAP.md +76 -162
  266. package/src/prompts/templates/HEARTBEAT.md +3 -1
  267. package/src/prompts/templates/SOUL.md +30 -9
  268. package/src/prompts/templates/UPDATES.md +8 -0
  269. package/src/providers/anthropic/client.ts +107 -219
  270. package/src/runtime/assistant-event-hub.ts +22 -0
  271. package/src/runtime/auth/route-policy.ts +23 -0
  272. package/src/runtime/auth/token-service.ts +8 -0
  273. package/src/runtime/http-server.ts +32 -2
  274. package/src/runtime/http-types.ts +12 -1
  275. package/src/runtime/migrations/vbundle-builder.ts +389 -3
  276. package/src/runtime/migrations/vbundle-importer.ts +8 -6
  277. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
  278. package/src/runtime/routes/app-management-routes.ts +1 -11
  279. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
  280. package/src/runtime/routes/archive-utils.ts +29 -0
  281. package/src/runtime/routes/avatar-routes.ts +2 -9
  282. package/src/runtime/routes/btw-routes.ts +14 -1
  283. package/src/runtime/routes/conversation-analysis-routes.ts +185 -0
  284. package/src/runtime/routes/conversation-management-routes.ts +1 -14
  285. package/src/runtime/routes/conversation-query-routes.ts +49 -3
  286. package/src/runtime/routes/conversation-routes.ts +270 -44
  287. package/src/runtime/routes/group-routes.ts +22 -8
  288. package/src/runtime/routes/heartbeat-routes.ts +4 -10
  289. package/src/runtime/routes/identity-routes.ts +53 -18
  290. package/src/runtime/routes/llm-context-normalization.ts +14 -10
  291. package/src/runtime/routes/log-export/AGENTS.md +104 -0
  292. package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
  293. package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
  294. package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
  295. package/src/runtime/routes/log-export-routes.ts +41 -278
  296. package/src/runtime/routes/memory-item-routes.test.ts +168 -233
  297. package/src/runtime/routes/migration-routes.ts +18 -7
  298. package/src/runtime/routes/profiler-routes.ts +350 -0
  299. package/src/runtime/routes/schedule-routes.ts +27 -12
  300. package/src/runtime/routes/settings-routes.ts +95 -8
  301. package/src/runtime/routes/subagents-routes.ts +28 -7
  302. package/src/runtime/routes/user-route-dispatcher.ts +223 -0
  303. package/src/runtime/routes/user-routes.ts +41 -0
  304. package/src/runtime/routes/workspace-routes.ts +0 -1
  305. package/src/schedule/schedule-store.ts +30 -0
  306. package/src/schedule/scheduler.ts +45 -18
  307. package/src/skills/catalog-install.ts +10 -2
  308. package/src/skills/inline-command-runner.ts +12 -14
  309. package/src/skills/managed-store.ts +2 -2
  310. package/src/skills/skill-memory.ts +1 -293
  311. package/src/subagent/index.ts +13 -3
  312. package/src/subagent/manager.ts +308 -29
  313. package/src/subagent/types.ts +68 -0
  314. package/src/tasks/task-runner.ts +4 -4
  315. package/src/tools/apps/executors.ts +29 -4
  316. package/src/tools/filesystem/list.ts +93 -0
  317. package/src/tools/permission-checker.ts +78 -18
  318. package/src/tools/registry.ts +4 -0
  319. package/src/tools/schedule/create.ts +3 -0
  320. package/src/tools/schedule/list.ts +1 -0
  321. package/src/tools/schedule/update.ts +6 -0
  322. package/src/tools/secret-detection-handler.ts +0 -1
  323. package/src/tools/shared/filesystem/errors.ts +5 -0
  324. package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
  325. package/src/tools/shared/filesystem/types.ts +17 -0
  326. package/src/tools/shared/shell-output.ts +31 -2
  327. package/src/tools/skills/sandbox-runner.ts +3 -6
  328. package/src/tools/subagent/abort.ts +12 -2
  329. package/src/tools/subagent/message.ts +9 -2
  330. package/src/tools/subagent/notify-parent.ts +79 -0
  331. package/src/tools/subagent/read.ts +29 -8
  332. package/src/tools/subagent/resolve.ts +21 -0
  333. package/src/tools/subagent/spawn.ts +2 -0
  334. package/src/tools/subagent/status.ts +11 -1
  335. package/src/tools/system/avatar-generator.ts +3 -3
  336. package/src/tools/system/register.ts +23 -0
  337. package/src/tools/system/set-permission-mode.ts +103 -0
  338. package/src/tools/terminal/parser.ts +30 -5
  339. package/src/tools/terminal/safe-env.ts +16 -1
  340. package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
  341. package/src/tools/terminal/sandbox.ts +4 -1
  342. package/src/tools/terminal/shell.ts +3 -5
  343. package/src/tools/tool-manifest.ts +6 -0
  344. package/src/tools/types.ts +2 -3
  345. package/src/util/logger.ts +1 -1
  346. package/src/util/platform.ts +50 -17
  347. package/src/watcher/provider-types.ts +1 -1
  348. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
  349. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
  350. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
  351. package/src/workspace/migrations/029-seed-pkb.ts +85 -0
  352. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
  353. package/src/workspace/migrations/registry.ts +6 -0
  354. package/src/workspace/top-level-renderer.ts +5 -9
  355. package/src/__tests__/cli-memory.test.ts +0 -377
  356. package/src/__tests__/clipboard.test.ts +0 -88
  357. package/src/cli/cli-memory.ts +0 -179
  358. package/src/util/clipboard.ts +0 -34
@@ -7,6 +7,7 @@ import {
7
7
  createEdge,
8
8
  createNode,
9
9
  createTrigger,
10
+ deduplicateParagraphs,
10
11
  deleteEdge,
11
12
  deleteNode,
12
13
  deleteTrigger,
@@ -1048,3 +1049,50 @@ describe("updateNode event trigger sync", () => {
1048
1049
  expect(triggers[0].eventDate).toBe(eventDate);
1049
1050
  });
1050
1051
  });
1052
+
1053
+ // ---------------------------------------------------------------------------
1054
+ // Paragraph deduplication
1055
+ // ---------------------------------------------------------------------------
1056
+
1057
+ describe("deduplicateParagraphs", () => {
1058
+ test("content with no duplicates passes through unchanged", () => {
1059
+ const input = "First paragraph.\n\nSecond paragraph.\n\nThird paragraph.";
1060
+ expect(deduplicateParagraphs(input)).toBe(input);
1061
+ });
1062
+
1063
+ test("two identical paragraphs separated by \\n\\n collapses to one", () => {
1064
+ const input = "Hello world.\n\nHello world.";
1065
+ expect(deduplicateParagraphs(input)).toBe("Hello world.");
1066
+ });
1067
+
1068
+ test("paragraphs that differ only in trailing whitespace are treated as duplicates", () => {
1069
+ const input = "Hello world. \n\nHello world.";
1070
+ expect(deduplicateParagraphs(input)).toBe("Hello world. ");
1071
+ });
1072
+
1073
+ test("bullet lists with repeated items are deduped", () => {
1074
+ const input = "- item one\n- item two\n- item one\n- item three";
1075
+ expect(deduplicateParagraphs(input)).toBe(
1076
+ "- item one\n- item two\n- item three",
1077
+ );
1078
+ });
1079
+
1080
+ test("empty content returns empty string", () => {
1081
+ expect(deduplicateParagraphs("")).toBe("");
1082
+ });
1083
+
1084
+ test("multiple duplicate paragraphs with different content", () => {
1085
+ const input = "Alpha.\n\nBeta.\n\nAlpha.\n\nGamma.\n\nBeta.";
1086
+ expect(deduplicateParagraphs(input)).toBe("Alpha.\n\nBeta.\n\nGamma.");
1087
+ });
1088
+
1089
+ test("bullet dedup within a paragraph preserves non-bullet lines", () => {
1090
+ const input = "Header:\n- item A\n- item B\n- item A";
1091
+ expect(deduplicateParagraphs(input)).toBe("Header:\n- item A\n- item B");
1092
+ });
1093
+
1094
+ test("paragraphs differing only in internal whitespace are treated as duplicates", () => {
1095
+ const input = "hello world\n\nhello world";
1096
+ expect(deduplicateParagraphs(input)).toBe("hello world");
1097
+ });
1098
+ });
@@ -2,12 +2,13 @@
2
2
  // Memory Graph — Data access layer
3
3
  // ---------------------------------------------------------------------------
4
4
 
5
- import { and, eq, inArray, sql } from "drizzle-orm";
5
+ import { and, desc, eq, inArray, sql } from "drizzle-orm";
6
6
  import { v4 as uuid } from "uuid";
7
7
 
8
8
  import { getDb } from "../db.js";
9
9
  import {
10
10
  memoryGraphEdges,
11
+ memoryGraphNodeEdits,
11
12
  memoryGraphNodes,
12
13
  memoryGraphTriggers,
13
14
  } from "../schema.js";
@@ -115,6 +116,60 @@ function rowToTrigger(
115
116
  };
116
117
  }
117
118
 
119
+ // ---------------------------------------------------------------------------
120
+ // Paragraph deduplication
121
+ // ---------------------------------------------------------------------------
122
+
123
+ /**
124
+ * Remove repeated paragraphs and bullet items from memory node content.
125
+ * Paragraphs are separated by `\n\n`. Within each paragraph, lines starting
126
+ * with `- ` are treated as bullet items and individually deduplicated.
127
+ */
128
+ export function deduplicateParagraphs(content: string): string {
129
+ if (!content) return content;
130
+
131
+ const paragraphs = content.split("\n\n");
132
+ const seen = new Set<string>();
133
+ const unique: string[] = [];
134
+
135
+ for (const paragraph of paragraphs) {
136
+ // Deduplicate bullet items within the paragraph
137
+ const lines = paragraph.split("\n");
138
+ const isBulletList = lines.some((l) => l.trimStart().startsWith("- "));
139
+
140
+ let processed: string;
141
+ if (isBulletList) {
142
+ const seenBullets = new Set<string>();
143
+ const uniqueLines: string[] = [];
144
+ for (const line of lines) {
145
+ if (line.trimStart().startsWith("- ")) {
146
+ const normalized = line.trim().replace(/\s+/g, " ");
147
+ if (!seenBullets.has(normalized)) {
148
+ seenBullets.add(normalized);
149
+ uniqueLines.push(line);
150
+ }
151
+ } else {
152
+ uniqueLines.push(line);
153
+ }
154
+ }
155
+ processed = uniqueLines.join("\n");
156
+ } else {
157
+ processed = paragraph;
158
+ }
159
+
160
+ const normalized = processed.trim().replace(/\s+/g, " ");
161
+ if (normalized === "") {
162
+ // Preserve empty paragraphs (whitespace-only) as separators
163
+ unique.push(processed);
164
+ } else if (!seen.has(normalized)) {
165
+ seen.add(normalized);
166
+ unique.push(processed);
167
+ }
168
+ }
169
+
170
+ return unique.join("\n\n");
171
+ }
172
+
118
173
  // ---------------------------------------------------------------------------
119
174
  // Node CRUD
120
175
  // ---------------------------------------------------------------------------
@@ -122,8 +177,11 @@ function rowToTrigger(
122
177
  export function createNode(node: NewNode): MemoryNode {
123
178
  const db = getDb();
124
179
  const id = uuid();
125
- db.insert(memoryGraphNodes).values(nodeToInsertValues(node, id)).run();
126
- return { ...node, id };
180
+ const cleanContent = deduplicateParagraphs(node.content);
181
+ db.insert(memoryGraphNodes)
182
+ .values(nodeToInsertValues({ ...node, content: cleanContent }, id))
183
+ .run();
184
+ return { ...node, content: cleanContent, id };
127
185
  }
128
186
 
129
187
  export function getNode(id: string): MemoryNode | null {
@@ -154,7 +212,8 @@ export function updateNode(
154
212
  const db = getDb();
155
213
  const updates: Record<string, unknown> = {};
156
214
 
157
- if (changes.content !== undefined) updates.content = changes.content;
215
+ if (changes.content !== undefined)
216
+ updates.content = deduplicateParagraphs(changes.content);
158
217
  if (changes.type !== undefined) updates.type = changes.type;
159
218
  if (changes.created !== undefined) updates.created = changes.created;
160
219
  if (changes.lastAccessed !== undefined)
@@ -180,7 +239,9 @@ export function updateNode(
180
239
  if (changes.partOfStory !== undefined)
181
240
  updates.partOfStory = changes.partOfStory;
182
241
  if (changes.imageRefs !== undefined)
183
- updates.imageRefs = changes.imageRefs ? JSON.stringify(changes.imageRefs) : null;
242
+ updates.imageRefs = changes.imageRefs
243
+ ? JSON.stringify(changes.imageRefs)
244
+ : null;
184
245
  if (changes.scopeId !== undefined) updates.scopeId = changes.scopeId;
185
246
  if (changes.eventDate !== undefined) updates.eventDate = changes.eventDate;
186
247
 
@@ -382,9 +443,7 @@ export function createTrigger(trigger: NewTrigger): MemoryTrigger {
382
443
 
383
444
  export function deleteTrigger(id: string): void {
384
445
  const db = getDb();
385
- db.delete(memoryGraphTriggers)
386
- .where(eq(memoryGraphTriggers.id, id))
387
- .run();
446
+ db.delete(memoryGraphTriggers).where(eq(memoryGraphTriggers.id, id)).run();
388
447
  }
389
448
 
390
449
  export function updateTrigger(
@@ -534,7 +593,13 @@ export function supersedeNode(
534
593
  /**
535
594
  * Apply a MemoryDiff atomically. All operations run in a single transaction.
536
595
  */
537
- export function applyDiff(diff: MemoryDiff): ApplyDiffResult {
596
+ export function applyDiff(
597
+ diff: MemoryDiff,
598
+ opts?: {
599
+ conversationId?: string;
600
+ source?: "extraction" | "consolidation" | "manual";
601
+ },
602
+ ): ApplyDiffResult {
538
603
  const db = getDb();
539
604
  const result: ApplyDiffResult = {
540
605
  nodesCreated: 0,
@@ -558,7 +623,10 @@ export function applyDiff(diff: MemoryDiff): ApplyDiffResult {
558
623
  // Create nodes
559
624
  for (const node of diff.createNodes) {
560
625
  const id = uuid();
561
- tx.insert(memoryGraphNodes).values(nodeToInsertValues(node, id)).run();
626
+ const cleanContent = deduplicateParagraphs(node.content);
627
+ tx.insert(memoryGraphNodes)
628
+ .values(nodeToInsertValues({ ...node, content: cleanContent }, id))
629
+ .run();
562
630
  result.nodesCreated++;
563
631
  result.createdNodeIds.push(id);
564
632
  }
@@ -567,7 +635,8 @@ export function applyDiff(diff: MemoryDiff): ApplyDiffResult {
567
635
  for (const update of diff.updateNodes) {
568
636
  const updates: Record<string, unknown> = {};
569
637
  const c = update.changes;
570
- if (c.content !== undefined) updates.content = c.content;
638
+ if (c.content !== undefined)
639
+ updates.content = deduplicateParagraphs(c.content as string);
571
640
  if (c.type !== undefined) updates.type = c.type;
572
641
  if (c.emotionalCharge !== undefined)
573
642
  updates.emotionalCharge = JSON.stringify(c.emotionalCharge);
@@ -584,6 +653,28 @@ export function applyDiff(diff: MemoryDiff): ApplyDiffResult {
584
653
  updates.sourceConversations = JSON.stringify(c.sourceConversations);
585
654
  if (c.eventDate !== undefined) updates.eventDate = c.eventDate;
586
655
 
656
+ // Record edit history when content changes
657
+ if (updates.content !== undefined) {
658
+ const current = tx
659
+ .select({ content: memoryGraphNodes.content })
660
+ .from(memoryGraphNodes)
661
+ .where(eq(memoryGraphNodes.id, update.id))
662
+ .get();
663
+ if (current && current.content !== updates.content) {
664
+ tx.insert(memoryGraphNodeEdits)
665
+ .values({
666
+ id: uuid(),
667
+ nodeId: update.id,
668
+ previousContent: current.content,
669
+ newContent: updates.content as string,
670
+ source: opts?.source ?? "extraction",
671
+ conversationId: opts?.conversationId ?? null,
672
+ created: Date.now(),
673
+ })
674
+ .run();
675
+ }
676
+ }
677
+
587
678
  if (Object.keys(updates).length > 0) {
588
679
  tx.update(memoryGraphNodes)
589
680
  .set(updates)
@@ -697,3 +788,51 @@ export function applyDiff(diff: MemoryDiff): ApplyDiffResult {
697
788
 
698
789
  return result;
699
790
  }
791
+
792
+ // ---------------------------------------------------------------------------
793
+ // Node edit history
794
+ // ---------------------------------------------------------------------------
795
+
796
+ /** Record a content change to a memory node for edit chain tracking. */
797
+ export function recordNodeEdit(opts: {
798
+ nodeId: string;
799
+ previousContent: string;
800
+ newContent: string;
801
+ source: "extraction" | "consolidation" | "manual";
802
+ conversationId?: string;
803
+ }): void {
804
+ const db = getDb();
805
+ db.insert(memoryGraphNodeEdits)
806
+ .values({
807
+ id: uuid(),
808
+ nodeId: opts.nodeId,
809
+ previousContent: opts.previousContent,
810
+ newContent: opts.newContent,
811
+ source: opts.source,
812
+ conversationId: opts.conversationId ?? null,
813
+ created: Date.now(),
814
+ })
815
+ .run();
816
+ }
817
+
818
+ /** Retrieve the edit history for a memory node, newest first. */
819
+ export function getNodeEditHistory(
820
+ nodeId: string,
821
+ limit = 20,
822
+ ): Array<{
823
+ id: string;
824
+ previousContent: string;
825
+ newContent: string;
826
+ source: string;
827
+ conversationId: string | null;
828
+ created: number;
829
+ }> {
830
+ const db = getDb();
831
+ return db
832
+ .select()
833
+ .from(memoryGraphNodeEdits)
834
+ .where(eq(memoryGraphNodeEdits.nodeId, nodeId))
835
+ .orderBy(desc(memoryGraphNodeEdits.created))
836
+ .limit(limit)
837
+ .all();
838
+ }
@@ -1,32 +1,21 @@
1
1
  // ---------------------------------------------------------------------------
2
2
  // Memory Graph — Tool handlers for recall and remember
3
3
  //
4
- // These are the implementations behind the recall/remember tool definitions.
5
4
  // recall: search the living graph or raw archive
6
- // remember: immediate CRUD on graph nodes (replaces NOW.md)
5
+ // remember: save facts to the PKB (buffer.md + daily archive)
7
6
  // ---------------------------------------------------------------------------
8
7
 
8
+ import { appendFileSync, existsSync, mkdirSync } from "node:fs";
9
+ import { join } from "node:path";
10
+
9
11
  import type { AssistantConfig } from "../../config/types.js";
10
12
  import { getLogger } from "../../util/logger.js";
13
+ import { getWorkspaceDir } from "../../util/platform.js";
11
14
  import { buildExcerpt, buildFtsMatchQuery } from "../conversation-queries.js";
12
15
  import { embedWithRetry } from "../embed.js";
13
16
  import { generateSparseEmbedding } from "../embedding-backend.js";
14
- import { enqueueGraphNodeEmbed, searchGraphNodes } from "./graph-search.js";
15
- import {
16
- createNode,
17
- deleteNode,
18
- getNode,
19
- getNodesByIds,
20
- updateNode,
21
- } from "./store.js";
22
- import type {
23
- DecayCurve,
24
- EmotionalCharge,
25
- Fidelity,
26
- MemoryType,
27
- NewNode,
28
- SourceType,
29
- } from "./types.js";
17
+ import { searchGraphNodes } from "./graph-search.js";
18
+ import { getNodesByIds } from "./store.js";
30
19
 
31
20
  const log = getLogger("graph-tool-handlers");
32
21
 
@@ -95,6 +84,17 @@ async function handleMemoryRecall(
95
84
  // Generate sparse embedding for hybrid search (dense + sparse with RRF fusion)
96
85
  const sparseVector = generateSparseEmbedding(input.query);
97
86
 
87
+ // Build date range filter for Qdrant-level filtering
88
+ const dateRange: { afterMs?: number; beforeMs?: number } = {};
89
+ if (input.filters?.after) {
90
+ const afterMs = new Date(input.filters.after).getTime();
91
+ if (!isNaN(afterMs)) dateRange.afterMs = afterMs;
92
+ }
93
+ if (input.filters?.before) {
94
+ const beforeMs = new Date(input.filters.before).getTime();
95
+ if (!isNaN(beforeMs)) dateRange.beforeMs = beforeMs;
96
+ }
97
+
98
98
  // Search graph nodes
99
99
  const limit = Math.max(1, Math.min(input.num_results ?? 20, 50));
100
100
  const searchResults = await searchGraphNodes(
@@ -102,6 +102,9 @@ async function handleMemoryRecall(
102
102
  limit,
103
103
  [scopeId],
104
104
  sparseVector,
105
+ dateRange.afterMs != null || dateRange.beforeMs != null
106
+ ? dateRange
107
+ : undefined,
105
108
  );
106
109
  if (searchResults.length === 0) {
107
110
  return { results: [], mode: "memory", query: input.query };
@@ -121,16 +124,6 @@ async function handleMemoryRecall(
121
124
  if (!input.filters.types.includes(node.type)) return [];
122
125
  }
123
126
 
124
- // Date filters
125
- if (input.filters?.after) {
126
- const afterMs = new Date(input.filters.after).getTime();
127
- if (!isNaN(afterMs) && node.created < afterMs) return [];
128
- }
129
- if (input.filters?.before) {
130
- const beforeMs = new Date(input.filters.before).getTime();
131
- if (!isNaN(beforeMs) && node.created > beforeMs) return [];
132
- }
133
-
134
127
  return [
135
128
  {
136
129
  id: node.id,
@@ -157,7 +150,28 @@ async function handleArchiveRecall(
157
150
 
158
151
  try {
159
152
  const limit = Math.max(1, Math.min(input.num_results ?? 20, 50));
160
- const ftsMatch = buildFtsMatchQuery(input.query.trim());
153
+ const ftsMatch = buildFtsMatchQuery(input.query.trim(), {
154
+ allowFts5Syntax: true,
155
+ });
156
+
157
+ const afterMs = input.filters?.after
158
+ ? new Date(input.filters.after).getTime()
159
+ : NaN;
160
+ const beforeMs = input.filters?.before
161
+ ? new Date(input.filters.before).getTime()
162
+ : NaN;
163
+ const dateConditions: string[] = [];
164
+ const dateParams: number[] = [];
165
+ if (!isNaN(afterMs)) {
166
+ dateConditions.push("m.created_at >= ?");
167
+ dateParams.push(afterMs);
168
+ }
169
+ if (!isNaN(beforeMs)) {
170
+ dateConditions.push("m.created_at <= ?");
171
+ dateParams.push(beforeMs);
172
+ }
173
+ const dateClause =
174
+ dateConditions.length > 0 ? " AND " + dateConditions.join(" AND ") : "";
161
175
 
162
176
  type ArchiveRow = {
163
177
  id: string;
@@ -177,11 +191,12 @@ async function handleArchiveRecall(
177
191
  JOIN messages m ON m.id = fts.message_id
178
192
  JOIN conversations c ON c.id = m.conversation_id
179
193
  WHERE messages_fts MATCH ?
180
- AND c.memory_scope_id = ?
194
+ AND c.memory_scope_id = ?${dateClause}
181
195
  ORDER BY rank
182
196
  LIMIT ?`,
183
197
  ftsMatch,
184
198
  scopeId,
199
+ ...dateParams,
185
200
  limit,
186
201
  );
187
202
  } else if (!input.query.trim()) {
@@ -199,11 +214,12 @@ async function handleArchiveRecall(
199
214
  `SELECT m.id, m.content, m.role, m.created_at, c.id as conversation_id
200
215
  FROM messages m
201
216
  JOIN conversations c ON c.id = m.conversation_id
202
- WHERE m.content LIKE ? ESCAPE '\\' AND c.memory_scope_id = ?
217
+ WHERE m.content LIKE ? ESCAPE '\\' AND c.memory_scope_id = ?${dateClause}
203
218
  ORDER BY m.created_at DESC
204
219
  LIMIT ?`,
205
220
  likePattern,
206
221
  scopeId,
222
+ ...dateParams,
207
223
  limit,
208
224
  );
209
225
  }
@@ -228,199 +244,58 @@ async function handleArchiveRecall(
228
244
  }
229
245
 
230
246
  // ---------------------------------------------------------------------------
231
- // remember handler
247
+ // remember handler — writes to PKB buffer + daily archive
232
248
  // ---------------------------------------------------------------------------
233
249
 
234
250
  export interface RememberInput {
235
- op: "save" | "update" | "delete";
236
- memory_id?: string;
237
- content?: string;
238
- type?: string;
239
- significance?: number;
240
- emotional_charge?: {
241
- valence?: number;
242
- intensity?: number;
243
- };
251
+ content: string;
244
252
  }
245
253
 
246
254
  export interface RememberResult {
247
255
  success: boolean;
248
- op: string;
249
- memory_id?: string;
250
256
  message: string;
251
257
  }
252
258
 
253
- const VALID_TYPES = new Set<MemoryType>([
254
- "episodic",
255
- "semantic",
256
- "procedural",
257
- "emotional",
258
- "prospective",
259
- "behavioral",
260
- "narrative",
261
- "shared",
262
- ]);
263
-
264
259
  export function handleRemember(
265
260
  input: RememberInput,
266
- conversationId: string,
267
- scopeId: string,
261
+ _conversationId: string,
262
+ _scopeId: string,
268
263
  ): RememberResult {
269
- switch (input.op) {
270
- case "save":
271
- return handleSave(input, conversationId, scopeId);
272
- case "update":
273
- return handleUpdate(input);
274
- case "delete":
275
- return handleDelete(input);
276
- default:
277
- return {
278
- success: false,
279
- op: input.op,
280
- message: `Unknown operation: ${input.op}`,
281
- };
264
+ if (!input.content || input.content.trim().length === 0) {
265
+ return { success: false, message: "content is required" };
282
266
  }
283
- }
284
267
 
285
- function handleSave(
286
- input: RememberInput,
287
- conversationId: string,
288
- scopeId: string,
289
- ): RememberResult {
290
- if (!input.content) {
291
- return {
292
- success: false,
293
- op: "save",
294
- message: "content is required for save",
295
- };
296
- }
297
- if (!input.type || !VALID_TYPES.has(input.type as MemoryType)) {
298
- return {
299
- success: false,
300
- op: "save",
301
- message: `type is required and must be one of: ${[...VALID_TYPES].join(", ")}`,
302
- };
268
+ const workspaceDir = getWorkspaceDir();
269
+ const pkbDir = join(workspaceDir, "pkb");
270
+ const archiveDir = join(pkbDir, "archive");
271
+
272
+ // Ensure directories exist
273
+ mkdirSync(pkbDir, { recursive: true });
274
+ mkdirSync(archiveDir, { recursive: true });
275
+
276
+ // Build timestamped entry
277
+ const now = new Date();
278
+ const month = now.toLocaleString("en-US", { month: "short" });
279
+ const day = now.getDate();
280
+ const hours = now.getHours();
281
+ const minutes = String(now.getMinutes()).padStart(2, "0");
282
+ const ampm = hours >= 12 ? "PM" : "AM";
283
+ const displayHour = hours % 12 || 12;
284
+ const entry = `- [${month} ${day}, ${displayHour}:${minutes} ${ampm}] ${input.content.trim()}\n`;
285
+
286
+ // Append to buffer.md
287
+ const bufferPath = join(pkbDir, "buffer.md");
288
+ appendFileSync(bufferPath, entry, "utf-8");
289
+
290
+ // Append to daily archive
291
+ const yyyy = now.getFullYear();
292
+ const mm = String(now.getMonth() + 1).padStart(2, "0");
293
+ const dd = String(now.getDate()).padStart(2, "0");
294
+ const archivePath = join(archiveDir, `${yyyy}-${mm}-${dd}.md`);
295
+ if (!existsSync(archivePath)) {
296
+ appendFileSync(archivePath, `# ${month} ${day}, ${yyyy}\n\n`, "utf-8");
303
297
  }
298
+ appendFileSync(archivePath, entry, "utf-8");
304
299
 
305
- const now = Date.now();
306
- const emotionalCharge: EmotionalCharge = {
307
- valence: clamp(input.emotional_charge?.valence ?? 0, -1, 1),
308
- intensity: clamp(input.emotional_charge?.intensity ?? 0, 0, 1),
309
- decayCurve: "linear" as DecayCurve,
310
- decayRate: 0.05,
311
- originalIntensity: clamp(input.emotional_charge?.intensity ?? 0, 0, 1),
312
- };
313
-
314
- const node: NewNode = {
315
- content: input.content,
316
- type: input.type as MemoryType,
317
- created: now,
318
- lastAccessed: now,
319
- lastConsolidated: now,
320
- eventDate: null,
321
- emotionalCharge,
322
- fidelity: "vivid" as Fidelity,
323
- confidence: 0.95, // Explicitly saved = high confidence
324
- significance: clamp(input.significance ?? 0.5, 0, 1),
325
- stability: 14,
326
- reinforcementCount: 0,
327
- lastReinforced: now,
328
- sourceConversations: [conversationId],
329
- sourceType: "direct" as SourceType,
330
- narrativeRole: null,
331
- partOfStory: null,
332
- imageRefs: null,
333
- scopeId,
334
- };
335
-
336
- const created = createNode(node);
337
-
338
- // Enqueue embedding job immediately
339
- enqueueGraphNodeEmbed(created.id);
340
-
341
- return {
342
- success: true,
343
- op: "save",
344
- memory_id: created.id,
345
- message: "Memory saved.",
346
- };
347
- }
348
-
349
- function handleUpdate(input: RememberInput): RememberResult {
350
- if (!input.memory_id) {
351
- return {
352
- success: false,
353
- op: "update",
354
- message: "memory_id is required for update",
355
- };
356
- }
357
-
358
- const existing = getNode(input.memory_id);
359
- if (!existing) {
360
- return {
361
- success: false,
362
- op: "update",
363
- memory_id: input.memory_id,
364
- message: "Memory not found",
365
- };
366
- }
367
-
368
- const changes: Record<string, unknown> = {};
369
- if (input.content) changes.content = input.content;
370
- if (input.significance != null)
371
- changes.significance = clamp(input.significance, 0, 1);
372
- if (input.type && VALID_TYPES.has(input.type as MemoryType))
373
- changes.type = input.type;
374
-
375
- if (Object.keys(changes).length > 0) {
376
- updateNode(input.memory_id, changes);
377
- // Re-embed if content changed
378
- if (input.content) {
379
- enqueueGraphNodeEmbed(input.memory_id);
380
- }
381
- }
382
-
383
- return {
384
- success: true,
385
- op: "update",
386
- memory_id: input.memory_id,
387
- message: "Memory updated.",
388
- };
389
- }
390
-
391
- function handleDelete(input: RememberInput): RememberResult {
392
- if (!input.memory_id) {
393
- return {
394
- success: false,
395
- op: "delete",
396
- message: "memory_id is required for delete",
397
- };
398
- }
399
-
400
- const existing = getNode(input.memory_id);
401
- if (!existing) {
402
- return {
403
- success: false,
404
- op: "delete",
405
- memory_id: input.memory_id,
406
- message: "Memory not found",
407
- };
408
- }
409
-
410
- deleteNode(input.memory_id);
411
-
412
- return {
413
- success: true,
414
- op: "delete",
415
- memory_id: input.memory_id,
416
- message: "Memory deleted.",
417
- };
418
- }
419
-
420
- // ---------------------------------------------------------------------------
421
- // Helpers
422
- // ---------------------------------------------------------------------------
423
-
424
- function clamp(v: number, min: number, max: number): number {
425
- return Math.max(min, Math.min(max, v));
300
+ return { success: true, message: "Saved to knowledge base." };
426
301
  }