@vellumai/assistant 0.5.16 → 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 (407) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/Dockerfile +0 -3
  3. package/knip.json +2 -1
  4. package/openapi.yaml +660 -80
  5. package/package.json +1 -1
  6. package/src/__tests__/actor-token-service.test.ts +68 -0
  7. package/src/__tests__/agent-loop.test.ts +0 -32
  8. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  9. package/src/__tests__/anthropic-provider.test.ts +57 -3
  10. package/src/__tests__/app-compiler.test.ts +120 -0
  11. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
  12. package/src/__tests__/call-conversation-messages.test.ts +2 -6
  13. package/src/__tests__/call-domain.test.ts +2 -6
  14. package/src/__tests__/call-pointer-messages.test.ts +2 -14
  15. package/src/__tests__/call-recovery.test.ts +2 -6
  16. package/src/__tests__/call-routes-http.test.ts +2 -6
  17. package/src/__tests__/call-store.test.ts +2 -6
  18. package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
  19. package/src/__tests__/canonical-guardian-store.test.ts +2 -6
  20. package/src/__tests__/channel-delivery-store.test.ts +2 -6
  21. package/src/__tests__/channel-retry-sweep.test.ts +2 -6
  22. package/src/__tests__/checker.test.ts +25 -3
  23. package/src/__tests__/clawhub.test.ts +54 -24
  24. package/src/__tests__/cli-command-risk-guard.test.ts +14 -0
  25. package/src/__tests__/cli-memory.test.ts +74 -69
  26. package/src/__tests__/config-schema.test.ts +1 -1
  27. package/src/__tests__/config-set-platform-guard.test.ts +302 -0
  28. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
  29. package/src/__tests__/contacts-tools.test.ts +31 -0
  30. package/src/__tests__/context-overflow-reducer.test.ts +86 -0
  31. package/src/__tests__/context-token-estimator.test.ts +175 -10
  32. package/src/__tests__/conversation-agent-loop-overflow.test.ts +9 -0
  33. package/src/__tests__/conversation-agent-loop.test.ts +9 -0
  34. package/src/__tests__/conversation-attachments.test.ts +2 -6
  35. package/src/__tests__/conversation-attention-store.test.ts +2 -6
  36. package/src/__tests__/conversation-clear-safety.test.ts +2 -6
  37. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
  38. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
  39. package/src/__tests__/conversation-disk-view.test.ts +2 -6
  40. package/src/__tests__/conversation-error.test.ts +33 -2
  41. package/src/__tests__/conversation-fork-crud.test.ts +2 -6
  42. package/src/__tests__/conversation-history-web-search.test.ts +5 -0
  43. package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
  44. package/src/__tests__/conversation-media-retry.test.ts +91 -0
  45. package/src/__tests__/conversation-starter-routes.test.ts +20 -11
  46. package/src/__tests__/conversation-store.test.ts +2 -6
  47. package/src/__tests__/conversation-usage.test.ts +2 -6
  48. package/src/__tests__/conversation-wipe.test.ts +11 -408
  49. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
  50. package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
  51. package/src/__tests__/credential-security-e2e.test.ts +2 -0
  52. package/src/__tests__/followup-tools.test.ts +2 -6
  53. package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
  54. package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
  55. package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
  56. package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
  57. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
  58. package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
  59. package/src/__tests__/guardian-action-store.test.ts +2 -6
  60. package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
  61. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
  62. package/src/__tests__/guardian-dispatch.test.ts +2 -6
  63. package/src/__tests__/guardian-grant-minting.test.ts +2 -14
  64. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
  65. package/src/__tests__/guardian-routing-invariants.test.ts +192 -6
  66. package/src/__tests__/guardian-routing-state.test.ts +2 -6
  67. package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
  68. package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
  69. package/src/__tests__/injection-block.test.ts +154 -0
  70. package/src/__tests__/install-meta.test.ts +506 -0
  71. package/src/__tests__/install-skill-routing.test.ts +292 -0
  72. package/src/__tests__/invite-redemption-service.test.ts +2 -6
  73. package/src/__tests__/invite-routes-http.test.ts +2 -6
  74. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +2 -14
  75. package/src/__tests__/list-messages-attachments.test.ts +2 -6
  76. package/src/__tests__/llm-context-route-provider.test.ts +2 -6
  77. package/src/__tests__/llm-request-log-turn-query.test.ts +2 -6
  78. package/src/__tests__/llm-usage-store.test.ts +2 -6
  79. package/src/__tests__/log-export-workspace.test.ts +2 -6
  80. package/src/__tests__/managed-store.test.ts +38 -11
  81. package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
  82. package/src/__tests__/memory-recall-log-store.test.ts +2 -6
  83. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
  84. package/src/__tests__/non-member-access-request.test.ts +2 -6
  85. package/src/__tests__/notification-guardian-path.test.ts +2 -6
  86. package/src/__tests__/oauth-cli.test.ts +364 -2
  87. package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
  88. package/src/__tests__/outlook-attachments.test.ts +301 -0
  89. package/src/__tests__/outlook-automation-tools.test.ts +425 -0
  90. package/src/__tests__/outlook-categories.test.ts +212 -0
  91. package/src/__tests__/outlook-client-automation.test.ts +246 -0
  92. package/src/__tests__/outlook-compose-tools.test.ts +325 -0
  93. package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
  94. package/src/__tests__/outlook-email-watcher.test.ts +322 -0
  95. package/src/__tests__/outlook-follow-up.test.ts +196 -0
  96. package/src/__tests__/outlook-messaging-provider.test.ts +498 -3
  97. package/src/__tests__/outlook-trash.test.ts +77 -0
  98. package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
  99. package/src/__tests__/platform-callback-registration.test.ts +4 -4
  100. package/src/__tests__/playbook-execution.test.ts +76 -80
  101. package/src/__tests__/playbook-tools.test.ts +5 -7
  102. package/src/__tests__/provider-error-scenarios.test.ts +21 -0
  103. package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
  104. package/src/__tests__/registry.test.ts +2 -2
  105. package/src/__tests__/require-fresh-approval.test.ts +64 -2
  106. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
  107. package/src/__tests__/runtime-events-sse.test.ts +2 -6
  108. package/src/__tests__/schedule-store.test.ts +2 -6
  109. package/src/__tests__/schedule-tools.test.ts +2 -6
  110. package/src/__tests__/scheduler-recurrence.test.ts +1 -5
  111. package/src/__tests__/scoped-approval-grants.test.ts +2 -6
  112. package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
  113. package/src/__tests__/search-skills-unified.test.ts +421 -0
  114. package/src/__tests__/secret-onetime-send.test.ts +2 -0
  115. package/src/__tests__/send-endpoint-busy.test.ts +2 -6
  116. package/src/__tests__/sequence-store.test.ts +2 -6
  117. package/src/__tests__/server-history-render.test.ts +2 -6
  118. package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
  119. package/src/__tests__/skill-feature-flags.test.ts +6 -6
  120. package/src/__tests__/skill-load-feature-flag.test.ts +11 -11
  121. package/src/__tests__/skill-memory.test.ts +140 -98
  122. package/src/__tests__/skills-uninstall.test.ts +2 -2
  123. package/src/__tests__/skills.test.ts +1 -1
  124. package/src/__tests__/slack-inbound-verification.test.ts +2 -6
  125. package/src/__tests__/task-compiler.test.ts +2 -6
  126. package/src/__tests__/task-management-tools.test.ts +2 -6
  127. package/src/__tests__/task-memory-cleanup.test.ts +173 -229
  128. package/src/__tests__/task-runner.test.ts +2 -6
  129. package/src/__tests__/task-scheduler.test.ts +2 -6
  130. package/src/__tests__/test-preload.ts +3 -0
  131. package/src/__tests__/tool-approval-handler.test.ts +2 -6
  132. package/src/__tests__/tool-grant-request-escalation.test.ts +2 -6
  133. package/src/__tests__/tool-side-effects-slack-dm.test.ts +276 -0
  134. package/src/__tests__/trust-store.test.ts +1 -1
  135. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -6
  136. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -6
  137. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -6
  138. package/src/__tests__/trusted-contact-verification.test.ts +2 -6
  139. package/src/__tests__/turn-boundary-resolution.test.ts +2 -6
  140. package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -6
  141. package/src/__tests__/usage-routes.test.ts +2 -6
  142. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  143. package/src/__tests__/voice-invite-redemption.test.ts +2 -6
  144. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -6
  145. package/src/__tests__/voice-session-bridge.test.ts +2 -6
  146. package/src/__tests__/volume-security-guard.test.ts +2 -0
  147. package/src/__tests__/workspace-lifecycle.test.ts +29 -1
  148. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +2 -6
  149. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +2 -6
  150. package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
  151. package/src/__tests__/workspace-policy.test.ts +1 -1
  152. package/src/agent/attachments.ts +7 -2
  153. package/src/agent/image-optimize.ts +165 -0
  154. package/src/agent/loop.ts +1 -15
  155. package/src/bundler/app-compiler.ts +179 -2
  156. package/src/bundler/package-resolver.ts +3 -5
  157. package/src/cli/__tests__/notifications.test.ts +1 -2
  158. package/src/cli/cli-memory.ts +67 -64
  159. package/src/cli/commands/avatar.ts +3 -3
  160. package/src/cli/commands/config.ts +26 -13
  161. package/src/cli/commands/doctor.ts +2 -2
  162. package/src/cli/commands/memory.ts +41 -55
  163. package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
  164. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
  165. package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
  166. package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
  167. package/src/cli/commands/oauth/connect.ts +11 -6
  168. package/src/cli/commands/oauth/mode.ts +7 -0
  169. package/src/cli/commands/oauth/shared.ts +39 -3
  170. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  171. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  172. package/src/cli/commands/platform/__tests__/status.test.ts +5 -5
  173. package/src/cli/commands/platform/index.ts +16 -16
  174. package/src/cli/commands/skills.ts +88 -16
  175. package/src/cli/commands/trust.ts +2 -2
  176. package/src/cli/lib/daemon-credential-client.ts +2 -3
  177. package/src/config/bundled-skills/acp/TOOLS.json +1 -1
  178. package/src/config/bundled-skills/contacts/SKILL.md +0 -1
  179. package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
  180. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
  181. package/src/config/bundled-skills/gmail/SKILL.md +2 -10
  182. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
  183. package/src/config/bundled-skills/messaging/SKILL.md +10 -18
  184. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
  185. package/src/config/bundled-skills/outlook/SKILL.md +189 -0
  186. package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
  187. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
  188. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
  189. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
  190. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
  191. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
  192. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
  193. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
  194. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
  195. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
  196. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
  197. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
  198. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
  199. package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
  200. package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
  201. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
  202. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
  203. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
  204. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
  205. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
  206. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
  207. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
  208. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
  209. package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
  210. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
  211. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
  212. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
  213. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
  214. package/src/config/bundled-skills/slack/SKILL.md +1 -7
  215. package/src/config/bundled-tool-registry.ts +56 -4
  216. package/src/config/env-registry.ts +15 -8
  217. package/src/config/feature-flag-registry.json +21 -124
  218. package/src/config/schemas/platform.ts +8 -0
  219. package/src/config/schemas/timeouts.ts +1 -1
  220. package/src/config/skills.ts +18 -7
  221. package/src/context/token-estimator.ts +25 -18
  222. package/src/context/window-manager.ts +6 -2
  223. package/src/credential-execution/process-manager.ts +3 -1
  224. package/src/daemon/context-overflow-reducer.ts +46 -2
  225. package/src/daemon/conversation-agent-loop-handlers.ts +123 -82
  226. package/src/daemon/conversation-agent-loop.ts +96 -61
  227. package/src/daemon/conversation-error.ts +31 -8
  228. package/src/daemon/conversation-lifecycle.ts +33 -0
  229. package/src/daemon/conversation-media-retry.ts +85 -7
  230. package/src/daemon/conversation-notifiers.ts +4 -1
  231. package/src/daemon/conversation-runtime-assembly.ts +5 -0
  232. package/src/daemon/conversation.ts +41 -2
  233. package/src/daemon/daemon-control.ts +8 -2
  234. package/src/daemon/handlers/shared.ts +22 -12
  235. package/src/daemon/handlers/skills.ts +416 -202
  236. package/src/daemon/lifecycle.ts +40 -1
  237. package/src/daemon/main.ts +5 -1
  238. package/src/daemon/message-types/conversations.ts +4 -1
  239. package/src/daemon/message-types/messages.ts +3 -1
  240. package/src/daemon/message-types/skills.ts +97 -36
  241. package/src/daemon/providers-setup.ts +5 -0
  242. package/src/daemon/server.ts +11 -2
  243. package/src/daemon/tool-side-effects.ts +27 -5
  244. package/src/heartbeat/heartbeat-service.ts +1 -0
  245. package/src/hooks/cli.ts +2 -2
  246. package/src/hooks/runner.ts +15 -38
  247. package/src/inbound/platform-callback-registration.ts +14 -14
  248. package/src/memory/admin.ts +11 -45
  249. package/src/memory/conversation-bootstrap.ts +2 -0
  250. package/src/memory/conversation-crud.ts +242 -348
  251. package/src/memory/conversation-group-migration.ts +157 -0
  252. package/src/memory/conversation-queries.ts +4 -2
  253. package/src/memory/db-init.ts +30 -3
  254. package/src/memory/embed.ts +73 -0
  255. package/src/memory/embedding-backend.ts +8 -14
  256. package/src/memory/embedding-runtime-manager.ts +12 -114
  257. package/src/memory/fingerprint.ts +2 -2
  258. package/src/memory/graph/bootstrap.ts +512 -0
  259. package/src/memory/graph/capability-seed.ts +297 -0
  260. package/src/memory/graph/consolidation.ts +691 -0
  261. package/src/memory/graph/conversation-graph-memory.ts +630 -0
  262. package/src/memory/graph/decay.test.ts +208 -0
  263. package/src/memory/graph/decay.ts +195 -0
  264. package/src/memory/graph/extraction-job.ts +69 -0
  265. package/src/memory/graph/extraction.test.ts +936 -0
  266. package/src/memory/graph/extraction.ts +1254 -0
  267. package/src/memory/graph/graph-search.ts +266 -0
  268. package/src/memory/graph/image-ref-utils.ts +29 -0
  269. package/src/memory/graph/injection.test.ts +513 -0
  270. package/src/memory/graph/injection.ts +439 -0
  271. package/src/memory/graph/inspect.ts +534 -0
  272. package/src/memory/graph/narrative.ts +267 -0
  273. package/src/memory/graph/pattern-scan.ts +269 -0
  274. package/src/memory/graph/retriever.ts +1008 -0
  275. package/src/memory/graph/scoring.test.ts +548 -0
  276. package/src/memory/graph/scoring.ts +232 -0
  277. package/src/memory/graph/serendipity.ts +65 -0
  278. package/src/memory/graph/store.test.ts +1050 -0
  279. package/src/memory/graph/store.ts +699 -0
  280. package/src/memory/graph/tool-handlers.ts +426 -0
  281. package/src/memory/graph/tools.ts +141 -0
  282. package/src/memory/graph/triggers.test.ts +487 -0
  283. package/src/memory/graph/triggers.ts +223 -0
  284. package/src/memory/graph/types.ts +271 -0
  285. package/src/memory/group-crud.ts +191 -0
  286. package/src/memory/indexer.ts +37 -19
  287. package/src/memory/job-handlers/cleanup.ts +0 -53
  288. package/src/memory/job-handlers/conversation-starters.ts +91 -53
  289. package/src/memory/job-handlers/embedding.ts +5 -31
  290. package/src/memory/job-handlers/index-maintenance.ts +23 -11
  291. package/src/memory/job-handlers/summarization.ts +32 -17
  292. package/src/memory/job-utils.ts +1 -1
  293. package/src/memory/jobs-store.ts +50 -70
  294. package/src/memory/jobs-worker.ts +147 -112
  295. package/src/memory/message-content.ts +1 -0
  296. package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
  297. package/src/memory/migrations/203-drop-memory-items-tables.ts +23 -0
  298. package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
  299. package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
  300. package/src/memory/migrations/index.ts +4 -0
  301. package/src/memory/migrations/registry.ts +8 -0
  302. package/src/memory/qdrant-client.ts +44 -17
  303. package/src/memory/schema/index.ts +1 -0
  304. package/src/memory/schema/memory-graph.ts +139 -0
  305. package/src/memory/search/semantic.ts +47 -91
  306. package/src/memory/task-memory-cleanup.ts +28 -50
  307. package/src/messaging/providers/outlook/adapter.ts +8 -1
  308. package/src/messaging/providers/outlook/client.ts +299 -0
  309. package/src/messaging/providers/outlook/types.ts +118 -0
  310. package/src/notifications/adapters/macos.ts +1 -0
  311. package/src/notifications/copy-composer.ts +9 -0
  312. package/src/notifications/signal.ts +16 -0
  313. package/src/oauth/seed-providers.ts +2 -1
  314. package/src/permissions/checker.ts +24 -3
  315. package/src/permissions/defaults.ts +4 -4
  316. package/src/permissions/workspace-policy.ts +1 -1
  317. package/src/playbooks/playbook-compiler.ts +19 -18
  318. package/src/playbooks/types.ts +4 -3
  319. package/src/prompts/system-prompt.ts +3 -29
  320. package/src/providers/anthropic/client.ts +47 -19
  321. package/src/providers/gemini/client.ts +1 -1
  322. package/src/providers/openai/client.ts +1 -1
  323. package/src/providers/registry.ts +1 -1
  324. package/src/providers/retry.ts +19 -3
  325. package/src/runtime/actor-trust-resolver.ts +5 -1
  326. package/src/runtime/auth/route-policy.ts +7 -0
  327. package/src/runtime/guardian-reply-router.ts +5 -1
  328. package/src/runtime/http-server.ts +23 -3
  329. package/src/runtime/middleware/auth.ts +20 -0
  330. package/src/runtime/routes/attachment-routes.test.ts +106 -0
  331. package/src/runtime/routes/attachment-routes.ts +106 -16
  332. package/src/runtime/routes/brain-graph-routes.ts +21 -22
  333. package/src/runtime/routes/btw-routes.ts +8 -0
  334. package/src/runtime/routes/conversation-management-routes.ts +2 -0
  335. package/src/runtime/routes/conversation-starter-routes.ts +2 -2
  336. package/src/runtime/routes/debug-routes.ts +1 -1
  337. package/src/runtime/routes/global-search-routes.ts +21 -19
  338. package/src/runtime/routes/group-routes.ts +207 -0
  339. package/src/runtime/routes/guardian-action-routes.ts +21 -10
  340. package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
  341. package/src/runtime/routes/inbound-message-handler.ts +19 -0
  342. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
  343. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
  344. package/src/runtime/routes/memory-item-routes.test.ts +2 -14
  345. package/src/runtime/routes/memory-item-routes.ts +341 -388
  346. package/src/runtime/routes/schedule-routes.ts +2 -0
  347. package/src/runtime/routes/skills-routes.ts +103 -37
  348. package/src/runtime/routes/work-items-routes.test.ts +2 -6
  349. package/src/schedule/scheduler.ts +8 -1
  350. package/src/security/oauth2.ts +1 -1
  351. package/src/security/secure-keys.ts +4 -8
  352. package/src/shared/provider-env-vars.ts +19 -0
  353. package/src/skills/catalog-cache.ts +5 -0
  354. package/src/skills/catalog-install.ts +15 -14
  355. package/src/skills/clawhub.ts +134 -154
  356. package/src/skills/install-meta.ts +208 -0
  357. package/src/skills/managed-store.ts +27 -16
  358. package/src/skills/skill-memory.ts +152 -77
  359. package/src/skills/skillssh-registry.ts +19 -17
  360. package/src/tasks/task-runner.ts +3 -1
  361. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
  362. package/src/tools/browser/runtime-check.ts +3 -1
  363. package/src/tools/memory/register.ts +63 -46
  364. package/src/tools/permission-checker.ts +7 -1
  365. package/src/tools/shared/filesystem/image-read.ts +22 -85
  366. package/src/tools/terminal/safe-env.ts +1 -0
  367. package/src/tools/tool-manifest.ts +3 -3
  368. package/src/util/browser.ts +25 -10
  369. package/src/util/bun-runtime.ts +172 -0
  370. package/src/watcher/providers/outlook-calendar.ts +343 -0
  371. package/src/watcher/providers/outlook.ts +198 -0
  372. package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
  373. package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
  374. package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
  375. package/src/workspace/migrations/registry.ts +6 -0
  376. package/src/__tests__/context-memory-e2e.test.ts +0 -415
  377. package/src/__tests__/journal-context.test.ts +0 -268
  378. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
  379. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
  380. package/src/__tests__/memory-query-builder.test.ts +0 -59
  381. package/src/__tests__/memory-recall-quality.test.ts +0 -1046
  382. package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
  383. package/src/__tests__/memory-regressions.test.ts +0 -3696
  384. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
  385. package/src/daemon/conversation-memory.ts +0 -207
  386. package/src/memory/conversation-starters-cadence.ts +0 -74
  387. package/src/memory/items-extractor.ts +0 -860
  388. package/src/memory/job-handlers/batch-extraction.ts +0 -753
  389. package/src/memory/job-handlers/extraction.ts +0 -40
  390. package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -355
  391. package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
  392. package/src/memory/journal-memory.ts +0 -224
  393. package/src/memory/query-builder.ts +0 -47
  394. package/src/memory/query-expansion.ts +0 -83
  395. package/src/memory/retriever.test.ts +0 -1592
  396. package/src/memory/retriever.ts +0 -1331
  397. package/src/memory/search/formatting.test.ts +0 -140
  398. package/src/memory/search/formatting.ts +0 -262
  399. package/src/memory/search/mmr.ts +0 -139
  400. package/src/memory/search/ranking.ts +0 -15
  401. package/src/memory/search/staleness.ts +0 -40
  402. package/src/memory/search/tier-classifier.ts +0 -18
  403. package/src/memory/search/types.ts +0 -121
  404. package/src/prompts/journal-context.ts +0 -154
  405. package/src/tools/memory/definitions.ts +0 -69
  406. package/src/tools/memory/handlers.test.ts +0 -562
  407. package/src/tools/memory/handlers.ts +0 -434
