@vellumai/assistant 0.5.15 → 0.6.0

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 (503) hide show
  1. package/ARCHITECTURE.md +3 -3
  2. package/Dockerfile +0 -3
  3. package/docs/architecture/integrations.md +15 -14
  4. package/knip.json +4 -1
  5. package/openapi.yaml +670 -122
  6. package/package.json +1 -1
  7. package/src/__tests__/actor-token-service.test.ts +68 -0
  8. package/src/__tests__/agent-loop.test.ts +0 -32
  9. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  10. package/src/__tests__/anthropic-provider.test.ts +57 -3
  11. package/src/__tests__/app-compiler.test.ts +120 -0
  12. package/src/__tests__/assistant-feature-flags-integration.test.ts +5 -377
  13. package/src/__tests__/call-conversation-messages.test.ts +2 -6
  14. package/src/__tests__/call-domain.test.ts +2 -6
  15. package/src/__tests__/call-pointer-messages.test.ts +2 -14
  16. package/src/__tests__/call-recovery.test.ts +2 -6
  17. package/src/__tests__/call-routes-http.test.ts +2 -6
  18. package/src/__tests__/call-store.test.ts +2 -6
  19. package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
  20. package/src/__tests__/canonical-guardian-store.test.ts +2 -6
  21. package/src/__tests__/ces-rpc-credential-backend.test.ts +4 -1
  22. package/src/__tests__/channel-delivery-store.test.ts +2 -6
  23. package/src/__tests__/channel-retry-sweep.test.ts +2 -6
  24. package/src/__tests__/checker.test.ts +84 -3
  25. package/src/__tests__/clawhub.test.ts +54 -24
  26. package/src/__tests__/cli-command-risk-guard.test.ts +108 -6
  27. package/src/__tests__/cli-memory.test.ts +377 -0
  28. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +12 -2
  29. package/src/__tests__/config-schema.test.ts +1 -3
  30. package/src/__tests__/config-set-platform-guard.test.ts +302 -0
  31. package/src/__tests__/config-watcher-feature-flags.test.ts +211 -0
  32. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
  33. package/src/__tests__/contacts-tools.test.ts +31 -0
  34. package/src/__tests__/context-overflow-reducer.test.ts +86 -0
  35. package/src/__tests__/context-token-estimator.test.ts +175 -10
  36. package/src/__tests__/conversation-agent-loop-overflow.test.ts +9 -0
  37. package/src/__tests__/conversation-agent-loop.test.ts +9 -0
  38. package/src/__tests__/conversation-attachments.test.ts +2 -6
  39. package/src/__tests__/conversation-attention-store.test.ts +2 -6
  40. package/src/__tests__/conversation-clear-safety.test.ts +2 -6
  41. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
  42. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
  43. package/src/__tests__/conversation-disk-view.test.ts +2 -6
  44. package/src/__tests__/conversation-error.test.ts +33 -2
  45. package/src/__tests__/conversation-fork-crud.test.ts +2 -6
  46. package/src/__tests__/conversation-history-web-search.test.ts +5 -0
  47. package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
  48. package/src/__tests__/conversation-media-retry.test.ts +91 -0
  49. package/src/__tests__/conversation-runtime-assembly.test.ts +7 -4
  50. package/src/__tests__/conversation-slash-commands.test.ts +2 -6
  51. package/src/__tests__/conversation-starter-routes.test.ts +20 -11
  52. package/src/__tests__/conversation-store.test.ts +2 -6
  53. package/src/__tests__/conversation-usage.test.ts +3 -6
  54. package/src/__tests__/conversation-wipe.test.ts +11 -408
  55. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
  56. package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
  57. package/src/__tests__/credential-security-e2e.test.ts +6 -1
  58. package/src/__tests__/docker-signing-key-bootstrap.test.ts +7 -73
  59. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -7
  60. package/src/__tests__/followup-tools.test.ts +2 -6
  61. package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
  62. package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
  63. package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
  64. package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
  65. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
  66. package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
  67. package/src/__tests__/guardian-action-store.test.ts +2 -6
  68. package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
  69. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
  70. package/src/__tests__/guardian-dispatch.test.ts +2 -6
  71. package/src/__tests__/guardian-grant-minting.test.ts +2 -14
  72. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
  73. package/src/__tests__/guardian-routing-invariants.test.ts +343 -6
  74. package/src/__tests__/guardian-routing-state.test.ts +2 -6
  75. package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
  76. package/src/__tests__/heartbeat-service.test.ts +1 -3
  77. package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
  78. package/src/__tests__/injection-block.test.ts +154 -0
  79. package/src/__tests__/install-meta.test.ts +506 -0
  80. package/src/__tests__/install-skill-routing.test.ts +292 -0
  81. package/src/__tests__/intent-routing.test.ts +6 -18
  82. package/src/__tests__/invite-redemption-service.test.ts +2 -6
  83. package/src/__tests__/invite-routes-http.test.ts +2 -6
  84. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +2 -14
  85. package/src/__tests__/list-messages-attachments.test.ts +2 -6
  86. package/src/__tests__/llm-context-route-provider.test.ts +2 -6
  87. package/src/__tests__/llm-request-log-turn-query.test.ts +2 -6
  88. package/src/__tests__/llm-usage-store.test.ts +2 -6
  89. package/src/__tests__/log-export-workspace.test.ts +4 -34
  90. package/src/__tests__/managed-skill-lifecycle.test.ts +7 -37
  91. package/src/__tests__/managed-store.test.ts +40 -21
  92. package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
  93. package/src/__tests__/memory-recall-log-store.test.ts +2 -6
  94. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
  95. package/src/__tests__/messaging-send-tool.test.ts +6 -6
  96. package/src/__tests__/migration-cross-version-compatibility.test.ts +1 -29
  97. package/src/__tests__/migration-export-http.test.ts +3 -34
  98. package/src/__tests__/migration-import-commit-http.test.ts +1 -29
  99. package/src/__tests__/migration-import-preflight-http.test.ts +3 -34
  100. package/src/__tests__/no-domain-routing-in-prompt-guard.test.ts +2 -1
  101. package/src/__tests__/non-member-access-request.test.ts +2 -6
  102. package/src/__tests__/notification-guardian-path.test.ts +2 -6
  103. package/src/__tests__/oauth-apps-routes.test.ts +120 -10
  104. package/src/__tests__/oauth-cli.test.ts +364 -2
  105. package/src/__tests__/oauth-connect-orchestrator.test.ts +709 -0
  106. package/src/__tests__/oauth-provider-serializer.test.ts +2 -1
  107. package/src/__tests__/oauth-provider-visibility.test.ts +149 -0
  108. package/src/__tests__/oauth-providers-routes.test.ts +5 -2
  109. package/src/__tests__/oauth-store.test.ts +0 -5
  110. package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
  111. package/src/__tests__/outlook-attachments.test.ts +301 -0
  112. package/src/__tests__/outlook-automation-tools.test.ts +425 -0
  113. package/src/__tests__/outlook-categories.test.ts +212 -0
  114. package/src/__tests__/outlook-client-automation.test.ts +246 -0
  115. package/src/__tests__/outlook-compose-tools.test.ts +325 -0
  116. package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
  117. package/src/__tests__/outlook-email-watcher.test.ts +322 -0
  118. package/src/__tests__/outlook-follow-up.test.ts +196 -0
  119. package/src/__tests__/outlook-messaging-provider.test.ts +1071 -0
  120. package/src/__tests__/outlook-trash.test.ts +77 -0
  121. package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
  122. package/src/__tests__/path-policy.test.ts +2 -17
  123. package/src/__tests__/permission-types.test.ts +0 -1
  124. package/src/__tests__/platform-callback-registration.test.ts +7 -11
  125. package/src/__tests__/playbook-execution.test.ts +76 -80
  126. package/src/__tests__/playbook-tools.test.ts +5 -7
  127. package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
  128. package/src/__tests__/provider-error-scenarios.test.ts +21 -2
  129. package/src/__tests__/qdrant-manager.test.ts +68 -21
  130. package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
  131. package/src/__tests__/registry.test.ts +2 -2
  132. package/src/__tests__/require-fresh-approval.test.ts +64 -3
  133. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
  134. package/src/__tests__/runtime-events-sse.test.ts +2 -6
  135. package/src/__tests__/sandbox-diagnostics.test.ts +20 -29
  136. package/src/__tests__/scaffold-managed-skill-tool.test.ts +2 -10
  137. package/src/__tests__/schedule-store.test.ts +2 -6
  138. package/src/__tests__/schedule-tools.test.ts +2 -6
  139. package/src/__tests__/scheduler-recurrence.test.ts +1 -5
  140. package/src/__tests__/scoped-approval-grants.test.ts +2 -6
  141. package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
  142. package/src/__tests__/search-skills-unified.test.ts +421 -0
  143. package/src/__tests__/secret-allowlist.test.ts +20 -35
  144. package/src/__tests__/secret-onetime-send.test.ts +2 -0
  145. package/src/__tests__/send-endpoint-busy.test.ts +2 -6
  146. package/src/__tests__/sequence-store.test.ts +2 -6
  147. package/src/__tests__/server-history-render.test.ts +2 -6
  148. package/src/__tests__/shell-credential-ref.test.ts +0 -5
  149. package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
  150. package/src/__tests__/skill-feature-flags.test.ts +6 -6
  151. package/src/__tests__/skill-load-feature-flag.test.ts +13 -54
  152. package/src/__tests__/skill-load-inline-command.test.ts +3 -65
  153. package/src/__tests__/skill-load-inline-includes.test.ts +3 -65
  154. package/src/__tests__/skill-load-tool.test.ts +3 -67
  155. package/src/__tests__/skill-memory.test.ts +480 -195
  156. package/src/__tests__/skills-uninstall.test.ts +2 -2
  157. package/src/__tests__/skills.test.ts +23 -50
  158. package/src/__tests__/slack-channel-config.test.ts +2 -21
  159. package/src/__tests__/slack-inbound-verification.test.ts +2 -6
  160. package/src/__tests__/starter-bundle.test.ts +2 -8
  161. package/src/__tests__/stt-hints.test.ts +7 -2
  162. package/src/__tests__/system-prompt.test.ts +25 -45
  163. package/src/__tests__/task-compiler.test.ts +2 -27
  164. package/src/__tests__/task-management-tools.test.ts +2 -27
  165. package/src/__tests__/task-memory-cleanup.test.ts +173 -250
  166. package/src/__tests__/task-runner.test.ts +2 -27
  167. package/src/__tests__/task-scheduler.test.ts +2 -27
  168. package/src/__tests__/terminal-tools.test.ts +1 -17
  169. package/src/__tests__/test-preload.ts +3 -0
  170. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +0 -79
  171. package/src/__tests__/tool-approval-handler.test.ts +4 -27
  172. package/src/__tests__/tool-execution-abort-cleanup.test.ts +2 -11
  173. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -25
  174. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  175. package/src/__tests__/tool-executor.test.ts +0 -1
  176. package/src/__tests__/tool-grant-request-escalation.test.ts +4 -27
  177. package/src/__tests__/tool-preview-lifecycle.test.ts +0 -20
  178. package/src/__tests__/tool-side-effects-slack-dm.test.ts +276 -0
  179. package/src/__tests__/trust-store.test.ts +10 -42
  180. package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -30
  181. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +3 -27
  182. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -28
  183. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -28
  184. package/src/__tests__/trusted-contact-verification.test.ts +2 -28
  185. package/src/__tests__/turn-boundary-resolution.test.ts +2 -34
  186. package/src/__tests__/twilio-provider.test.ts +0 -16
  187. package/src/__tests__/twilio-routes-twiml.test.ts +7 -12
  188. package/src/__tests__/twilio-routes.test.ts +0 -24
  189. package/src/__tests__/update-bulletin.test.ts +17 -89
  190. package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -26
  191. package/src/__tests__/usage-routes.test.ts +2 -27
  192. package/src/__tests__/user-reference.test.ts +1 -5
  193. package/src/__tests__/vbundle-pax-and-symlink.test.ts +4 -34
  194. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +2 -53
  195. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  196. package/src/__tests__/voice-invite-redemption.test.ts +2 -27
  197. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -30
  198. package/src/__tests__/voice-session-bridge.test.ts +2 -27
  199. package/src/__tests__/volume-security-guard.test.ts +2 -0
  200. package/src/__tests__/workspace-lifecycle.test.ts +29 -1
  201. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +4 -29
  202. package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +2 -2
  203. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +4 -29
  204. package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
  205. package/src/__tests__/workspace-migration-down-functions.test.ts +0 -6
  206. package/src/__tests__/workspace-policy.test.ts +1 -1
  207. package/src/acp/client-handler.ts +1 -2
  208. package/src/agent/attachments.ts +7 -2
  209. package/src/agent/image-optimize.ts +165 -0
  210. package/src/agent/loop.ts +1 -15
  211. package/src/bundler/app-compiler.ts +179 -2
  212. package/src/bundler/package-resolver.ts +3 -5
  213. package/src/cli/__tests__/notifications.test.ts +1 -24
  214. package/src/cli/cli-memory.ts +179 -0
  215. package/src/cli/commands/avatar.ts +3 -3
  216. package/src/cli/commands/config.ts +26 -13
  217. package/src/cli/commands/doctor.ts +2 -2
  218. package/src/cli/commands/memory.ts +41 -55
  219. package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
  220. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
  221. package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
  222. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
  223. package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
  224. package/src/cli/commands/oauth/connect.ts +26 -6
  225. package/src/cli/commands/oauth/mode.ts +7 -0
  226. package/src/cli/commands/oauth/providers.ts +49 -42
  227. package/src/cli/commands/oauth/shared.ts +39 -3
  228. package/src/cli/commands/platform/__tests__/connect.test.ts +3 -49
  229. package/src/cli/commands/platform/__tests__/disconnect.test.ts +3 -49
  230. package/src/cli/commands/platform/__tests__/status.test.ts +5 -55
  231. package/src/cli/commands/platform/index.ts +16 -16
  232. package/src/cli/commands/skills.ts +88 -16
  233. package/src/cli/commands/trust.ts +2 -2
  234. package/src/cli/lib/daemon-credential-client.ts +2 -3
  235. package/src/config/bundled-skills/acp/TOOLS.json +1 -1
  236. package/src/config/bundled-skills/computer-use/TOOLS.json +7 -7
  237. package/src/config/bundled-skills/contacts/SKILL.md +0 -1
  238. package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
  239. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
  240. package/src/config/bundled-skills/gmail/SKILL.md +2 -10
  241. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
  242. package/src/config/bundled-skills/messaging/SKILL.md +26 -19
  243. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
  244. package/src/config/bundled-skills/outlook/SKILL.md +189 -0
  245. package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
  246. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
  247. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
  248. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
  249. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
  250. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
  251. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
  252. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
  253. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
  254. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
  255. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
  256. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
  257. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
  258. package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
  259. package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
  260. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
  261. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
  262. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
  263. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
  264. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
  265. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
  266. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
  267. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
  268. package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
  269. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
  270. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
  271. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
  272. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
  273. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  274. package/src/config/bundled-skills/slack/SKILL.md +1 -7
  275. package/src/config/bundled-tool-registry.ts +56 -4
  276. package/src/config/env-registry.ts +15 -8
  277. package/src/config/feature-flag-registry.json +29 -116
  278. package/src/config/loader.ts +4 -0
  279. package/src/config/schemas/platform.ts +8 -0
  280. package/src/config/schemas/security.ts +0 -6
  281. package/src/config/schemas/services.ts +8 -0
  282. package/src/config/schemas/timeouts.ts +1 -1
  283. package/src/config/skills.ts +18 -7
  284. package/src/context/token-estimator.ts +25 -18
  285. package/src/context/window-manager.ts +32 -9
  286. package/src/credential-execution/approval-bridge.ts +0 -1
  287. package/src/credential-execution/process-manager.ts +3 -1
  288. package/src/daemon/config-watcher.ts +51 -0
  289. package/src/daemon/context-overflow-reducer.ts +46 -2
  290. package/src/daemon/conversation-agent-loop-handlers.ts +123 -82
  291. package/src/daemon/conversation-agent-loop.ts +99 -63
  292. package/src/daemon/conversation-error.ts +31 -8
  293. package/src/daemon/conversation-lifecycle.ts +33 -0
  294. package/src/daemon/conversation-media-retry.ts +85 -7
  295. package/src/daemon/conversation-notifiers.ts +4 -1
  296. package/src/daemon/conversation-process.ts +1 -0
  297. package/src/daemon/conversation-runtime-assembly.ts +5 -0
  298. package/src/daemon/conversation-usage.ts +1 -0
  299. package/src/daemon/conversation.ts +41 -2
  300. package/src/daemon/daemon-control.ts +8 -2
  301. package/src/daemon/handlers/shared.ts +22 -12
  302. package/src/daemon/handlers/skills.ts +423 -201
  303. package/src/daemon/lifecycle.ts +52 -4
  304. package/src/daemon/main.ts +5 -1
  305. package/src/daemon/message-types/conversations.ts +5 -1
  306. package/src/daemon/message-types/messages.ts +3 -1
  307. package/src/daemon/message-types/skills.ts +97 -36
  308. package/src/daemon/providers-setup.ts +7 -0
  309. package/src/daemon/server.ts +35 -22
  310. package/src/daemon/tool-side-effects.ts +27 -5
  311. package/src/events/domain-events.ts +1 -2
  312. package/src/heartbeat/heartbeat-service.ts +1 -0
  313. package/src/hooks/cli.ts +2 -2
  314. package/src/hooks/runner.ts +15 -38
  315. package/src/inbound/platform-callback-registration.ts +14 -14
  316. package/src/memory/admin.ts +11 -45
  317. package/src/memory/conversation-bootstrap.ts +2 -0
  318. package/src/memory/conversation-crud.ts +242 -348
  319. package/src/memory/conversation-group-migration.ts +157 -0
  320. package/src/memory/conversation-queries.ts +4 -2
  321. package/src/memory/db-init.ts +39 -3
  322. package/src/memory/embed.ts +73 -0
  323. package/src/memory/embedding-backend.ts +8 -14
  324. package/src/memory/embedding-runtime-manager.ts +12 -114
  325. package/src/memory/fingerprint.ts +2 -2
  326. package/src/memory/graph/bootstrap.ts +512 -0
  327. package/src/memory/graph/capability-seed.ts +297 -0
  328. package/src/memory/graph/consolidation.ts +691 -0
  329. package/src/memory/graph/conversation-graph-memory.ts +630 -0
  330. package/src/memory/graph/decay.test.ts +208 -0
  331. package/src/memory/graph/decay.ts +195 -0
  332. package/src/memory/graph/extraction-job.ts +69 -0
  333. package/src/memory/graph/extraction.test.ts +936 -0
  334. package/src/memory/graph/extraction.ts +1254 -0
  335. package/src/memory/graph/graph-search.ts +266 -0
  336. package/src/memory/graph/image-ref-utils.ts +29 -0
  337. package/src/memory/graph/injection.test.ts +513 -0
  338. package/src/memory/graph/injection.ts +439 -0
  339. package/src/memory/graph/inspect.ts +534 -0
  340. package/src/memory/graph/narrative.ts +267 -0
  341. package/src/memory/graph/pattern-scan.ts +269 -0
  342. package/src/memory/graph/retriever.ts +1008 -0
  343. package/src/memory/graph/scoring.test.ts +548 -0
  344. package/src/memory/graph/scoring.ts +232 -0
  345. package/src/memory/graph/serendipity.ts +65 -0
  346. package/src/memory/graph/store.test.ts +1050 -0
  347. package/src/memory/graph/store.ts +699 -0
  348. package/src/memory/graph/tool-handlers.ts +426 -0
  349. package/src/memory/graph/tools.ts +141 -0
  350. package/src/memory/graph/triggers.test.ts +487 -0
  351. package/src/memory/graph/triggers.ts +223 -0
  352. package/src/memory/graph/types.ts +271 -0
  353. package/src/memory/group-crud.ts +191 -0
  354. package/src/memory/indexer.ts +37 -19
  355. package/src/memory/job-handlers/cleanup.ts +0 -53
  356. package/src/memory/job-handlers/conversation-starters.ts +91 -53
  357. package/src/memory/job-handlers/embedding.test.ts +3 -27
  358. package/src/memory/job-handlers/embedding.ts +5 -31
  359. package/src/memory/job-handlers/index-maintenance.ts +23 -11
  360. package/src/memory/job-handlers/summarization.ts +32 -17
  361. package/src/memory/job-utils.ts +1 -1
  362. package/src/memory/jobs-store.ts +50 -70
  363. package/src/memory/jobs-worker.ts +147 -112
  364. package/src/memory/llm-usage-store.ts +35 -2
  365. package/src/memory/message-content.ts +1 -0
  366. package/src/memory/migrations/201-oauth-providers-feature-flag.ts +11 -0
  367. package/src/memory/migrations/202-drop-callback-transport-column.ts +13 -0
  368. package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
  369. package/src/memory/migrations/203-drop-memory-items-tables.ts +23 -0
  370. package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
  371. package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
  372. package/src/memory/migrations/index.ts +6 -0
  373. package/src/memory/migrations/registry.ts +8 -0
  374. package/src/memory/qdrant-client.ts +44 -17
  375. package/src/memory/qdrant-manager.ts +26 -5
  376. package/src/memory/schema/index.ts +1 -0
  377. package/src/memory/schema/memory-graph.ts +139 -0
  378. package/src/memory/schema/oauth.ts +1 -1
  379. package/src/memory/search/semantic.ts +47 -91
  380. package/src/memory/slack-thread-store.ts +17 -0
  381. package/src/memory/task-memory-cleanup.ts +28 -50
  382. package/src/messaging/providers/outlook/adapter.ts +200 -0
  383. package/src/messaging/providers/outlook/client.ts +610 -0
  384. package/src/messaging/providers/outlook/types.ts +201 -0
  385. package/src/notifications/adapters/macos.ts +1 -0
  386. package/src/notifications/adapters/slack.ts +1 -1
  387. package/src/notifications/copy-composer.ts +9 -0
  388. package/src/notifications/signal.ts +16 -0
  389. package/src/oauth/__tests__/identity-verifier.test.ts +1 -1
  390. package/src/oauth/connect-orchestrator.ts +10 -3
  391. package/src/oauth/oauth-store.ts +10 -11
  392. package/src/oauth/provider-serializer.ts +3 -0
  393. package/src/oauth/provider-visibility.ts +16 -0
  394. package/src/oauth/seed-providers.ts +50 -17
  395. package/src/permissions/checker.ts +62 -9
  396. package/src/permissions/defaults.ts +4 -4
  397. package/src/permissions/types.ts +2 -4
  398. package/src/permissions/workspace-policy.ts +1 -1
  399. package/src/playbooks/playbook-compiler.ts +19 -18
  400. package/src/playbooks/types.ts +4 -3
  401. package/src/prompts/system-prompt.ts +6 -93
  402. package/src/prompts/templates/UPDATES.md +6 -0
  403. package/src/providers/anthropic/client.ts +47 -19
  404. package/src/providers/gemini/client.ts +1 -1
  405. package/src/providers/openai/client.ts +1 -1
  406. package/src/providers/registry.ts +1 -1
  407. package/src/providers/retry.ts +19 -3
  408. package/src/runtime/actor-trust-resolver.ts +5 -1
  409. package/src/runtime/auth/__tests__/credential-service.test.ts +1 -27
  410. package/src/runtime/auth/__tests__/token-service.test.ts +1 -25
  411. package/src/runtime/auth/route-policy.ts +7 -4
  412. package/src/runtime/guardian-reply-router.ts +10 -2
  413. package/src/runtime/http-server.ts +23 -3
  414. package/src/runtime/middleware/auth.ts +20 -0
  415. package/src/runtime/routes/attachment-routes.test.ts +106 -0
  416. package/src/runtime/routes/attachment-routes.ts +106 -16
  417. package/src/runtime/routes/brain-graph-routes.ts +21 -22
  418. package/src/runtime/routes/btw-routes.ts +8 -0
  419. package/src/runtime/routes/conversation-management-routes.ts +2 -0
  420. package/src/runtime/routes/conversation-query-routes.ts +2 -58
  421. package/src/runtime/routes/conversation-starter-routes.ts +2 -2
  422. package/src/runtime/routes/debug-routes.ts +1 -1
  423. package/src/runtime/routes/global-search-routes.ts +21 -19
  424. package/src/runtime/routes/group-routes.ts +207 -0
  425. package/src/runtime/routes/guardian-action-routes.ts +21 -10
  426. package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
  427. package/src/runtime/routes/inbound-message-handler.ts +19 -0
  428. package/src/runtime/routes/inbound-stages/background-dispatch.ts +43 -2
  429. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
  430. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
  431. package/src/runtime/routes/memory-item-routes.test.ts +2 -31
  432. package/src/runtime/routes/memory-item-routes.ts +385 -341
  433. package/src/runtime/routes/oauth-apps.ts +18 -1
  434. package/src/runtime/routes/oauth-providers.ts +13 -1
  435. package/src/runtime/routes/schedule-routes.ts +2 -0
  436. package/src/runtime/routes/settings-routes.ts +1 -0
  437. package/src/runtime/routes/skills-routes.ts +103 -37
  438. package/src/runtime/routes/usage-routes.ts +19 -2
  439. package/src/runtime/routes/work-items-routes.test.ts +2 -27
  440. package/src/runtime/routes/workspace-routes.test.ts +3 -27
  441. package/src/schedule/scheduler.ts +8 -1
  442. package/src/security/oauth2.ts +1 -1
  443. package/src/security/secret-allowlist.ts +4 -4
  444. package/src/security/secure-keys.ts +4 -8
  445. package/src/shared/provider-env-vars.ts +19 -0
  446. package/src/skills/catalog-cache.ts +5 -0
  447. package/src/skills/catalog-install.ts +15 -14
  448. package/src/skills/clawhub.ts +134 -154
  449. package/src/skills/install-meta.ts +208 -0
  450. package/src/skills/managed-store.ts +27 -16
  451. package/src/skills/skill-memory.ts +210 -96
  452. package/src/skills/skillssh-registry.ts +19 -17
  453. package/src/tasks/task-runner.ts +3 -1
  454. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
  455. package/src/tools/browser/runtime-check.ts +3 -1
  456. package/src/tools/memory/register.ts +63 -46
  457. package/src/tools/permission-checker.ts +7 -19
  458. package/src/tools/shared/filesystem/image-read.ts +22 -85
  459. package/src/tools/skills/skill-script-runner.ts +1 -1
  460. package/src/tools/terminal/safe-env.ts +1 -0
  461. package/src/tools/tool-manifest.ts +3 -3
  462. package/src/util/browser.ts +25 -10
  463. package/src/util/bun-runtime.ts +172 -0
  464. package/src/util/device-id.ts +3 -65
  465. package/src/watcher/providers/outlook-calendar.ts +343 -0
  466. package/src/watcher/providers/outlook.ts +198 -0
  467. package/src/workspace/git-service.ts +27 -6
  468. package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
  469. package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
  470. package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
  471. package/src/workspace/migrations/registry.ts +6 -0
  472. package/src/__tests__/context-memory-e2e.test.ts +0 -415
  473. package/src/__tests__/journal-context.test.ts +0 -268
  474. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
  475. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
  476. package/src/__tests__/memory-query-builder.test.ts +0 -59
  477. package/src/__tests__/memory-recall-quality.test.ts +0 -1046
  478. package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
  479. package/src/__tests__/memory-regressions.test.ts +0 -3696
  480. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
  481. package/src/daemon/conversation-memory.ts +0 -207
  482. package/src/memory/conversation-starters-cadence.ts +0 -74
  483. package/src/memory/items-extractor.ts +0 -860
  484. package/src/memory/job-handlers/batch-extraction.ts +0 -741
  485. package/src/memory/job-handlers/extraction.ts +0 -40
  486. package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -383
  487. package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
  488. package/src/memory/journal-memory.ts +0 -224
  489. package/src/memory/query-builder.ts +0 -47
  490. package/src/memory/query-expansion.ts +0 -83
  491. package/src/memory/retriever.test.ts +0 -1590
  492. package/src/memory/retriever.ts +0 -1323
  493. package/src/memory/search/formatting.test.ts +0 -140
  494. package/src/memory/search/formatting.ts +0 -262
  495. package/src/memory/search/mmr.ts +0 -136
  496. package/src/memory/search/ranking.ts +0 -15
  497. package/src/memory/search/staleness.ts +0 -40
  498. package/src/memory/search/tier-classifier.ts +0 -18
  499. package/src/memory/search/types.ts +0 -121
  500. package/src/prompts/journal-context.ts +0 -156
  501. package/src/tools/memory/definitions.ts +0 -69
  502. package/src/tools/memory/handlers.test.ts +0 -590
  503. package/src/tools/memory/handlers.ts +0 -434
@@ -1,25 +1,7 @@
1
- import { mkdtempSync, rmSync } from "node:fs";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
1
+ import { rmSync } from "node:fs";
4
2
  import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
5
3
 
6
- import { eq } from "drizzle-orm";
7
-
8
- const testDir = mkdtempSync(join(tmpdir(), "skill-memory-"));
9
-
10
- mock.module("../util/platform.js", () => ({
11
- getDataDir: () => testDir,
12
- isMacOS: () => process.platform === "darwin",
13
- isLinux: () => process.platform === "linux",
14
- isWindows: () => process.platform === "win32",
15
- getPidPath: () => join(testDir, "test.pid"),
16
- getDbPath: () => join(testDir, "test.db"),
17
- getLogPath: () => join(testDir, "test.log"),
18
- ensureDataDir: () => {},
19
- getWorkspaceSkillsDir: () => join(testDir, "skills"),
20
- getWorkspaceConfigPath: () => join(testDir, "config.json"),
21
- readPlatformToken: () => undefined,
22
- }));
4
+ import { eq, like } from "drizzle-orm";
23
5
 
24
6
  mock.module("../util/logger.js", () => ({
25
7
  getLogger: () =>
@@ -38,16 +20,25 @@ mock.module("../memory/qdrant-client.js", () => ({
38
20
  initQdrantClient: () => {},
39
21
  }));
40
22
 
41
- // Controllable mock for resolveCatalog used by seedCatalogSkillMemories
42
- let mockResolveCatalog: () => Promise<
43
- import("../skills/catalog-install.js").CatalogSkill[]
44
- > = async () => [];
23
+ // Controllable mock for loadSkillCatalog used by seedCatalogSkillMemories
24
+ let mockLoadSkillCatalog: () => import("../config/skills.js").SkillSummary[] =
25
+ () => [];
26
+
27
+ mock.module("../config/skills.js", () => ({
28
+ loadSkillCatalog: (..._args: unknown[]) => mockLoadSkillCatalog(),
29
+ }));
30
+
31
+ // Controllable mock for getCachedCatalogSync used by seedCatalogSkillMemories
32
+ let mockGetCachedCatalogSync: () => import("../skills/catalog-install.js").CatalogSkill[] =
33
+ () => [];
45
34
 
46
- mock.module("../skills/catalog-install.js", () => ({
47
- resolveCatalog: (..._args: unknown[]) => mockResolveCatalog(),
35
+ mock.module("../skills/catalog-cache.js", () => ({
36
+ getCachedCatalogSync: (..._args: unknown[]) => mockGetCachedCatalogSync(),
37
+ getCatalog: async () => mockGetCachedCatalogSync(),
38
+ invalidateCatalogCache: () => {},
48
39
  }));
49
40
 
50
- // Controllable mock for isAssistantFeatureFlagEnabled used by seedCatalogSkillMemories
41
+ // Controllable mock for isAssistantFeatureFlagEnabled used by resolveSkillStates
51
42
  let mockIsFeatureFlagEnabled: (key: string) => boolean = () => true;
52
43
 
53
44
  mock.module("../config/assistant-feature-flags.js", () => ({
@@ -78,40 +69,44 @@ mock.module("../config/loader.js", () => ({
78
69
  invalidateConfigCache: () => {},
79
70
  }));
80
71
 
72
+ import type { SkillSummary } from "../config/skills.js";
81
73
  import { getDb, initializeDb, resetDb } from "../memory/db.js";
82
- import { memoryItems, memoryJobs } from "../memory/schema.js";
83
- import type { CatalogSkill } from "../skills/catalog-install.js";
74
+ import { memoryGraphNodes, memoryJobs } from "../memory/schema.js";
84
75
  import {
85
76
  buildCapabilityStatement,
86
77
  deleteSkillCapabilityMemory,
78
+ fromSkillSummary,
87
79
  seedCatalogSkillMemories,
80
+ type SkillCapabilityInput,
88
81
  upsertSkillCapabilityMemory,
89
82
  } from "../skills/skill-memory.js";
83
+ import { ensureDataDir, getDbPath } from "../util/platform.js";
90
84
 
85
+ ensureDataDir();
91
86
  initializeDb();
92
87
 
93
88
  afterAll(() => {
94
89
  resetDb();
95
- try {
96
- rmSync(testDir, { recursive: true });
97
- } catch {
98
- // best effort cleanup
99
- }
100
90
  });
101
91
 
102
92
  function resetTables() {
103
93
  const db = getDb();
104
- db.run("DELETE FROM memory_item_sources");
105
94
  db.run("DELETE FROM memory_embeddings");
106
- db.run("DELETE FROM memory_items");
95
+ db.run("DELETE FROM memory_graph_nodes");
107
96
  db.run("DELETE FROM memory_jobs");
108
97
  }
109
98
 
110
- function makeSkill(overrides: Partial<CatalogSkill> = {}): CatalogSkill {
99
+ function makeSkillSummary(
100
+ overrides: Partial<SkillSummary> = {},
101
+ ): SkillSummary {
111
102
  return {
112
103
  id: "test-skill",
113
- name: "Test Skill",
104
+ name: "test-skill",
105
+ displayName: "Test Skill",
114
106
  description: "A skill for testing",
107
+ directoryPath: "/skills/test-skill",
108
+ skillFilePath: "/skills/test-skill/SKILL.md",
109
+ source: "managed",
115
110
  ...overrides,
116
111
  };
117
112
  }
@@ -120,33 +115,63 @@ function makeSkill(overrides: Partial<CatalogSkill> = {}): CatalogSkill {
120
115
 
121
116
  describe("buildCapabilityStatement", () => {
122
117
  test("includes display name, id, and description", () => {
123
- const entry = makeSkill({
124
- metadata: { vellum: { "display-name": "My Skill" } },
125
- });
126
- const result = buildCapabilityStatement(entry);
118
+ const input: SkillCapabilityInput = {
119
+ id: "test-skill",
120
+ displayName: "My Skill",
121
+ description: "A skill for testing",
122
+ };
123
+ const result = buildCapabilityStatement(input);
127
124
  expect(result).toContain('"My Skill"');
128
125
  expect(result).toContain("(test-skill)");
129
126
  expect(result).toContain("A skill for testing");
130
127
  });
131
128
 
132
129
  test("includes activation hints when present", () => {
133
- const entry = makeSkill({
134
- metadata: {
135
- vellum: {
136
- "display-name": "My Skill",
137
- "activation-hints": ["user asks to search", "needs web data"],
138
- },
139
- },
140
- });
141
- const result = buildCapabilityStatement(entry);
130
+ const input: SkillCapabilityInput = {
131
+ id: "test-skill",
132
+ displayName: "My Skill",
133
+ description: "A skill for testing",
134
+ activationHints: ["user asks to search", "needs web data"],
135
+ };
136
+ const result = buildCapabilityStatement(input);
142
137
  expect(result).toContain("Use when:");
143
138
  expect(result).toContain("user asks to search");
144
139
  expect(result).toContain("needs web data");
145
140
  });
146
141
 
147
- test("works without metadata (falls back to name)", () => {
148
- const entry = makeSkill({ metadata: undefined });
149
- const result = buildCapabilityStatement(entry);
142
+ test("includes avoidWhen routing cues when present", () => {
143
+ const input: SkillCapabilityInput = {
144
+ id: "test-skill",
145
+ displayName: "My Skill",
146
+ description: "A skill for testing",
147
+ avoidWhen: ["user wants local files only", "offline mode"],
148
+ };
149
+ const result = buildCapabilityStatement(input);
150
+ expect(result).toContain("Avoid when:");
151
+ expect(result).toContain("user wants local files only");
152
+ expect(result).toContain("offline mode");
153
+ });
154
+
155
+ test("includes both activationHints and avoidWhen when present", () => {
156
+ const input: SkillCapabilityInput = {
157
+ id: "test-skill",
158
+ displayName: "My Skill",
159
+ description: "A skill for testing",
160
+ activationHints: ["user asks to search"],
161
+ avoidWhen: ["offline mode"],
162
+ };
163
+ const result = buildCapabilityStatement(input);
164
+ expect(result).toContain("Use when: user asks to search.");
165
+ expect(result).toContain("Avoid when: offline mode.");
166
+ });
167
+
168
+ test("works with just name as displayName", () => {
169
+ const input: SkillCapabilityInput = {
170
+ id: "test-skill",
171
+ displayName: "Test Skill",
172
+ description: "A skill for testing",
173
+ };
174
+ const result = buildCapabilityStatement(input);
150
175
  expect(result).toContain('"Test Skill"');
151
176
  expect(result).toContain("(test-skill)");
152
177
  expect(result).toContain("A skill for testing");
@@ -154,77 +179,131 @@ describe("buildCapabilityStatement", () => {
154
179
 
155
180
  test("truncates long statements to 500 chars", () => {
156
181
  const longDesc = "x".repeat(600);
157
- const entry = makeSkill({ description: longDesc });
158
- const result = buildCapabilityStatement(entry);
182
+ const input: SkillCapabilityInput = {
183
+ id: "test-skill",
184
+ displayName: "Test Skill",
185
+ description: longDesc,
186
+ };
187
+ const result = buildCapabilityStatement(input);
159
188
  expect(result.length).toBe(500);
160
189
  });
161
190
  });
162
191
 
192
+ // ─── fromSkillSummary ────────────────────────────────────────────────────────
193
+
194
+ describe("fromSkillSummary", () => {
195
+ test("maps displayName from SkillSummary", () => {
196
+ const entry = makeSkillSummary({ displayName: "Pretty Name" });
197
+ const input = fromSkillSummary(entry);
198
+ expect(input.displayName).toBe("Pretty Name");
199
+ });
200
+
201
+ test("maps activationHints from SkillSummary", () => {
202
+ const hints = ["user asks to search", "needs web data"];
203
+ const entry = makeSkillSummary({ activationHints: hints });
204
+ const input = fromSkillSummary(entry);
205
+ expect(input.activationHints).toEqual(hints);
206
+ });
207
+
208
+ test("leaves activationHints undefined when not present", () => {
209
+ const entry = makeSkillSummary({ activationHints: undefined });
210
+ const input = fromSkillSummary(entry);
211
+ expect(input.activationHints).toBeUndefined();
212
+ });
213
+
214
+ test("maps avoidWhen from SkillSummary", () => {
215
+ const cues = ["offline mode", "user wants local files only"];
216
+ const entry = makeSkillSummary({ avoidWhen: cues });
217
+ const input = fromSkillSummary(entry);
218
+ expect(input.avoidWhen).toEqual(cues);
219
+ });
220
+
221
+ test("leaves avoidWhen undefined when not present", () => {
222
+ const entry = makeSkillSummary({ avoidWhen: undefined });
223
+ const input = fromSkillSummary(entry);
224
+ expect(input.avoidWhen).toBeUndefined();
225
+ });
226
+
227
+ test("copies id and description directly", () => {
228
+ const entry = makeSkillSummary({
229
+ id: "my-id",
230
+ description: "Does amazing things",
231
+ });
232
+ const input = fromSkillSummary(entry);
233
+ expect(input.id).toBe("my-id");
234
+ expect(input.description).toBe("Does amazing things");
235
+ });
236
+ });
237
+
163
238
  // ─── upsertSkillCapabilityMemory ─────────────────────────────────────────────
164
239
 
165
240
  describe("upsertSkillCapabilityMemory", () => {
166
241
  beforeEach(resetTables);
167
242
 
168
- test("inserts with correct kind, subject, confidence, importance", () => {
169
- const entry = makeSkill();
170
- upsertSkillCapabilityMemory("test-skill", entry);
243
+ test("inserts with correct type, content, confidence, significance", () => {
244
+ const input = fromSkillSummary(makeSkillSummary());
245
+ upsertSkillCapabilityMemory("test-skill", input);
171
246
 
172
247
  const db = getDb();
173
- const items = db.select().from(memoryItems).all();
248
+ const items = db.select().from(memoryGraphNodes).all();
174
249
  expect(items).toHaveLength(1);
175
- expect(items[0].kind).toBe("capability");
176
- expect(items[0].subject).toBe("skill:test-skill");
250
+ expect(items[0].type).toBe("procedural");
251
+ expect(items[0].content).toMatch(/^skill:test-skill\n/);
177
252
  expect(items[0].confidence).toBe(1.0);
178
- expect(items[0].importance).toBe(0.7);
179
- expect(items[0].status).toBe("active");
253
+ expect(items[0].significance).toBe(0.7);
254
+ expect(items[0].fidelity).toBe("vivid");
180
255
  expect(items[0].scopeId).toBe("default");
181
256
 
182
- // Should also enqueue an embed_item job
257
+ // Should also enqueue an embed_graph_node job
183
258
  const jobs = db.select().from(memoryJobs).all();
184
259
  expect(jobs).toHaveLength(1);
185
- expect(jobs[0].type).toBe("embed_item");
260
+ expect(jobs[0].type).toBe("embed_graph_node");
186
261
  });
187
262
 
188
- test("is idempotent (same entry only touches lastSeenAt)", () => {
189
- const entry = makeSkill();
190
- upsertSkillCapabilityMemory("test-skill", entry);
263
+ test("is idempotent (same entry only touches lastAccessed)", () => {
264
+ const input = fromSkillSummary(makeSkillSummary());
265
+ upsertSkillCapabilityMemory("test-skill", input);
191
266
 
192
267
  const db = getDb();
193
- const before = db.select().from(memoryItems).all();
268
+ const before = db.select().from(memoryGraphNodes).all();
194
269
  expect(before).toHaveLength(1);
195
- const originalLastSeen = before[0].lastSeenAt;
270
+ const originalLastAccessed = before[0].lastAccessed;
196
271
 
197
272
  // Upsert again
198
- upsertSkillCapabilityMemory("test-skill", entry);
273
+ upsertSkillCapabilityMemory("test-skill", input);
199
274
 
200
- const after = db.select().from(memoryItems).all();
275
+ const after = db.select().from(memoryGraphNodes).all();
201
276
  expect(after).toHaveLength(1);
202
- // Fingerprint should be the same, so only lastSeenAt changes
203
- expect(after[0].fingerprint).toBe(before[0].fingerprint);
204
- expect(after[0].lastSeenAt).toBeGreaterThanOrEqual(originalLastSeen);
277
+ // Same content, so only lastAccessed changes
278
+ expect(after[0].content).toBe(before[0].content);
279
+ expect(after[0].lastAccessed).toBeGreaterThanOrEqual(originalLastAccessed);
205
280
 
206
281
  // Should NOT enqueue a second embed job (only 1 from initial insert)
207
282
  const jobs = db.select().from(memoryJobs).all();
208
283
  expect(jobs).toHaveLength(1);
209
284
  });
210
285
 
211
- test("updates statement when description changes", () => {
212
- const entry = makeSkill({ description: "Original description" });
213
- upsertSkillCapabilityMemory("test-skill", entry);
286
+ test("updates content when description changes", () => {
287
+ const input = fromSkillSummary(
288
+ makeSkillSummary({ description: "Original description" }),
289
+ );
290
+ upsertSkillCapabilityMemory("test-skill", input);
214
291
 
215
292
  const db = getDb();
216
- const before = db.select().from(memoryItems).all();
293
+ const before = db.select().from(memoryGraphNodes).all();
217
294
  expect(before).toHaveLength(1);
218
- expect(before[0].statement).toContain("Original description");
295
+ expect(before[0].content).toContain("Original description");
219
296
 
220
297
  // Change description
221
- const updatedEntry = makeSkill({ description: "Updated description" });
222
- upsertSkillCapabilityMemory("test-skill", updatedEntry);
298
+ const updatedInput = fromSkillSummary(
299
+ makeSkillSummary({ description: "Updated description" }),
300
+ );
301
+ upsertSkillCapabilityMemory("test-skill", updatedInput);
223
302
 
224
- const after = db.select().from(memoryItems).all();
303
+ const after = db.select().from(memoryGraphNodes).all();
225
304
  expect(after).toHaveLength(1);
226
- expect(after[0].statement).toContain("Updated description");
227
- expect(after[0].fingerprint).not.toBe(before[0].fingerprint);
305
+ expect(after[0].content).toContain("Updated description");
306
+ expect(after[0].content).not.toBe(before[0].content);
228
307
 
229
308
  // Should enqueue a second embed job
230
309
  const jobs = db.select().from(memoryJobs).all();
@@ -232,33 +311,33 @@ describe("upsertSkillCapabilityMemory", () => {
232
311
  });
233
312
 
234
313
  test("reactivates soft-deleted items", () => {
235
- const entry = makeSkill();
236
- upsertSkillCapabilityMemory("test-skill", entry);
314
+ const input = fromSkillSummary(makeSkillSummary());
315
+ upsertSkillCapabilityMemory("test-skill", input);
237
316
 
238
317
  const db = getDb();
239
318
  // Soft-delete the item
240
- db.update(memoryItems)
241
- .set({ status: "deleted" })
242
- .where(eq(memoryItems.subject, "skill:test-skill"))
319
+ db.update(memoryGraphNodes)
320
+ .set({ fidelity: "gone" })
321
+ .where(like(memoryGraphNodes.content, "skill:test-skill\n%"))
243
322
  .run();
244
323
 
245
- const deleted = db.select().from(memoryItems).all();
246
- expect(deleted[0].status).toBe("deleted");
324
+ const deleted = db.select().from(memoryGraphNodes).all();
325
+ expect(deleted[0].fidelity).toBe("gone");
247
326
 
248
327
  // Clear jobs from initial insert
249
328
  db.run("DELETE FROM memory_jobs");
250
329
 
251
330
  // Upsert again — should reactivate
252
- upsertSkillCapabilityMemory("test-skill", entry);
331
+ upsertSkillCapabilityMemory("test-skill", input);
253
332
 
254
- const reactivated = db.select().from(memoryItems).all();
333
+ const reactivated = db.select().from(memoryGraphNodes).all();
255
334
  expect(reactivated).toHaveLength(1);
256
- expect(reactivated[0].status).toBe("active");
335
+ expect(reactivated[0].fidelity).toBe("vivid");
257
336
 
258
337
  // Should enqueue embed job for reactivated item
259
338
  const jobs = db.select().from(memoryJobs).all();
260
339
  expect(jobs).toHaveLength(1);
261
- expect(jobs[0].type).toBe("embed_item");
340
+ expect(jobs[0].type).toBe("embed_graph_node");
262
341
  });
263
342
 
264
343
  test("does not throw on DB error", () => {
@@ -268,12 +347,15 @@ describe("upsertSkillCapabilityMemory", () => {
268
347
  // dropping the table it reads from. Use a fresh DB without initialization.
269
348
  // Instead, verify the try/catch by closing and reopening:
270
349
  // resetDb closes the connection; getDb lazily reconnects.
271
- // We drop the memory_items table to force an error on the next query.
350
+ // We drop the memory_graph_nodes table to force an error on the next query.
272
351
  const db = getDb();
273
- db.run("DROP TABLE IF EXISTS memory_items");
352
+ db.run("DROP TABLE IF EXISTS memory_graph_nodes");
274
353
 
275
354
  expect(() => {
276
- upsertSkillCapabilityMemory("test-skill", makeSkill());
355
+ upsertSkillCapabilityMemory(
356
+ "test-skill",
357
+ fromSkillSummary(makeSkillSummary()),
358
+ );
277
359
  }).not.toThrow();
278
360
 
279
361
  // Restore DB state for subsequent tests.
@@ -281,8 +363,9 @@ describe("upsertSkillCapabilityMemory", () => {
281
363
  // resetting the connection leaves stale migration checkpoints that skip
282
364
  // checkpoint-guarded ALTER TABLE migrations (e.g. source_type column).
283
365
  resetDb();
366
+ const dbPath = getDbPath();
284
367
  for (const ext of ["", "-wal", "-shm"]) {
285
- rmSync(join(testDir, `test.db${ext}`), { force: true });
368
+ rmSync(`${dbPath}${ext}`, { force: true });
286
369
  }
287
370
  initializeDb();
288
371
  });
@@ -294,19 +377,19 @@ describe("deleteSkillCapabilityMemory", () => {
294
377
  beforeEach(resetTables);
295
378
 
296
379
  test("soft-deletes matching item", () => {
297
- const entry = makeSkill();
298
- upsertSkillCapabilityMemory("test-skill", entry);
380
+ const input = fromSkillSummary(makeSkillSummary());
381
+ upsertSkillCapabilityMemory("test-skill", input);
299
382
 
300
383
  const db = getDb();
301
- const before = db.select().from(memoryItems).all();
384
+ const before = db.select().from(memoryGraphNodes).all();
302
385
  expect(before).toHaveLength(1);
303
- expect(before[0].status).toBe("active");
386
+ expect(before[0].fidelity).toBe("vivid");
304
387
 
305
388
  deleteSkillCapabilityMemory("test-skill");
306
389
 
307
- const after = db.select().from(memoryItems).all();
390
+ const after = db.select().from(memoryGraphNodes).all();
308
391
  expect(after).toHaveLength(1);
309
- expect(after[0].status).toBe("deleted");
392
+ expect(after[0].fidelity).toBe("gone");
310
393
  });
311
394
 
312
395
  test("is no-op for missing item", () => {
@@ -316,7 +399,7 @@ describe("deleteSkillCapabilityMemory", () => {
316
399
  }).not.toThrow();
317
400
 
318
401
  const db = getDb();
319
- const items = db.select().from(memoryItems).all();
402
+ const items = db.select().from(memoryGraphNodes).all();
320
403
  expect(items).toHaveLength(0);
321
404
  });
322
405
 
@@ -324,7 +407,7 @@ describe("deleteSkillCapabilityMemory", () => {
324
407
  // Close and reopen DB, then drop the table to force a query error
325
408
  resetDb();
326
409
  const db = getDb();
327
- db.run("DROP TABLE IF EXISTS memory_items");
410
+ db.run("DROP TABLE IF EXISTS memory_graph_nodes");
328
411
 
329
412
  expect(() => {
330
413
  deleteSkillCapabilityMemory("test-skill");
@@ -333,8 +416,9 @@ describe("deleteSkillCapabilityMemory", () => {
333
416
  // Restore DB state for subsequent tests (see upsert "does not throw" test
334
417
  // for rationale on why we delete the DB file).
335
418
  resetDb();
419
+ const dbPath = getDbPath();
336
420
  for (const ext of ["", "-wal", "-shm"]) {
337
- rmSync(join(testDir, `test.db${ext}`), { force: true });
421
+ rmSync(`${dbPath}${ext}`, { force: true });
338
422
  }
339
423
  initializeDb();
340
424
  });
@@ -346,215 +430,416 @@ describe("seedCatalogSkillMemories", () => {
346
430
  beforeEach(() => {
347
431
  resetTables();
348
432
  // Reset mocks to defaults
349
- mockResolveCatalog = async () => [];
433
+ mockLoadSkillCatalog = () => [];
350
434
  mockIsFeatureFlagEnabled = () => true;
435
+ // Default: non-empty cache so pruning is allowed
436
+ mockGetCachedCatalogSync = () => [
437
+ { id: "_sentinel", name: "_sentinel", description: "" },
438
+ ];
351
439
  });
352
440
 
353
- test("upserts capability memories for all catalog entries", async () => {
354
- const skills: CatalogSkill[] = [
355
- makeSkill({ id: "skill-a", name: "Skill A", description: "Does A" }),
356
- makeSkill({ id: "skill-b", name: "Skill B", description: "Does B" }),
357
- makeSkill({ id: "skill-c", name: "Skill C", description: "Does C" }),
441
+ test("upserts capability memories for all enabled skills", () => {
442
+ const skills: SkillSummary[] = [
443
+ makeSkillSummary({
444
+ id: "skill-a",
445
+ displayName: "Skill A",
446
+ description: "Does A",
447
+ }),
448
+ makeSkillSummary({
449
+ id: "skill-b",
450
+ displayName: "Skill B",
451
+ description: "Does B",
452
+ }),
453
+ makeSkillSummary({
454
+ id: "skill-c",
455
+ displayName: "Skill C",
456
+ description: "Does C",
457
+ }),
358
458
  ];
359
- mockResolveCatalog = async () => skills;
459
+ mockLoadSkillCatalog = () => skills;
360
460
 
361
- await seedCatalogSkillMemories();
461
+ seedCatalogSkillMemories();
362
462
 
363
463
  const db = getDb();
364
464
  const items = db
365
465
  .select()
366
- .from(memoryItems)
367
- .where(eq(memoryItems.kind, "capability"))
466
+ .from(memoryGraphNodes)
467
+ .where(eq(memoryGraphNodes.type, "procedural"))
368
468
  .all();
369
469
  expect(items).toHaveLength(3);
370
470
 
371
- const subjects = items.map((i) => i.subject).sort();
372
- expect(subjects).toEqual([
471
+ const contentPrefixes = items.map((i) => i.content.split("\n")[0]).sort();
472
+ expect(contentPrefixes).toEqual([
373
473
  "skill:skill-a",
374
474
  "skill:skill-b",
375
475
  "skill:skill-c",
376
476
  ]);
377
477
 
378
- // All should be active
478
+ // All should be vivid
379
479
  for (const item of items) {
380
- expect(item.status).toBe("active");
480
+ expect(item.fidelity).toBe("vivid");
381
481
  }
382
482
  });
383
483
 
384
- test("prunes stale capabilities for skills no longer in catalog", async () => {
484
+ test("includes bundled skills in seeded memories", () => {
485
+ const skills: SkillSummary[] = [
486
+ makeSkillSummary({
487
+ id: "managed-skill",
488
+ displayName: "Managed",
489
+ description: "A managed skill",
490
+ source: "managed",
491
+ }),
492
+ makeSkillSummary({
493
+ id: "bundled-skill",
494
+ displayName: "Bundled",
495
+ description: "A bundled skill",
496
+ source: "bundled",
497
+ bundled: true,
498
+ }),
499
+ ];
500
+ mockLoadSkillCatalog = () => skills;
501
+
502
+ seedCatalogSkillMemories();
503
+
504
+ const db = getDb();
505
+ const items = db
506
+ .select()
507
+ .from(memoryGraphNodes)
508
+ .where(eq(memoryGraphNodes.type, "procedural"))
509
+ .all();
510
+ expect(items).toHaveLength(2);
511
+
512
+ const contentPrefixes = items.map((i) => i.content.split("\n")[0]).sort();
513
+ expect(contentPrefixes).toEqual(["skill:bundled-skill", "skill:managed-skill"]);
514
+
515
+ for (const item of items) {
516
+ expect(item.fidelity).toBe("vivid");
517
+ }
518
+ });
519
+
520
+ test("excludes bundled skills filtered by allowBundled config", () => {
521
+ const skills: SkillSummary[] = [
522
+ makeSkillSummary({
523
+ id: "allowed-bundled",
524
+ displayName: "Allowed Bundled",
525
+ description: "This bundled skill is allowed",
526
+ source: "bundled",
527
+ bundled: true,
528
+ }),
529
+ makeSkillSummary({
530
+ id: "blocked-bundled",
531
+ displayName: "Blocked Bundled",
532
+ description: "This bundled skill is not in allowBundled",
533
+ source: "bundled",
534
+ bundled: true,
535
+ }),
536
+ makeSkillSummary({
537
+ id: "managed-skill",
538
+ displayName: "Managed",
539
+ description: "A managed skill",
540
+ source: "managed",
541
+ }),
542
+ ];
543
+ mockLoadSkillCatalog = () => skills;
544
+
545
+ // Override config to set allowBundled to only allow one bundled skill
546
+ const configWithAllowBundled = {
547
+ ...TEST_CONFIG,
548
+ skills: {
549
+ ...TEST_CONFIG.skills,
550
+ allowBundled: ["allowed-bundled"],
551
+ },
552
+ };
553
+ mock.module("../config/loader.js", () => ({
554
+ loadConfig: () => configWithAllowBundled,
555
+ getConfig: () => configWithAllowBundled,
556
+ loadRawConfig: () => ({}),
557
+ saveRawConfig: () => {},
558
+ invalidateConfigCache: () => {},
559
+ }));
560
+
561
+ seedCatalogSkillMemories();
562
+
563
+ const db = getDb();
564
+ const items = db
565
+ .select()
566
+ .from(memoryGraphNodes)
567
+ .where(eq(memoryGraphNodes.type, "procedural"))
568
+ .all();
569
+
570
+ // Only allowed-bundled and managed-skill should be seeded
571
+ expect(items).toHaveLength(2);
572
+ const contentPrefixes = items.map((i) => i.content.split("\n")[0]).sort();
573
+ expect(contentPrefixes).toEqual(["skill:allowed-bundled", "skill:managed-skill"]);
574
+
575
+ // Restore default config mock
576
+ mock.module("../config/loader.js", () => ({
577
+ loadConfig: () => TEST_CONFIG,
578
+ getConfig: () => TEST_CONFIG,
579
+ loadRawConfig: () => ({}),
580
+ saveRawConfig: () => {},
581
+ invalidateConfigCache: () => {},
582
+ }));
583
+ });
584
+
585
+ test("prunes stale capabilities for skills no longer enabled", () => {
385
586
  // First seed with three skills
386
- const initialSkills: CatalogSkill[] = [
387
- makeSkill({ id: "skill-a", name: "Skill A", description: "Does A" }),
388
- makeSkill({ id: "skill-b", name: "Skill B", description: "Does B" }),
389
- makeSkill({ id: "skill-c", name: "Skill C", description: "Does C" }),
587
+ const initialSkills: SkillSummary[] = [
588
+ makeSkillSummary({
589
+ id: "skill-a",
590
+ displayName: "Skill A",
591
+ description: "Does A",
592
+ }),
593
+ makeSkillSummary({
594
+ id: "skill-b",
595
+ displayName: "Skill B",
596
+ description: "Does B",
597
+ }),
598
+ makeSkillSummary({
599
+ id: "skill-c",
600
+ displayName: "Skill C",
601
+ description: "Does C",
602
+ }),
390
603
  ];
391
- mockResolveCatalog = async () => initialSkills;
392
- await seedCatalogSkillMemories();
604
+ mockLoadSkillCatalog = () => initialSkills;
605
+ seedCatalogSkillMemories();
393
606
 
394
607
  const db = getDb();
395
608
  const beforeItems = db
396
609
  .select()
397
- .from(memoryItems)
398
- .where(eq(memoryItems.kind, "capability"))
610
+ .from(memoryGraphNodes)
611
+ .where(eq(memoryGraphNodes.type, "procedural"))
399
612
  .all();
400
613
  expect(beforeItems).toHaveLength(3);
401
- expect(beforeItems.every((i) => i.status === "active")).toBe(true);
614
+ expect(beforeItems.every((i) => i.fidelity === "vivid")).toBe(true);
402
615
 
403
616
  // Now seed with only skill-a — skill-b and skill-c should be pruned
404
- mockResolveCatalog = async () => [
405
- makeSkill({ id: "skill-a", name: "Skill A", description: "Does A" }),
617
+ mockLoadSkillCatalog = () => [
618
+ makeSkillSummary({
619
+ id: "skill-a",
620
+ displayName: "Skill A",
621
+ description: "Does A",
622
+ }),
406
623
  ];
407
- await seedCatalogSkillMemories();
624
+ seedCatalogSkillMemories();
408
625
 
409
626
  const afterItems = db
410
627
  .select()
411
- .from(memoryItems)
412
- .where(eq(memoryItems.kind, "capability"))
628
+ .from(memoryGraphNodes)
629
+ .where(eq(memoryGraphNodes.type, "procedural"))
413
630
  .all();
414
631
  expect(afterItems).toHaveLength(3); // still 3 rows, but 2 are soft-deleted
415
632
 
416
- const active = afterItems.filter((i) => i.status === "active");
417
- const deleted = afterItems.filter((i) => i.status === "deleted");
633
+ const active = afterItems.filter((i) => i.fidelity === "vivid");
634
+ const deleted = afterItems.filter((i) => i.fidelity === "gone");
418
635
 
419
636
  expect(active).toHaveLength(1);
420
- expect(active[0].subject).toBe("skill:skill-a");
637
+ expect(active[0].content).toMatch(/^skill:skill-a\n/);
421
638
 
422
639
  expect(deleted).toHaveLength(2);
423
- const deletedSubjects = deleted.map((i) => i.subject).sort();
424
- expect(deletedSubjects).toEqual(["skill:skill-b", "skill:skill-c"]);
640
+ const deletedPrefixes = deleted.map((i) => i.content.split("\n")[0]).sort();
641
+ expect(deletedPrefixes).toEqual(["skill:skill-b", "skill:skill-c"]);
425
642
  });
426
643
 
427
- test("handles empty catalog without errors", async () => {
644
+ test("handles empty catalog without errors", () => {
428
645
  // Pre-populate a skill so we can verify it gets pruned
429
646
  upsertSkillCapabilityMemory(
430
647
  "existing-skill",
431
- makeSkill({ id: "existing-skill" }),
648
+ fromSkillSummary(makeSkillSummary({ id: "existing-skill" })),
432
649
  );
433
650
 
434
651
  const db = getDb();
435
- const beforeItems = db.select().from(memoryItems).all();
652
+ const beforeItems = db.select().from(memoryGraphNodes).all();
436
653
  expect(beforeItems).toHaveLength(1);
437
- expect(beforeItems[0].status).toBe("active");
654
+ expect(beforeItems[0].fidelity).toBe("vivid");
438
655
 
439
656
  // Seed with empty catalog
440
- mockResolveCatalog = async () => [];
441
- await seedCatalogSkillMemories();
657
+ mockLoadSkillCatalog = () => [];
658
+ seedCatalogSkillMemories();
442
659
 
443
660
  // The existing skill should be pruned (soft-deleted)
444
- const afterItems = db.select().from(memoryItems).all();
661
+ const afterItems = db.select().from(memoryGraphNodes).all();
662
+ expect(afterItems).toHaveLength(1);
663
+ expect(afterItems[0].fidelity).toBe("gone");
664
+ });
665
+
666
+ test("does not prune when catalog cache is empty", () => {
667
+ // Pre-populate a skill
668
+ upsertSkillCapabilityMemory(
669
+ "existing-skill",
670
+ fromSkillSummary(makeSkillSummary({ id: "existing-skill" })),
671
+ );
672
+
673
+ const db = getDb();
674
+ const beforeItems = db.select().from(memoryGraphNodes).all();
675
+ expect(beforeItems).toHaveLength(1);
676
+ expect(beforeItems[0].fidelity).toBe("vivid");
677
+
678
+ // Seed with empty catalog AND empty cache — pruning guard should skip
679
+ mockLoadSkillCatalog = () => [];
680
+ mockGetCachedCatalogSync = () => [];
681
+ seedCatalogSkillMemories();
682
+
683
+ // The existing skill should NOT be pruned because the cache is empty
684
+ const afterItems = db.select().from(memoryGraphNodes).all();
445
685
  expect(afterItems).toHaveLength(1);
446
- expect(afterItems[0].status).toBe("deleted");
686
+ expect(afterItems[0].fidelity).toBe("vivid");
447
687
  });
448
688
 
449
- test("does not throw when resolveCatalog rejects", async () => {
450
- mockResolveCatalog = async () => {
451
- throw new Error("Network failure");
689
+ test("does not prune non-skill capability memories", () => {
690
+ // Pre-insert a non-skill capability memory directly into the DB
691
+ const db = getDb();
692
+ const now = Date.now();
693
+ db.insert(memoryGraphNodes)
694
+ .values({
695
+ id: "cli-doctor-item",
696
+ type: "procedural",
697
+ content: "cli:doctor\nThe doctor command diagnoses issues.",
698
+ fidelity: "vivid",
699
+ confidence: 1.0,
700
+ significance: 0.7,
701
+ sourceType: "inferred",
702
+ scopeId: "default",
703
+ created: now,
704
+ lastAccessed: now,
705
+ lastConsolidated: now,
706
+ emotionalCharge: '{"valence":0,"intensity":0.1,"decayCurve":"linear","decayRate":0.05,"originalIntensity":0.1}',
707
+ stability: 14,
708
+ reinforcementCount: 0,
709
+ lastReinforced: now,
710
+ sourceConversations: "[]",
711
+ narrativeRole: null,
712
+ partOfStory: null,
713
+ })
714
+ .run();
715
+
716
+ // Seed with empty catalog — skill pruner runs but should skip cli:* items
717
+ mockLoadSkillCatalog = () => [];
718
+ seedCatalogSkillMemories();
719
+
720
+ const item = db
721
+ .select()
722
+ .from(memoryGraphNodes)
723
+ .where(like(memoryGraphNodes.content, "cli:doctor\n%"))
724
+ .get();
725
+ expect(item).toBeDefined();
726
+ expect(item!.fidelity).toBe("vivid");
727
+ });
728
+
729
+ test("does not throw when loadSkillCatalog throws", () => {
730
+ mockLoadSkillCatalog = () => {
731
+ throw new Error("Catalog load failure");
452
732
  };
453
733
 
454
734
  // Best-effort: should not propagate the error
455
- await expect(seedCatalogSkillMemories()).resolves.toBeUndefined();
735
+ expect(() => seedCatalogSkillMemories()).not.toThrow();
456
736
  });
457
737
 
458
- test("skips skills whose feature flag is disabled", async () => {
459
- const skills: CatalogSkill[] = [
460
- makeSkill({
738
+ test("skips skills whose feature flag is disabled", () => {
739
+ const skills: SkillSummary[] = [
740
+ makeSkillSummary({
461
741
  id: "unflagged-skill",
462
- name: "Unflagged",
742
+ displayName: "Unflagged",
463
743
  description: "No flag",
464
744
  }),
465
- makeSkill({
745
+ makeSkillSummary({
466
746
  id: "flagged-skill",
467
- name: "Flagged",
747
+ displayName: "Flagged",
468
748
  description: "Has flag",
469
- metadata: { vellum: { "feature-flag": "my_gated_feature" } },
749
+ featureFlag: "my_gated_feature",
470
750
  }),
471
751
  ];
472
- mockResolveCatalog = async () => skills;
752
+ mockLoadSkillCatalog = () => skills;
473
753
 
474
754
  // Disable the feature flag for the flagged skill
475
755
  mockIsFeatureFlagEnabled = (key: string) => key !== "my_gated_feature";
476
756
 
477
- await seedCatalogSkillMemories();
757
+ seedCatalogSkillMemories();
478
758
 
479
759
  const db = getDb();
480
760
  const items = db
481
761
  .select()
482
- .from(memoryItems)
483
- .where(eq(memoryItems.kind, "capability"))
762
+ .from(memoryGraphNodes)
763
+ .where(eq(memoryGraphNodes.type, "procedural"))
484
764
  .all();
485
765
 
486
766
  // Only the unflagged skill should have a capability row
487
767
  expect(items).toHaveLength(1);
488
- expect(items[0].subject).toBe("skill:unflagged-skill");
489
- expect(items[0].status).toBe("active");
768
+ expect(items[0].content).toMatch(/^skill:unflagged-skill\n/);
769
+ expect(items[0].fidelity).toBe("vivid");
490
770
  });
491
771
 
492
- test("prunes pre-existing capability for a skill whose flag becomes disabled", async () => {
772
+ test("prunes pre-existing capability for a skill whose flag becomes disabled", () => {
493
773
  // First seed with both skills, all flags enabled
494
- const skills: CatalogSkill[] = [
495
- makeSkill({
774
+ const skills: SkillSummary[] = [
775
+ makeSkillSummary({
496
776
  id: "unflagged-skill",
497
- name: "Unflagged",
777
+ displayName: "Unflagged",
498
778
  description: "No flag",
499
779
  }),
500
- makeSkill({
780
+ makeSkillSummary({
501
781
  id: "flagged-skill",
502
- name: "Flagged",
782
+ displayName: "Flagged",
503
783
  description: "Has flag",
504
- metadata: { vellum: { "feature-flag": "my_gated_feature" } },
784
+ featureFlag: "my_gated_feature",
505
785
  }),
506
786
  ];
507
- mockResolveCatalog = async () => skills;
787
+ mockLoadSkillCatalog = () => skills;
508
788
  mockIsFeatureFlagEnabled = () => true;
509
- await seedCatalogSkillMemories();
789
+ seedCatalogSkillMemories();
510
790
 
511
791
  const db = getDb();
512
792
  const beforeItems = db
513
793
  .select()
514
- .from(memoryItems)
515
- .where(eq(memoryItems.kind, "capability"))
794
+ .from(memoryGraphNodes)
795
+ .where(eq(memoryGraphNodes.type, "procedural"))
516
796
  .all();
517
797
  expect(beforeItems).toHaveLength(2);
518
- expect(beforeItems.every((i) => i.status === "active")).toBe(true);
798
+ expect(beforeItems.every((i) => i.fidelity === "vivid")).toBe(true);
519
799
 
520
800
  // Now disable the flag — the flagged skill should be pruned
521
801
  mockIsFeatureFlagEnabled = (key: string) => key !== "my_gated_feature";
522
- await seedCatalogSkillMemories();
802
+ seedCatalogSkillMemories();
523
803
 
524
804
  const afterItems = db
525
805
  .select()
526
- .from(memoryItems)
527
- .where(eq(memoryItems.kind, "capability"))
806
+ .from(memoryGraphNodes)
807
+ .where(eq(memoryGraphNodes.type, "procedural"))
528
808
  .all();
529
809
  expect(afterItems).toHaveLength(2); // still 2 rows, but one soft-deleted
530
810
 
531
- const active = afterItems.filter((i) => i.status === "active");
532
- const deleted = afterItems.filter((i) => i.status === "deleted");
811
+ const active = afterItems.filter((i) => i.fidelity === "vivid");
812
+ const deleted = afterItems.filter((i) => i.fidelity === "gone");
533
813
 
534
814
  expect(active).toHaveLength(1);
535
- expect(active[0].subject).toBe("skill:unflagged-skill");
815
+ expect(active[0].content).toMatch(/^skill:unflagged-skill\n/);
536
816
 
537
817
  expect(deleted).toHaveLength(1);
538
- expect(deleted[0].subject).toBe("skill:flagged-skill");
818
+ expect(deleted[0].content).toMatch(/^skill:flagged-skill\n/);
539
819
  });
540
820
 
541
- test("does not throw on DB error during pruning", async () => {
542
- mockResolveCatalog = async () => [
543
- makeSkill({ id: "skill-a", name: "Skill A", description: "Does A" }),
821
+ test("does not throw on DB error during pruning", () => {
822
+ mockLoadSkillCatalog = () => [
823
+ makeSkillSummary({
824
+ id: "skill-a",
825
+ displayName: "Skill A",
826
+ description: "Does A",
827
+ }),
544
828
  ];
545
829
 
546
- // Drop memory_items to force a DB error during the prune phase
830
+ // Drop memory_graph_nodes to force a DB error during the prune phase
547
831
  resetDb();
548
832
  const db = getDb();
549
- db.run("DROP TABLE IF EXISTS memory_items");
833
+ db.run("DROP TABLE IF EXISTS memory_graph_nodes");
550
834
 
551
- await expect(seedCatalogSkillMemories()).resolves.toBeUndefined();
835
+ expect(() => seedCatalogSkillMemories()).not.toThrow();
552
836
 
553
837
  // Restore DB state for subsequent tests (see upsert "does not throw" test
554
838
  // for rationale on why we delete the DB file).
555
839
  resetDb();
840
+ const dbPath = getDbPath();
556
841
  for (const ext of ["", "-wal", "-shm"]) {
557
- rmSync(join(testDir, `test.db${ext}`), { force: true });
842
+ rmSync(`${dbPath}${ext}`, { force: true });
558
843
  }
559
844
  initializeDb();
560
845
  });