@@ -0,0 +1,165 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import {
4
+ existsSync,
5
+ mkdirSync,
6
+ readdirSync,
7
+ readFileSync,
8
+ statSync,
9
+ unlinkSync,
10
+ writeFileSync,
11
+ } from "node:fs";
12
+ import { tmpdir } from "node:os";
13
+ import { join } from "node:path";
14
+
15
+ import { parseImageDimensions } from "../context/image-dimensions.js";
16
+
17
+ // Anthropic's documented max dimension — images larger than this are scaled
18
+ // down server-side anyway, so pre-scaling is zero quality loss.
19
+ const MAX_DIMENSION = 1568;
20
+
21
+ // Threshold below which we skip optimization — small images don't need it.
22
+ const OPTIMIZE_THRESHOLD_BYTES = 300 * 1024; // 300 KB
23
+
24
+ const JPEG_QUALITY = 80;
25
+
26
+ // Content-addressed disk cache to avoid re-running sips on the same image.
27
+ const CACHE_MAX_ENTRIES = 500;
28
+
29
+ function getCacheDir(): string {
30
+ return join(tmpdir(), "vellum-optimized-images");
31
+ }
32
+
33
+ function readFromCache(
34
+ key: string,
35
+ ): { data: string; mediaType: string } | null {
36
+ try {
37
+ const cachePath = join(getCacheDir(), `${key}.jpg`);
38
+ if (!existsSync(cachePath)) return null;
39
+ const buf = readFileSync(cachePath) as Buffer;
40
+ return { data: buf.toString("base64"), mediaType: "image/jpeg" };
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ function writeToCache(key: string, optimizedBytes: Buffer): void {
47
+ try {
48
+ const dir = getCacheDir();
49
+ mkdirSync(dir, { recursive: true });
50
+ writeFileSync(join(dir, `${key}.jpg`), optimizedBytes);
51
+ evictIfNeeded(dir);
52
+ } catch {
53
+ // Cache write failure is non-fatal.
54
+ }
55
+ }
56
+
57
+ function evictIfNeeded(dir: string): void {
58
+ try {
59
+ const entries = readdirSync(dir)
60
+ .filter((f) => f.endsWith(".jpg"))
61
+ .map((f) => {
62
+ const full = join(dir, f);
63
+ return { path: full, mtimeMs: statSync(full).mtimeMs };
64
+ })
65
+ .sort((a, b) => a.mtimeMs - b.mtimeMs);
66
+ const excess = entries.length - CACHE_MAX_ENTRIES;
67
+ for (let i = 0; i < excess; i++) {
68
+ try {
69
+ unlinkSync(entries[i]!.path);
70
+ } catch {
71
+ /* ignore */
72
+ }
73
+ }
74
+ } catch {
75
+ /* ignore */
76
+ }
77
+ }
78
+
79
+ function runSips(inputBytes: Buffer): Buffer | null {
80
+ const srcPath = join(tmpdir(), `vellum-img-opt-${Date.now()}-src`);
81
+ const outPath = join(tmpdir(), `vellum-img-opt-${Date.now()}-out.jpg`);
82
+ try {
83
+ writeFileSync(srcPath, inputBytes);
84
+ execFileSync(
85
+ "sips",
86
+ [
87
+ "--resampleHeightWidthMax",
88
+ String(MAX_DIMENSION),
89
+ "-s",
90
+ "format",
91
+ "jpeg",
92
+ "-s",
93
+ "formatOptions",
94
+ String(JPEG_QUALITY),
95
+ srcPath,
96
+ "--out",
97
+ outPath,
98
+ ],
99
+ { stdio: "pipe", timeout: 15_000 },
100
+ );
101
+ return readFileSync(outPath) as Buffer;
102
+ } catch {
103
+ return null;
104
+ } finally {
105
+ try {
106
+ unlinkSync(srcPath);
107
+ } catch {
108
+ /* ignore */
109
+ }
110
+ try {
111
+ unlinkSync(outPath);
112
+ } catch {
113
+ /* ignore */
114
+ }
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Downscale a base64 image to fit within Anthropic's recommended dimensions
120
+ * (1568px max side). Returns the original data unchanged if the image is
121
+ * already small enough or if optimization fails.
122
+ *
123
+ * Anthropic applies the same scaling server-side, so this is zero quality
124
+ * loss — we just do it pre-flight to keep request payloads small and avoid
125
+ * 413 "request too large" errors when many images accumulate in context.
126
+ *
127
+ * Results are cached on disk by content hash so repeated sends of the same
128
+ * image (or daemon restarts) skip the sips call entirely.
129
+ */
130
+ export function optimizeImageForTransport(
131
+ base64Data: string,
132
+ mediaType: string,
133
+ ): { data: string; mediaType: string } {
134
+ const rawBytes = Buffer.from(base64Data, "base64");
135
+
136
+ // Small images don't need optimization.
137
+ if (rawBytes.length <= OPTIMIZE_THRESHOLD_BYTES) {
138
+ return { data: base64Data, mediaType };
139
+ }
140
+
141
+ // If dimensions are already within limits, skip.
142
+ const dims = parseImageDimensions(base64Data, mediaType);
143
+ if (
144
+ dims &&
145
+ dims.width <= MAX_DIMENSION &&
146
+ dims.height <= MAX_DIMENSION
147
+ ) {
148
+ return { data: base64Data, mediaType };
149
+ }
150
+
151
+ // Content-addressed cache lookup.
152
+ const hash = createHash("sha256").update(rawBytes).digest("hex");
153
+ const cacheKey = hash.slice(0, 16);
154
+ const cached = readFromCache(cacheKey);
155
+ if (cached) return cached;
156
+
157
+ // Run sips (macOS). On other platforms this gracefully returns null.
158
+ const optimized = runSips(rawBytes);
159
+ if (!optimized) {
160
+ return { data: base64Data, mediaType };
161
+ }
162
+
163
+ writeToCache(cacheKey, optimized);
164
+ return { data: optimized.toString("base64"), mediaType: "image/jpeg" };
165
+ }
package/src/agent/loop.ts CHANGED
@@ -101,10 +101,7 @@ const DEFAULT_CONFIG: AgentLoopConfig = {
101
101
  minTurnIntervalMs: 150,
102
102
  };
103
103
 
104
- const PROGRESS_CHECK_INTERVAL = 5;
105
104
  const MAX_CONSECUTIVE_ERROR_NUDGES = 3;
106
- const PROGRESS_CHECK_REMINDER =
107
- "You have been using tools for several turns. Check whether you are making meaningful progress toward the user's goal. If you are stuck in a loop or not making progress, summarize what you have tried and ask the user for guidance instead of continuing.";
108
105
 
109
106
  export interface ResolvedSystemPrompt {
110
107
  systemPrompt: string;
@@ -573,21 +570,11 @@ export class AgentLoop {
573
570
  break;
574
571
  }
575
572
 
576
- // Track tool-use turns and inject progress reminder every N turns
577
573
  toolUseTurns++;
578
- const isProgressCheckTurn =
579
- toolUseTurns % PROGRESS_CHECK_INTERVAL === 0;
580
- if (isProgressCheckTurn) {
581
- resultBlocks.push({
582
- type: "text",
583
- text: `<system_notice>${PROGRESS_CHECK_REMINDER}</system_notice>`,
584
- });
585
- }
586
574
 
587
575
  // When any tool returned an error, nudge the LLM to retry with
588
576
  // corrected parameters instead of ending its turn. Skip the nudge
589
- // when the progress check fires (to avoid contradictory instructions)
590
- // and after MAX_CONSECUTIVE_ERROR_NUDGES consecutive error turns
577
+ // after MAX_CONSECUTIVE_ERROR_NUDGES consecutive error turns
591
578
  // (the error is likely unrecoverable at that point).
592
579
  const hasToolError = toolResults.some(({ result }) => result.isError);
593
580
  if (hasToolError) {
@@ -597,7 +584,6 @@ export class AgentLoop {
597
584
  }
598
585
  if (
599
586
  hasToolError &&
600
- !isProgressCheckTurn &&
601
587
  consecutiveErrorTurns <= MAX_CONSECUTIVE_ERROR_NUDGES
602
588
  ) {
603
589
  resultBlocks.push({
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { existsSync, rmSync } from "node:fs";
13
13
  import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
14
- import { dirname, join } from "node:path";
14
+ import { dirname, join, resolve } from "node:path";
15
15
 
16
16
  import { getLogger } from "../util/logger.js";
17
17
  import { ensureCompilerTools } from "./compiler-tools.js";
@@ -79,6 +79,161 @@ function parseEsbuildStderr(stderr: string): {
79
79
  return { errors, warnings };
80
80
  }
81
81
 
82
+ /**
83
+ * Decode JS string escape sequences (\xHH, \uHHHH, \u{H+}, standard
84
+ * escapes) so that obfuscated specifiers like `"\x2e\x2e/etc/passwd"` are
85
+ * normalised before the path-containment check.
86
+ */
87
+ function decodeJsEscapes(raw: string): string {
88
+ return raw.replace(
89
+ /\\(?:x([0-9a-fA-F]{2})|u\{([0-9a-fA-F]+)\}|u([0-9a-fA-F]{4})|([nrtbfv0'"\\]))/g,
90
+ (_, hex2, codePoint, hex4, std) => {
91
+ if (hex2) return String.fromCharCode(parseInt(hex2, 16));
92
+ if (codePoint) return String.fromCodePoint(parseInt(codePoint, 16));
93
+ if (hex4) return String.fromCharCode(parseInt(hex4, 16));
94
+ const map: Record<string, string> = {
95
+ n: "\n",
96
+ r: "\r",
97
+ t: "\t",
98
+ b: "\b",
99
+ f: "\f",
100
+ v: "\v",
101
+ "0": "\0",
102
+ "'": "'",
103
+ '"': '"',
104
+ "\\": "\\",
105
+ };
106
+ return map[std] ?? std;
107
+ },
108
+ );
109
+ }
110
+
111
+ /**
112
+ * Strip JS block comments and line comments from source, replacing them
113
+ * with same-length whitespace so character offsets (used for line-number
114
+ * calculation) are preserved. String literals are left intact.
115
+ */
116
+ function stripJsComments(source: string): string {
117
+ let out = "";
118
+ for (let i = 0; i < source.length; ) {
119
+ const c = source[i];
120
+ const next = source[i + 1];
121
+
122
+ // String literal — pass through unchanged
123
+ if (c === '"' || c === "'" || c === "`") {
124
+ const q = c;
125
+ out += source[i++];
126
+ while (i < source.length && source[i] !== q) {
127
+ if (source[i] === "\\") {
128
+ out += source[i++]; // backslash
129
+ }
130
+ if (i < source.length) out += source[i++]; // char or escaped char
131
+ }
132
+ if (i < source.length) out += source[i++]; // closing quote
133
+ continue;
134
+ }
135
+
136
+ // Block comment → replace with spaces (keep newlines for line counting)
137
+ if (c === "/" && next === "*") {
138
+ out += " "; // replace /*
139
+ i += 2;
140
+ while (
141
+ i < source.length &&
142
+ !(source[i] === "*" && source[i + 1] === "/")
143
+ ) {
144
+ out += source[i] === "\n" ? "\n" : " ";
145
+ i++;
146
+ }
147
+ if (i < source.length) {
148
+ out += " "; // replace */
149
+ i += 2;
150
+ }
151
+ continue;
152
+ }
153
+
154
+ // Line comment → replace with spaces
155
+ if (c === "/" && next === "/") {
156
+ out += " "; // replace //
157
+ i += 2;
158
+ while (i < source.length && source[i] !== "\n") {
159
+ out += " ";
160
+ i++;
161
+ }
162
+ continue;
163
+ }
164
+
165
+ out += source[i++];
166
+ }
167
+ return out;
168
+ }
169
+
170
+ /**
171
+ * Validate that all relative/absolute import paths in source files resolve
172
+ * within the app directory. This prevents crafted apps from importing
173
+ * arbitrary host files (e.g. `import data from '../../../../etc/passwd'`).
174
+ */
175
+ async function validateImportPaths(
176
+ srcDir: string,
177
+ appDir: string,
178
+ ): Promise<CompileDiagnostic[]> {
179
+ const resolvedAppDir = resolve(appDir);
180
+ const errors: CompileDiagnostic[] = [];
181
+
182
+ const files = await readdir(srcDir, { recursive: true });
183
+ for (const file of files) {
184
+ const fileName = String(file);
185
+ const isJs = /\.[jt]sx?$/.test(fileName);
186
+ const isCss = /\.css$/.test(fileName);
187
+ if (!isJs && !isCss) continue;
188
+
189
+ const filePath = join(srcDir, fileName);
190
+ const content = await readFile(filePath, "utf-8");
191
+ const fileDir = dirname(filePath);
192
+
193
+ // Strip JS comments so patterns like `from /* */ "path"` are detected
194
+ const scannable = isJs ? stripJsComments(content) : content;
195
+
196
+ const specifiers: Array<{ specifier: string; index: number }> = [];
197
+
198
+ if (isJs) {
199
+ // Match: from "x", import "x", import("x"), require("x")
200
+ const re = /(?:from|import|require)\s*\(?\s*["']([^"']+)["']/g;
201
+ for (const m of scannable.matchAll(re)) {
202
+ specifiers.push({ specifier: m[1], index: m.index! });
203
+ }
204
+ } else {
205
+ // CSS: @import "x", @import url("x"), url("x")
206
+ const re =
207
+ /(?:@import\s+(?:url\s*\(\s*)?|url\s*\(\s*)["']?([^"')\s;]+)["']?/g;
208
+ for (const m of content.matchAll(re)) {
209
+ if (m[1]) specifiers.push({ specifier: m[1], index: m.index! });
210
+ }
211
+ }
212
+
213
+ for (const { specifier, index } of specifiers) {
214
+ // Decode JS string escapes so \x2e\x2e/… is normalised to ../../…
215
+ const decoded = isJs ? decodeJsEscapes(specifier) : specifier;
216
+
217
+ // Only validate path-based imports (starting with . or /)
218
+ if (!decoded.startsWith(".") && !decoded.startsWith("/")) continue;
219
+
220
+ const resolved = resolve(fileDir, decoded);
221
+ if (
222
+ !resolved.startsWith(resolvedAppDir + "/") &&
223
+ resolved !== resolvedAppDir
224
+ ) {
225
+ const line = content.substring(0, index).split("\n").length;
226
+ errors.push({
227
+ text: `Import "${specifier}" resolves outside the app directory`,
228
+ location: { file: fileName, line, column: 0 },
229
+ });
230
+ }
231
+ }
232
+ }
233
+
234
+ return errors;
235
+ }
236
+
82
237
  /**
83
238
  * Scan source files for bare import specifiers and pre-install any
84
239
  * allowlisted packages into the shared cache so esbuild can resolve them.
@@ -86,6 +241,7 @@ function parseEsbuildStderr(stderr: string): {
86
241
  async function resolveAppImports(srcDir: string): Promise<void> {
87
242
  const importRe = /(?:import|from)\s+["']([^"'.][^"']*)["']/g;
88
243
  const seen = new Set<string>();
244
+ const failed: string[] = [];
89
245
 
90
246
  const files = await readdir(srcDir, { recursive: true });
91
247
  for (const file of files) {
@@ -97,9 +253,19 @@ async function resolveAppImports(srcDir: string): Promise<void> {
97
253
  const pkg = packageName(specifier);
98
254
  if (seen.has(pkg)) continue;
99
255
  seen.add(pkg);
100
- await resolvePackage(pkg);
256
+ const result = await resolvePackage(pkg);
257
+ if (result === null) {
258
+ failed.push(pkg);
259
+ }
101
260
  }
102
261
  }
262
+
263
+ if (failed.length > 0) {
264
+ log.warn(
265
+ { failed },
266
+ "Some imported packages could not be resolved — esbuild may fail",
267
+ );
268
+ }
103
269
  }
104
270
 
105
271
  /**
@@ -137,6 +303,17 @@ export async function compileApp(appDir: string): Promise<CompileResult> {
137
303
  };
138
304
  }
139
305
 
306
+ // Validate that path-based imports don't escape the app directory
307
+ const pathErrors = await validateImportPaths(srcDir, appDir);
308
+ if (pathErrors.length > 0) {
309
+ const durationMs = Math.round(performance.now() - start);
310
+ log.info(
311
+ { durationMs, errorCount: pathErrors.length },
312
+ "Build blocked: imports resolve outside app directory",
313
+ );
314
+ return { ok: false, errors: pathErrors, warnings: [], durationMs };
315
+ }
316
+
140
317
  // Scan source files for bare imports and JIT-install allowed packages
141
318
  await resolveAppImports(srcDir);
142
319
 
@@ -10,6 +10,7 @@ import { mkdir, stat } from "node:fs/promises";
10
10
  import { homedir } from "node:os";
11
11
  import { join } from "node:path";
12
12
 
13
+ import { ensureBun } from "../util/bun-runtime.js";
13
14
  import { getLogger } from "../util/logger.js";
14
15
 
15
16
  const log = getLogger("package-resolver");
@@ -117,14 +118,11 @@ async function installPackage(
117
118
  log.info({ pkg }, "Installing package into shared cache");
118
119
 
119
120
  try {
120
- const proc = Bun.spawn(["bun", "install", "--no-save", pkg], {
121
+ const bunPath = await ensureBun();
122
+ const proc = Bun.spawn([bunPath, "install", "--no-save", pkg], {
121
123
  cwd: cacheDir,
122
124
  stdout: "pipe",
123
125
  stderr: "pipe",
124
- env: {
125
- ...process.env,
126
- PATH: `${homedir()}/.bun/bin:${process.env.PATH}`,
127
- },
128
126
  });
129
127
 
130
128
  // Race against timeout
@@ -55,7 +55,7 @@ mock.module("../../channels/config.js", () => ({
55
55
 
56
56
  import { Command } from "commander";
57
57
 
58
- import { getDb, initializeDb, resetDb } from "../../memory/db.js";
58
+ import { getDb, initializeDb } from "../../memory/db.js";
59
59
  import { createEvent } from "../../notifications/events-store.js";
60
60
  import { registerNotificationsCommand } from "../commands/notifications.js";
61
61
 
@@ -129,7 +129,6 @@ beforeEach(() => {
129
129
  });
130
130
 
131
131
  afterAll(() => {
132
- resetDb();
133
132
  process.exitCode = 0;
134
133
  });
135
134
 
@@ -1,10 +1,9 @@
1
- import { and, eq } from "drizzle-orm";
1
+ import { and, eq, like, sql } from "drizzle-orm";
2
2
  import { v4 as uuid } from "uuid";
3
3
 
4
4
  import { getDb } from "../memory/db.js";
5
- import { computeMemoryFingerprint } from "../memory/fingerprint.js";
6
5
  import { enqueueMemoryJob } from "../memory/jobs-store.js";
7
- import { memoryItems } from "../memory/schema.js";
6
+ import { memoryGraphNodes } from "../memory/schema.js";
8
7
  import { getLogger } from "../util/logger.js";
9
8
  import { buildCliProgram } from "./program.js";
10
9
 
@@ -28,8 +27,17 @@ export function buildCliCapabilityStatement(
28
27
  return statement;
29
28
  }
30
29
 
30
+ /** Default emotional charge for capability graph nodes. */
31
+ const DEFAULT_EMOTIONAL_CHARGE = JSON.stringify({
32
+ valence: 0,
33
+ intensity: 0.1,
34
+ decayCurve: "linear",
35
+ decayRate: 0.05,
36
+ originalIntensity: 0.1,
37
+ });
38
+
31
39
  /**
32
- * Upsert a capability memory item for a CLI command.
40
+ * Upsert a capability memory graph node for a CLI command.
33
41
  * Best-effort: errors are logged but never thrown.
34
42
  */
35
43
  export function upsertCliCapabilityMemory(
@@ -38,100 +46,95 @@ export function upsertCliCapabilityMemory(
38
46
  ): void {
39
47
  try {
40
48
  const db = getDb();
41
- const subject = `cli:${commandName}`;
42
49
  const statement = buildCliCapabilityStatement(commandName, description);
43
- const kind = "capability";
50
+ const content = `cli:${commandName}\n${statement}`;
44
51
  const scopeId = "default";
45
- const confidence = 1.0;
46
- const importance = 0.7;
47
- const fingerprint = computeMemoryFingerprint(
48
- scopeId,
49
- kind,
50
- subject,
51
- statement,
52
- );
53
52
  const now = Date.now();
54
53
 
55
54
  const existing = db
56
55
  .select()
57
- .from(memoryItems)
56
+ .from(memoryGraphNodes)
58
57
  .where(
59
58
  and(
60
- eq(memoryItems.kind, kind),
61
- eq(memoryItems.subject, subject),
62
- eq(memoryItems.scopeId, scopeId),
59
+ eq(memoryGraphNodes.type, "procedural"),
60
+ like(memoryGraphNodes.content, `cli:${commandName}\n%`),
61
+ eq(memoryGraphNodes.scopeId, scopeId),
63
62
  ),
64
63
  )
65
64
  .get();
66
65
 
67
66
  if (existing) {
68
67
  if (
69
- existing.status === "active" &&
70
- existing.fingerprint === fingerprint
68
+ existing.content === content &&
69
+ existing.fidelity !== "gone"
71
70
  ) {
72
- // Same content — just touch lastSeenAt
73
- db.update(memoryItems)
74
- .set({ lastSeenAt: now })
75
- .where(eq(memoryItems.id, existing.id))
71
+ // Same content — just touch lastAccessed
72
+ db.update(memoryGraphNodes)
73
+ .set({ lastAccessed: now })
74
+ .where(eq(memoryGraphNodes.id, existing.id))
76
75
  .run();
77
76
  return;
78
77
  }
79
78
 
80
- if (existing.status === "active") {
81
- // Content changed — update statement and fingerprint
82
- db.update(memoryItems)
79
+ if (existing.fidelity !== "gone") {
80
+ // Content changed — update content
81
+ db.update(memoryGraphNodes)
83
82
  .set({
84
- statement,
85
- fingerprint,
86
- lastSeenAt: now,
83
+ content,
84
+ lastAccessed: now,
87
85
  })
88
- .where(eq(memoryItems.id, existing.id))
86
+ .where(eq(memoryGraphNodes.id, existing.id))
89
87
  .run();
90
- enqueueMemoryJob("embed_item", { itemId: existing.id });
88
+ enqueueMemoryJob("embed_graph_node", { nodeId: existing.id });
91
89
  return;
92
90
  }
93
91
 
94
- // status === "deleted" or other — reactivate
95
- db.update(memoryItems)
92
+ // fidelity === "gone" — reactivate
93
+ db.update(memoryGraphNodes)
96
94
  .set({
97
- status: "active",
98
- statement,
99
- fingerprint,
100
- lastSeenAt: now,
101
- firstSeenAt: now,
95
+ fidelity: "vivid",
96
+ content,
97
+ created: now,
98
+ lastAccessed: now,
102
99
  })
103
- .where(eq(memoryItems.id, existing.id))
100
+ .where(eq(memoryGraphNodes.id, existing.id))
104
101
  .run();
105
- enqueueMemoryJob("embed_item", { itemId: existing.id });
102
+ enqueueMemoryJob("embed_graph_node", { nodeId: existing.id });
106
103
  return;
107
104
  }
108
105
 
109
- // No existing — insert new row
106
+ // No existing — insert new graph node
110
107
  const id = uuid();
111
- db.insert(memoryItems)
108
+ db.insert(memoryGraphNodes)
112
109
  .values({
113
110
  id,
114
- kind,
115
- subject,
116
- statement,
117
- status: "active",
118
- confidence,
119
- importance,
120
- fingerprint,
121
- sourceType: "extraction",
111
+ content,
112
+ type: "procedural",
113
+ created: now,
114
+ lastAccessed: now,
115
+ lastConsolidated: now,
116
+ emotionalCharge: DEFAULT_EMOTIONAL_CHARGE,
117
+ fidelity: "vivid",
118
+ confidence: 1.0,
119
+ significance: 0.7,
120
+ stability: 14,
121
+ reinforcementCount: 0,
122
+ lastReinforced: now,
123
+ sourceConversations: JSON.stringify([]),
124
+ sourceType: "inferred",
125
+ narrativeRole: null,
126
+ partOfStory: null,
122
127
  scopeId,
123
- firstSeenAt: now,
124
- lastSeenAt: now,
125
128
  })
126
129
  .run();
127
- enqueueMemoryJob("embed_item", { itemId: id });
130
+ enqueueMemoryJob("embed_graph_node", { nodeId: id });
128
131
  } catch (err) {
129
132
  log.warn({ err, commandName }, "Failed to upsert CLI capability memory");
130
133
  }
131
134
  }
132
135
 
133
136
  /**
134
- * Seed capability memory items for all CLI commands.
137
+ * Seed capability memory graph nodes for all CLI commands.
135
138
  * Prunes stale entries whose commands are no longer registered.
136
139
  * Best-effort: errors are logged but never thrown.
137
140
  */
@@ -149,24 +152,24 @@ export function seedCliCommandMemories(): void {
149
152
  const db = getDb();
150
153
  const allCapabilities = db
151
154
  .select()
152
- .from(memoryItems)
155
+ .from(memoryGraphNodes)
153
156
  .where(
154
157
  and(
155
- eq(memoryItems.kind, "capability"),
156
- eq(memoryItems.scopeId, "default"),
157
- eq(memoryItems.status, "active"),
158
+ eq(memoryGraphNodes.type, "procedural"),
159
+ eq(memoryGraphNodes.scopeId, "default"),
160
+ sql`${memoryGraphNodes.fidelity} != 'gone'`,
158
161
  ),
159
162
  )
160
163
  .all();
161
164
 
162
165
  const now = Date.now();
163
166
  for (const item of allCapabilities) {
164
- if (!item.subject.startsWith("cli:")) continue;
165
- const itemCommandName = item.subject.replace("cli:", "");
167
+ if (!item.content.startsWith("cli:")) continue;
168
+ const itemCommandName = item.content.split("\n")[0].replace("cli:", "");
166
169
  if (!commandNames.has(itemCommandName)) {
167
- db.update(memoryItems)
168
- .set({ status: "deleted", lastSeenAt: now })
169
- .where(eq(memoryItems.id, item.id))
170
+ db.update(memoryGraphNodes)
171
+ .set({ fidelity: "gone", lastAccessed: now })
172
+ .where(eq(memoryGraphNodes.id, item.id))
170
173
  .run();
171
174
  }
172
175
  }