@vellumai/assistant 0.4.56 → 0.5.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 (457) hide show
  1. package/ARCHITECTURE.md +10 -10
  2. package/Dockerfile +3 -0
  3. package/README.md +11 -11
  4. package/docs/architecture/integrations.md +2 -2
  5. package/docs/architecture/memory.md +3 -4
  6. package/docs/credential-execution-service.md +13 -20
  7. package/node_modules/@vellumai/ces-contracts/src/error.ts +5 -4
  8. package/package.json +1 -1
  9. package/src/__tests__/actor-token-service.test.ts +7 -7
  10. package/src/__tests__/anthropic-provider.test.ts +172 -0
  11. package/src/__tests__/app-builder-tool-scripts.test.ts +15 -1
  12. package/src/__tests__/approval-cascade.test.ts +2 -2
  13. package/src/__tests__/approval-routes-http.test.ts +3 -4
  14. package/src/__tests__/asset-materialize-tool.test.ts +5 -5
  15. package/src/__tests__/asset-search-tool.test.ts +1 -1
  16. package/src/__tests__/assistant-attachments.test.ts +5 -5
  17. package/src/__tests__/assistant-events-sse-hardening.test.ts +1 -1
  18. package/src/__tests__/assistant-feature-flags-integration.test.ts +50 -38
  19. package/src/__tests__/attachments-store.test.ts +2 -2
  20. package/src/__tests__/avatar-e2e.test.ts +5 -3
  21. package/src/__tests__/browser-skill-endstate.test.ts +0 -1
  22. package/src/__tests__/call-routes-http.test.ts +2 -2
  23. package/src/__tests__/callback-handoff-copy.test.ts +1 -1
  24. package/src/__tests__/cancel-resolves-conversation-key.test.ts +158 -0
  25. package/src/__tests__/channel-readiness-routes.test.ts +0 -1
  26. package/src/__tests__/channel-readiness-service.test.ts +0 -1
  27. package/src/__tests__/checker.test.ts +31 -32
  28. package/src/__tests__/chrome-cdp.test.ts +47 -18
  29. package/src/__tests__/claude-code-skill-regression.test.ts +2 -2
  30. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  31. package/src/__tests__/config-schema.test.ts +9 -18
  32. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +1 -1
  33. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -4
  34. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -2
  35. package/src/__tests__/conversation-agent-loop.test.ts +11 -4
  36. package/src/__tests__/conversation-attachments.test.ts +1 -1
  37. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -2
  38. package/src/__tests__/conversation-error.test.ts +33 -0
  39. package/src/__tests__/conversation-init.benchmark.test.ts +0 -1
  40. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  41. package/src/__tests__/conversation-pairing.test.ts +1 -1
  42. package/src/__tests__/conversation-pre-run-repair.test.ts +4 -4
  43. package/src/__tests__/conversation-provider-retry-repair.test.ts +4 -4
  44. package/src/__tests__/conversation-queue.test.ts +23 -14
  45. package/src/__tests__/conversation-routes-slash-commands.test.ts +3 -3
  46. package/src/__tests__/conversation-runtime-assembly.test.ts +204 -185
  47. package/src/__tests__/conversation-seed-composer.test.ts +1 -1
  48. package/src/__tests__/conversation-slash-queue.test.ts +4 -4
  49. package/src/__tests__/conversation-slash-unknown.test.ts +4 -4
  50. package/src/__tests__/conversation-starter-routes.test.ts +291 -0
  51. package/src/__tests__/conversation-wipe.test.ts +438 -0
  52. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -3
  53. package/src/__tests__/conversation-workspace-injection.test.ts +4 -5
  54. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -5
  55. package/src/__tests__/credential-security-e2e.test.ts +20 -0
  56. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  57. package/src/__tests__/credential-vault-unit.test.ts +227 -0
  58. package/src/__tests__/credentials-cli.test.ts +3 -0
  59. package/src/__tests__/date-context.test.ts +59 -377
  60. package/src/__tests__/drop-capability-card-state-migration.test.ts +169 -0
  61. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +11 -45
  62. package/src/__tests__/emit-signal-routing-intent.test.ts +3 -3
  63. package/src/__tests__/encrypted-store.test.ts +249 -15
  64. package/src/__tests__/ephemeral-permissions.test.ts +4 -5
  65. package/src/__tests__/event-bus.test.ts +3 -3
  66. package/src/__tests__/file-read-tool.test.ts +40 -0
  67. package/src/__tests__/gateway-only-enforcement.test.ts +2 -2
  68. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  69. package/src/__tests__/gemini-image-service.test.ts +4 -4
  70. package/src/__tests__/gemini-provider.test.ts +6 -9
  71. package/src/__tests__/guardian-binding-drift-heal.test.ts +128 -0
  72. package/src/__tests__/guardian-dispatch.test.ts +0 -1
  73. package/src/__tests__/host-file-read-tool.test.ts +87 -0
  74. package/src/__tests__/host-shell-tool.test.ts +6 -6
  75. package/src/__tests__/http-user-message-parity.test.ts +2 -2
  76. package/src/__tests__/identity-intro-cache.test.ts +209 -0
  77. package/src/__tests__/intent-routing.test.ts +51 -99
  78. package/src/__tests__/invite-routes-http.test.ts +5 -0
  79. package/src/__tests__/list-messages-attachments.test.ts +1 -1
  80. package/src/__tests__/managed-proxy-context.test.ts +2 -5
  81. package/src/__tests__/managed-skill-lifecycle.test.ts +8 -8
  82. package/src/__tests__/media-generate-image.test.ts +32 -15
  83. package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
  84. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +1 -1
  85. package/src/__tests__/memory-lifecycle-e2e.test.ts +24 -18
  86. package/src/__tests__/memory-recall-quality.test.ts +4 -3
  87. package/src/__tests__/memory-regressions.test.ts +86 -90
  88. package/src/__tests__/migration-cross-version-compatibility.test.ts +32 -32
  89. package/src/__tests__/migration-export-http.test.ts +26 -27
  90. package/src/__tests__/migration-import-commit-http.test.ts +165 -37
  91. package/src/__tests__/migration-import-preflight-http.test.ts +81 -20
  92. package/src/__tests__/migration-validate-http.test.ts +16 -16
  93. package/src/__tests__/model-intents.test.ts +2 -2
  94. package/src/__tests__/no-domain-routing-in-prompt-guard.test.ts +1 -1
  95. package/src/__tests__/non-member-access-request.test.ts +3 -3
  96. package/src/__tests__/notification-broadcaster.test.ts +1 -1
  97. package/src/__tests__/notification-decision-fallback.test.ts +2 -2
  98. package/src/__tests__/notification-decision-identity.test.ts +8 -9
  99. package/src/__tests__/notification-decision-strategy.test.ts +1 -1
  100. package/src/__tests__/notification-deep-link.test.ts +1 -1
  101. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  102. package/src/__tests__/notification-schedule-dedup.test.ts +7 -7
  103. package/src/__tests__/oauth-store.test.ts +1 -3
  104. package/src/__tests__/oauth2-gateway-transport.test.ts +6 -1
  105. package/src/__tests__/onboarding-template-contract.test.ts +23 -59
  106. package/src/__tests__/provider-error-scenarios.test.ts +154 -0
  107. package/src/__tests__/provider-fail-open-selection.test.ts +2 -2
  108. package/src/__tests__/provider-managed-proxy-integration.test.ts +8 -9
  109. package/src/__tests__/provider-registry-ollama.test.ts +5 -2
  110. package/src/__tests__/qdrant-manager.test.ts +7 -7
  111. package/src/__tests__/ratelimit.test.ts +0 -74
  112. package/src/__tests__/recording-handler.test.ts +0 -1
  113. package/src/__tests__/require-fresh-approval.test.ts +1 -1
  114. package/src/__tests__/runtime-attachment-metadata.test.ts +1 -1
  115. package/src/__tests__/runtime-events-sse-parity.test.ts +1 -1
  116. package/src/__tests__/runtime-events-sse.test.ts +1 -1
  117. package/src/__tests__/scheduler-recurrence.test.ts +46 -2
  118. package/src/__tests__/schema-transforms.test.ts +114 -54
  119. package/src/__tests__/secret-onetime-send.test.ts +20 -0
  120. package/src/__tests__/secret-routes-managed-proxy.test.ts +5 -2
  121. package/src/__tests__/secret-scanner-executor.test.ts +1 -2
  122. package/src/__tests__/send-endpoint-busy.test.ts +63 -4
  123. package/src/__tests__/send-notification-tool.test.ts +2 -2
  124. package/src/__tests__/shell-credential-ref.test.ts +0 -1
  125. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -2
  126. package/src/__tests__/skill-memory.test.ts +549 -0
  127. package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -2
  128. package/src/__tests__/slack-app-setup-skill-regression.test.ts +37 -0
  129. package/src/__tests__/slack-channel-config.test.ts +109 -94
  130. package/src/__tests__/swarm-conversation-integration.test.ts +2 -2
  131. package/src/__tests__/swarm-recursion.test.ts +2 -2
  132. package/src/__tests__/swarm-tool.test.ts +2 -2
  133. package/src/__tests__/system-prompt.test.ts +19 -66
  134. package/src/__tests__/telegram-config.test.ts +121 -0
  135. package/src/__tests__/terminal-tools.test.ts +1 -1
  136. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -2
  137. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  138. package/src/__tests__/tool-executor-shell-integration.test.ts +1 -1
  139. package/src/__tests__/tool-executor.test.ts +1 -1
  140. package/src/__tests__/trace-emitter.test.ts +8 -1
  141. package/src/__tests__/trust-store.test.ts +7 -8
  142. package/src/__tests__/twilio-routes.test.ts +1 -18
  143. package/src/__tests__/user-reference.test.ts +82 -2
  144. package/src/__tests__/vbundle-pax-and-symlink.test.ts +196 -0
  145. package/src/__tests__/verification-control-plane-policy.test.ts +1 -1
  146. package/src/approvals/guardian-request-resolvers.ts +3 -3
  147. package/src/avatar/ascii-renderer.ts +2 -2
  148. package/src/avatar/png-renderer.ts +2 -2
  149. package/src/avatar/resvg-lazy.ts +21 -0
  150. package/src/calls/guardian-dispatch.ts +1 -1
  151. package/src/calls/relay-access-wait.ts +2 -2
  152. package/src/calls/twilio-rest.ts +0 -248
  153. package/src/cli/AGENTS.md +5 -8
  154. package/src/cli/__tests__/notifications.test.ts +5 -5
  155. package/src/cli/commands/avatar.ts +64 -2
  156. package/src/cli/commands/conversations.ts +131 -1
  157. package/src/cli/commands/credentials.ts +2 -0
  158. package/src/cli/commands/notifications.ts +3 -3
  159. package/src/cli.ts +10 -0
  160. package/src/config/bundled-skills/acp/SKILL.md +5 -5
  161. package/src/config/bundled-skills/acp/TOOLS.json +6 -6
  162. package/src/config/bundled-skills/app-builder/SKILL.md +42 -42
  163. package/src/config/bundled-skills/app-builder/TOOLS.json +10 -10
  164. package/src/config/bundled-skills/browser/SKILL.md +15 -15
  165. package/src/config/bundled-skills/browser/TOOLS.json +14 -14
  166. package/src/config/bundled-skills/chatgpt-import/SKILL.md +2 -2
  167. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +1 -1
  168. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
  169. package/src/config/bundled-skills/claude-code/SKILL.md +5 -5
  170. package/src/config/bundled-skills/computer-use/SKILL.md +2 -2
  171. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -15
  172. package/src/config/bundled-skills/contacts/SKILL.md +3 -3
  173. package/src/config/bundled-skills/contacts/TOOLS.json +4 -4
  174. package/src/config/bundled-skills/document/SKILL.md +4 -4
  175. package/src/config/bundled-skills/document/TOOLS.json +2 -2
  176. package/src/config/bundled-skills/followups/TOOLS.json +3 -3
  177. package/src/config/bundled-skills/gmail/SKILL.md +32 -32
  178. package/src/config/bundled-skills/gmail/TOOLS.json +16 -16
  179. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +1 -1
  180. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
  181. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -1
  182. package/src/config/bundled-skills/google-calendar/TOOLS.json +5 -5
  183. package/src/config/bundled-skills/google-calendar/types.ts +1 -1
  184. package/src/config/bundled-skills/heartbeat/SKILL.md +43 -0
  185. package/src/config/bundled-skills/image-studio/SKILL.md +3 -3
  186. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -3
  187. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +16 -12
  188. package/src/config/bundled-skills/media-processing/SKILL.md +40 -40
  189. package/src/config/bundled-skills/media-processing/TOOLS.json +8 -8
  190. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +2 -2
  191. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +1 -1
  192. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +5 -5
  193. package/src/config/bundled-skills/media-processing/services/gemini-video.ts +2 -2
  194. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -2
  195. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -2
  196. package/src/config/bundled-skills/media-processing/services/reduce.ts +3 -3
  197. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
  198. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +1 -1
  199. package/src/config/bundled-skills/messaging/SKILL.md +29 -25
  200. package/src/config/bundled-skills/messaging/TOOLS.json +11 -11
  201. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
  202. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  203. package/src/config/bundled-skills/notifications/SKILL.md +3 -3
  204. package/src/config/bundled-skills/notifications/TOOLS.json +2 -2
  205. package/src/config/bundled-skills/notifications/tools/send-notification.ts +3 -3
  206. package/src/config/bundled-skills/orchestration/SKILL.md +1 -1
  207. package/src/config/bundled-skills/orchestration/TOOLS.json +1 -1
  208. package/src/config/bundled-skills/phone-calls/SKILL.md +18 -14
  209. package/src/config/bundled-skills/phone-calls/TOOLS.json +3 -3
  210. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +2 -2
  211. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +2 -2
  212. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +1 -1
  213. package/src/config/bundled-skills/playbooks/TOOLS.json +4 -4
  214. package/src/config/bundled-skills/schedule/SKILL.md +26 -26
  215. package/src/config/bundled-skills/schedule/TOOLS.json +5 -5
  216. package/src/config/bundled-skills/screen-watch/SKILL.md +3 -3
  217. package/src/config/bundled-skills/screen-watch/TOOLS.json +1 -1
  218. package/src/config/bundled-skills/sequences/SKILL.md +2 -2
  219. package/src/config/bundled-skills/sequences/TOOLS.json +10 -10
  220. package/src/config/bundled-skills/sequences/tools/sequence-analytics.ts +2 -2
  221. package/src/config/bundled-skills/sequences/tools/sequence-enroll.ts +2 -2
  222. package/src/config/bundled-skills/sequences/tools/sequence-enrollment-list.ts +1 -1
  223. package/src/config/bundled-skills/sequences/tools/sequence-get.ts +1 -1
  224. package/src/config/bundled-skills/sequences/tools/sequence-import.ts +3 -3
  225. package/src/config/bundled-skills/sequences/tools/sequence-list.ts +1 -1
  226. package/src/config/bundled-skills/sequences/tools/sequence-update.ts +1 -1
  227. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  228. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -1
  229. package/src/config/bundled-skills/skill-management/TOOLS.json +5 -5
  230. package/src/config/bundled-skills/skills-catalog/SKILL.md +84 -0
  231. package/src/config/bundled-skills/slack/SKILL.md +2 -2
  232. package/src/config/bundled-skills/slack/TOOLS.json +8 -8
  233. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +3 -3
  234. package/src/config/bundled-skills/subagent/TOOLS.json +5 -5
  235. package/src/config/bundled-skills/tasks/SKILL.md +1 -1
  236. package/src/config/bundled-skills/tasks/TOOLS.json +9 -9
  237. package/src/config/bundled-skills/transcribe/SKILL.md +5 -5
  238. package/src/config/bundled-skills/transcribe/TOOLS.json +1 -1
  239. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +10 -10
  240. package/src/config/bundled-skills/watcher/SKILL.md +4 -4
  241. package/src/config/bundled-skills/watcher/TOOLS.json +5 -5
  242. package/src/config/feature-flag-registry.json +33 -17
  243. package/src/config/schemas/sandbox.ts +1 -1
  244. package/src/config/schemas/services.ts +13 -3
  245. package/src/config/schemas/timeouts.ts +0 -10
  246. package/src/contacts/contact-store.ts +63 -0
  247. package/src/contacts/contacts-write.ts +1 -1
  248. package/src/daemon/assistant-attachments.ts +2 -2
  249. package/src/daemon/conversation-agent-loop-handlers.ts +2 -2
  250. package/src/daemon/conversation-agent-loop.ts +7 -30
  251. package/src/daemon/conversation-error.ts +24 -0
  252. package/src/daemon/conversation-memory.ts +8 -7
  253. package/src/daemon/conversation-runtime-assembly.ts +141 -275
  254. package/src/daemon/conversation-slash.ts +7 -26
  255. package/src/daemon/conversation-surfaces.ts +14 -0
  256. package/src/daemon/conversation-tool-setup.ts +9 -8
  257. package/src/daemon/conversation.ts +2 -0
  258. package/src/daemon/daemon-control.ts +1 -1
  259. package/src/daemon/date-context.ts +10 -83
  260. package/src/daemon/handlers/config-channels.ts +12 -2
  261. package/src/daemon/handlers/config-slack-channel.ts +7 -1
  262. package/src/daemon/handlers/config-telegram.ts +6 -1
  263. package/src/daemon/handlers/conversations.ts +2 -2
  264. package/src/daemon/handlers/skills.ts +4 -0
  265. package/src/daemon/lifecycle.ts +28 -4
  266. package/src/daemon/providers-setup.ts +1 -1
  267. package/src/daemon/server.ts +1 -5
  268. package/src/daemon/shutdown-handlers.ts +9 -3
  269. package/src/daemon/tool-side-effects.ts +40 -0
  270. package/src/daemon/trace-emitter.ts +26 -2
  271. package/src/events/domain-events.ts +1 -1
  272. package/src/events/tool-permission-telemetry-listener.ts +46 -0
  273. package/src/inbound/platform-callback-registration.ts +0 -18
  274. package/src/media/app-icon-generator.ts +15 -8
  275. package/src/media/avatar-router.ts +15 -8
  276. package/src/media/gemini-image-service.ts +125 -21
  277. package/src/memory/attachments-store.ts +3 -3
  278. package/src/memory/channel-verification-sessions.ts +6 -6
  279. package/src/memory/conversation-crud.ts +196 -1
  280. package/src/memory/{thread-starters-cadence.ts → conversation-starters-cadence.ts} +9 -42
  281. package/src/memory/conversation-title-service.ts +2 -3
  282. package/src/memory/db-init.ts +25 -1
  283. package/src/memory/invite-store.ts +4 -4
  284. package/src/memory/items-extractor.ts +4 -4
  285. package/src/memory/job-handlers/{thread-starters.ts → conversation-starters.ts} +123 -38
  286. package/src/memory/jobs-store.ts +3 -2
  287. package/src/memory/jobs-worker.ts +7 -5
  288. package/src/memory/lifecycle-events-store.ts +63 -0
  289. package/src/memory/migrations/172-rename-created-by-session-id.ts +27 -0
  290. package/src/memory/migrations/173-rename-source-session-id.ts +16 -0
  291. package/src/memory/migrations/174-rename-thread-starters-table.ts +52 -0
  292. package/src/memory/migrations/175-create-lifecycle-events.ts +15 -0
  293. package/src/memory/migrations/176-drop-capability-card-state.ts +36 -0
  294. package/src/memory/migrations/177-create-trace-events-table.ts +40 -0
  295. package/src/memory/migrations/index.ts +6 -0
  296. package/src/memory/migrations/registry.ts +13 -0
  297. package/src/memory/retriever.test.ts +223 -96
  298. package/src/memory/retriever.ts +115 -138
  299. package/src/memory/schema/calls.ts +1 -1
  300. package/src/memory/schema/contacts.ts +1 -1
  301. package/src/memory/schema/infrastructure.ts +29 -0
  302. package/src/memory/schema/memory-core.ts +7 -17
  303. package/src/memory/schema/notifications.ts +1 -1
  304. package/src/memory/search/formatting.ts +23 -6
  305. package/src/memory/search/lexical.ts +2 -0
  306. package/src/memory/search/semantic.ts +2 -0
  307. package/src/memory/search/staleness.ts +5 -1
  308. package/src/memory/search/types.ts +4 -0
  309. package/src/memory/task-memory-cleanup.ts +96 -6
  310. package/src/memory/trace-event-store.ts +148 -0
  311. package/src/notifications/README.md +1 -1
  312. package/src/notifications/decision-engine.ts +45 -4
  313. package/src/notifications/emit-signal.ts +5 -4
  314. package/src/notifications/events-store.ts +4 -4
  315. package/src/notifications/signal.ts +1 -1
  316. package/src/oauth/manual-token-connection.ts +49 -25
  317. package/src/permissions/checker.ts +6 -5
  318. package/src/permissions/defaults.ts +4 -4
  319. package/src/prompts/__tests__/build-cli-reference-section.test.ts +9 -90
  320. package/src/prompts/cache-boundary.ts +8 -0
  321. package/src/prompts/system-prompt.ts +105 -634
  322. package/src/prompts/templates/BOOTSTRAP.md +172 -33
  323. package/src/prompts/templates/IDENTITY.md +8 -24
  324. package/src/prompts/templates/SOUL.md +20 -41
  325. package/src/prompts/templates/USER.md +3 -19
  326. package/src/prompts/user-reference.ts +14 -16
  327. package/src/providers/anthropic/client.ts +51 -19
  328. package/src/providers/gemini/client.ts +6 -9
  329. package/src/providers/managed-proxy/constants.ts +1 -7
  330. package/src/providers/managed-proxy/context.ts +0 -1
  331. package/src/providers/model-intents.ts +5 -5
  332. package/src/providers/openai/client.ts +10 -1
  333. package/src/providers/openrouter/client.ts +1 -0
  334. package/src/providers/ratelimit.ts +0 -35
  335. package/src/providers/registry.ts +3 -5
  336. package/src/providers/retry.ts +18 -1
  337. package/src/runtime/access-request-helper.ts +16 -2
  338. package/src/runtime/auth/route-policy.ts +7 -0
  339. package/src/runtime/channel-verification-service.ts +1 -1
  340. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  341. package/src/runtime/guardian-vellum-migration.ts +61 -1
  342. package/src/runtime/http-server.ts +8 -4
  343. package/src/runtime/migrations/vbundle-builder.ts +212 -32
  344. package/src/runtime/migrations/vbundle-import-analyzer.ts +74 -8
  345. package/src/runtime/migrations/vbundle-importer.ts +66 -1
  346. package/src/runtime/migrations/vbundle-validator.ts +17 -3
  347. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +4 -4
  348. package/src/runtime/routes/attachment-routes.ts +2 -2
  349. package/src/runtime/routes/btw-routes.ts +93 -0
  350. package/src/runtime/routes/channel-verification-routes.ts +19 -2
  351. package/src/runtime/routes/conversation-management-routes.ts +55 -1
  352. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  353. package/src/runtime/routes/conversation-routes.ts +49 -5
  354. package/src/runtime/routes/conversation-starter-routes.ts +207 -0
  355. package/src/runtime/routes/guardian-bootstrap-routes.ts +13 -9
  356. package/src/runtime/routes/identity-intro-cache.ts +105 -0
  357. package/src/runtime/routes/identity-routes.ts +51 -0
  358. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -1
  359. package/src/runtime/routes/inbound-stages/verification-intercept.ts +1 -1
  360. package/src/runtime/routes/migration-routes.ts +25 -13
  361. package/src/runtime/routes/secret-routes.ts +18 -0
  362. package/src/runtime/routes/settings-routes.ts +9 -9
  363. package/src/runtime/routes/telemetry-routes.ts +53 -0
  364. package/src/runtime/routes/trace-event-routes.ts +62 -0
  365. package/src/runtime/tool-grant-request-helper.ts +1 -1
  366. package/src/runtime/verification-outbound-actions.ts +47 -31
  367. package/src/security/encrypted-store.ts +262 -78
  368. package/src/skills/catalog-install.ts +10 -0
  369. package/src/skills/managed-store.ts +2 -0
  370. package/src/skills/skill-memory.ts +222 -0
  371. package/src/subagent/manager.ts +1 -4
  372. package/src/telemetry/types.ts +10 -1
  373. package/src/telemetry/usage-telemetry-reporter.test.ts +7 -2
  374. package/src/telemetry/usage-telemetry-reporter.ts +53 -4
  375. package/src/tools/AGENTS.md +11 -11
  376. package/src/tools/acp/spawn.ts +1 -1
  377. package/src/tools/apps/executors.ts +8 -8
  378. package/src/tools/apps/registry.ts +1 -1
  379. package/src/tools/assets/materialize.ts +6 -6
  380. package/src/tools/assets/search.ts +10 -10
  381. package/src/tools/browser/__tests__/auth-cache.test.ts +2 -2
  382. package/src/tools/browser/__tests__/auth-detector.test.ts +4 -4
  383. package/src/tools/browser/auth-detector.ts +6 -6
  384. package/src/tools/browser/browser-execution.ts +13 -13
  385. package/src/tools/browser/browser-manager.ts +3 -3
  386. package/src/tools/browser/chrome-cdp.ts +5 -5
  387. package/src/tools/browser/jit-auth.ts +2 -2
  388. package/src/tools/browser/network-recorder.test.ts +2 -2
  389. package/src/tools/browser/network-recorder.ts +3 -3
  390. package/src/tools/browser/runtime-check.ts +3 -3
  391. package/src/tools/claude-code/claude-code.ts +2 -2
  392. package/src/tools/computer-use/definitions.ts +18 -18
  393. package/src/tools/credential-execution/make-authenticated-request.ts +4 -4
  394. package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -3
  395. package/src/tools/credential-execution/run-authenticated-command.ts +4 -4
  396. package/src/tools/credentials/broker-types.ts +5 -5
  397. package/src/tools/credentials/broker.ts +15 -15
  398. package/src/tools/credentials/metadata-store.ts +2 -2
  399. package/src/tools/credentials/resolve.ts +1 -1
  400. package/src/tools/credentials/selection.ts +1 -1
  401. package/src/tools/credentials/tool-policy.ts +1 -1
  402. package/src/tools/credentials/vault.ts +115 -25
  403. package/src/tools/execution-target.ts +2 -2
  404. package/src/tools/executor.ts +7 -7
  405. package/src/tools/filesystem/edit.ts +2 -2
  406. package/src/tools/filesystem/read.ts +15 -4
  407. package/src/tools/filesystem/write.ts +1 -1
  408. package/src/tools/host-filesystem/edit.ts +2 -1
  409. package/src/tools/host-filesystem/read.ts +18 -1
  410. package/src/tools/host-filesystem/write.ts +1 -1
  411. package/src/tools/host-terminal/host-shell.ts +9 -8
  412. package/src/tools/mcp/mcp-tool-factory.ts +7 -6
  413. package/src/tools/memory/definitions.ts +6 -5
  414. package/src/tools/memory/handlers.test.ts +1 -1
  415. package/src/tools/network/__tests__/web-search.test.ts +3 -3
  416. package/src/tools/network/domain-normalize.ts +2 -2
  417. package/src/tools/network/script-proxy/session-manager.ts +10 -10
  418. package/src/tools/network/web-fetch.ts +1 -1
  419. package/src/tools/network/web-search.ts +3 -3
  420. package/src/tools/permission-checker.ts +8 -8
  421. package/src/tools/registry.ts +7 -7
  422. package/src/tools/schedule/list.ts +2 -2
  423. package/src/tools/schema-transforms.ts +31 -21
  424. package/src/tools/secret-detection-handler.ts +1 -1
  425. package/src/tools/sensitive-output-placeholders.ts +1 -1
  426. package/src/tools/shared/filesystem/edit-engine.ts +1 -1
  427. package/src/tools/shared/filesystem/file-ops-service.ts +3 -3
  428. package/src/tools/shared/filesystem/image-read.ts +25 -5
  429. package/src/tools/shared/filesystem/path-policy.ts +2 -2
  430. package/src/tools/shared/shell-output.ts +1 -1
  431. package/src/tools/side-effects.ts +1 -1
  432. package/src/tools/skills/execute.ts +1 -1
  433. package/src/tools/skills/load.ts +3 -3
  434. package/src/tools/skills/sandbox-runner.ts +3 -3
  435. package/src/tools/subagent/read.ts +1 -1
  436. package/src/tools/subagent/spawn.ts +2 -2
  437. package/src/tools/swarm/delegate.ts +3 -3
  438. package/src/tools/system/request-permission.ts +5 -4
  439. package/src/tools/terminal/backends/native.ts +4 -4
  440. package/src/tools/terminal/parser.ts +6 -6
  441. package/src/tools/terminal/sandbox-diagnostics.ts +1 -1
  442. package/src/tools/terminal/shell.ts +16 -16
  443. package/src/tools/tool-approval-handler.ts +21 -12
  444. package/src/tools/tool-manifest.ts +4 -4
  445. package/src/tools/types.ts +3 -3
  446. package/src/tools/ui-surface/definitions.ts +9 -37
  447. package/src/tools/watcher/list.ts +1 -1
  448. package/src/util/logger.ts +7 -2
  449. package/src/util/pricing.ts +4 -0
  450. package/src/util/retry.ts +29 -1
  451. package/src/workspace/migrations/007-web-search-provider-rename.ts +37 -0
  452. package/src/workspace/migrations/registry.ts +2 -0
  453. package/src/__tests__/cli-help-reference-sync.test.ts +0 -26
  454. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -190
  455. package/src/cli/reference.ts +0 -38
  456. package/src/memory/job-handlers/capability-cards.ts +0 -420
  457. package/src/runtime/routes/thread-starter-routes.ts +0 -294
@@ -16,7 +16,10 @@ mock.module("../config/env.js", () => ({ isHttpAuthDisabled: () => true }));
16
16
  import { createGuardianBinding } from "../contacts/contacts-write.js";
17
17
  import type { Conversation } from "../daemon/conversation.js";
18
18
  import type { ServerMessage } from "../daemon/message-protocol.js";
19
- import { createCanonicalGuardianRequest } from "../memory/canonical-guardian-store.js";
19
+ import {
20
+ createCanonicalGuardianRequest,
21
+ getCanonicalGuardianRequest,
22
+ } from "../memory/canonical-guardian-store.js";
20
23
  import { getOrCreateConversation } from "../memory/conversation-key-store.js";
21
24
 
22
25
  const testDir = realpathSync(
@@ -49,7 +52,7 @@ mock.module("../config/loader.js", () => ({
49
52
  model: "test",
50
53
  provider: "test",
51
54
  memory: { enabled: false },
52
- rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
55
+ rateLimit: { maxRequestsPerMinute: 0 },
53
56
  secretDetection: { enabled: false },
54
57
  contextWindow: { maxInputTokens: 200000 },
55
58
  services: {
@@ -61,9 +64,9 @@ mock.module("../config/loader.js", () => ({
61
64
  "image-generation": {
62
65
  mode: "your-own",
63
66
  provider: "gemini",
64
- model: "gemini-2.5-flash-image",
67
+ model: "gemini-3.1-flash-image-preview",
65
68
  },
66
- "web-search": { mode: "your-own", provider: "anthropic-native" },
69
+ "web-search": { mode: "your-own", provider: "inference-provider-native" },
67
70
  },
68
71
  }),
69
72
  }));
@@ -896,4 +899,60 @@ describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
896
899
 
897
900
  await stopServer();
898
901
  });
902
+
903
+ test("auto-deny resolves canonical guardian request so stale records do not cause pending_interaction_not_found", async () => {
904
+ const conversationKey = "conv-auto-deny-canonical";
905
+ const { conversationId } = getOrCreateConversation(conversationKey);
906
+ const requestId = "req-auto-deny-canonical";
907
+
908
+ // Step 1: Create a pending approval conversation with a canonical request.
909
+ const { conversation, denyAllPendingConfirmationsMock } =
910
+ makePendingApprovalConversation(requestId, false);
911
+
912
+ pendingInteractions.register(requestId, {
913
+ conversation,
914
+ conversationId,
915
+ kind: "confirmation",
916
+ });
917
+ createCanonicalGuardianRequest({
918
+ id: requestId,
919
+ kind: "tool_approval",
920
+ sourceType: "desktop",
921
+ sourceChannel: "vellum",
922
+ conversationId,
923
+ toolName: "bash",
924
+ guardianPrincipalId: "test-principal-id",
925
+ status: "pending",
926
+ requestCode: "STALE1",
927
+ expiresAt: Date.now() + 5 * 60 * 1000,
928
+ });
929
+
930
+ await startServer(() => conversation);
931
+
932
+ // Step 2: Send a non-approval message to trigger auto-deny of the
933
+ // pending confirmation. "do something else" is not an approval phrase,
934
+ // so tryConsumeCanonicalGuardianReply won't consume it, and the
935
+ // auto-deny path will fire.
936
+ const res = await fetch(messagesUrl(), {
937
+ method: "POST",
938
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
939
+ body: JSON.stringify({
940
+ conversationKey,
941
+ content: "do something else instead",
942
+ sourceChannel: "vellum",
943
+ interface: "macos",
944
+ }),
945
+ });
946
+ expect(res.status).toBe(202);
947
+ expect(denyAllPendingConfirmationsMock).toHaveBeenCalledTimes(1);
948
+
949
+ // Step 3: Verify the canonical guardian request was resolved to "denied".
950
+ // Without the fix, this would remain "pending", causing
951
+ // pending_interaction_not_found errors on subsequent "yes" messages.
952
+ const canonicalRequest = getCanonicalGuardianRequest(requestId);
953
+ expect(canonicalRequest).toBeDefined();
954
+ expect(canonicalRequest!.status).toBe("denied");
955
+
956
+ await stopServer();
957
+ });
899
958
  });
@@ -39,7 +39,7 @@ describe("send-notification tool", () => {
39
39
  expect(emitNotificationSignalMock).toHaveBeenCalledWith({
40
40
  sourceEventName: "user.send_notification",
41
41
  sourceChannel: "assistant_tool",
42
- sourceSessionId: "conv-override",
42
+ sourceContextId: "conv-override",
43
43
  attentionHints: {
44
44
  requiresAction: true,
45
45
  urgency: "high",
@@ -50,7 +50,7 @@ describe("send-notification tool", () => {
50
50
  contextPayload: {
51
51
  requestedMessage: "Your verification code is 123456",
52
52
  requestedByTool: "send_notification",
53
- requestedBySessionId: "conv-1",
53
+ requestedByContextId: "conv-1",
54
54
  requestedTitle: "Verification code",
55
55
  requestedByConversationId: "conv-override",
56
56
  preferredChannels: ["vellum"],
@@ -23,7 +23,6 @@ mock.module("../config/loader.js", () => ({
23
23
  ui: {},
24
24
 
25
25
  timeouts: { shellDefaultTimeoutSec: 120, shellMaxTimeoutSec: 600 },
26
- sandbox: { enabled: false, backend: "none" },
27
26
  secretDetection: { allowOneTimeSend: false },
28
27
  }),
29
28
  }));
@@ -34,8 +34,7 @@ const mockConfig = {
34
34
  shellMaxTimeoutSec: 600,
35
35
  permissionTimeoutSec: 300,
36
36
  },
37
- sandbox: { enabled: false },
38
- rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
37
+ rateLimit: { maxRequestsPerMinute: 0 },
39
38
  secretDetection: {
40
39
  enabled: true,
41
40
  action: "warn" as const,
@@ -0,0 +1,549 @@
1
+ import { mkdtempSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
5
+
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
+ }));
23
+
24
+ mock.module("../util/logger.js", () => ({
25
+ getLogger: () =>
26
+ new Proxy({} as Record<string, unknown>, {
27
+ get: () => () => {},
28
+ }),
29
+ }));
30
+
31
+ mock.module("../memory/qdrant-client.js", () => ({
32
+ getQdrantClient: () => ({
33
+ searchWithFilter: async () => [],
34
+ hybridSearch: async () => [],
35
+ upsertPoints: async () => {},
36
+ deletePoints: async () => {},
37
+ }),
38
+ initQdrantClient: () => {},
39
+ }));
40
+
41
+ // Controllable mock for resolveCatalog used by seedCatalogSkillMemories
42
+ let mockResolveCatalog: () => Promise<
43
+ import("../skills/catalog-install.js").CatalogSkill[]
44
+ > = async () => [];
45
+
46
+ mock.module("../skills/catalog-install.js", () => ({
47
+ resolveCatalog: (..._args: unknown[]) => mockResolveCatalog(),
48
+ }));
49
+
50
+ // Controllable mock for isAssistantFeatureFlagEnabled used by seedCatalogSkillMemories
51
+ let mockIsFeatureFlagEnabled: (key: string) => boolean = () => true;
52
+
53
+ mock.module("../config/assistant-feature-flags.js", () => ({
54
+ isAssistantFeatureFlagEnabled: (key: string, _config: unknown) =>
55
+ mockIsFeatureFlagEnabled(key),
56
+ getAssistantFeatureFlagDefaults: () => ({}),
57
+ }));
58
+
59
+ import { DEFAULT_CONFIG } from "../config/defaults.js";
60
+
61
+ const TEST_CONFIG = {
62
+ ...DEFAULT_CONFIG,
63
+ memory: {
64
+ ...DEFAULT_CONFIG.memory,
65
+ enabled: true,
66
+ extraction: {
67
+ ...DEFAULT_CONFIG.memory.extraction,
68
+ useLLM: false,
69
+ },
70
+ },
71
+ };
72
+
73
+ mock.module("../config/loader.js", () => ({
74
+ loadConfig: () => TEST_CONFIG,
75
+ getConfig: () => TEST_CONFIG,
76
+ loadRawConfig: () => ({}),
77
+ saveRawConfig: () => {},
78
+ invalidateConfigCache: () => {},
79
+ }));
80
+
81
+ 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";
84
+ import {
85
+ buildCapabilityStatement,
86
+ deleteSkillCapabilityMemory,
87
+ seedCatalogSkillMemories,
88
+ upsertSkillCapabilityMemory,
89
+ } from "../skills/skill-memory.js";
90
+
91
+ initializeDb();
92
+
93
+ afterAll(() => {
94
+ resetDb();
95
+ try {
96
+ rmSync(testDir, { recursive: true });
97
+ } catch {
98
+ // best effort cleanup
99
+ }
100
+ });
101
+
102
+ function resetTables() {
103
+ const db = getDb();
104
+ db.run("DELETE FROM memory_item_sources");
105
+ db.run("DELETE FROM memory_embeddings");
106
+ db.run("DELETE FROM memory_items");
107
+ db.run("DELETE FROM memory_jobs");
108
+ }
109
+
110
+ function makeSkill(overrides: Partial<CatalogSkill> = {}): CatalogSkill {
111
+ return {
112
+ id: "test-skill",
113
+ name: "Test Skill",
114
+ description: "A skill for testing",
115
+ ...overrides,
116
+ };
117
+ }
118
+
119
+ // ─── buildCapabilityStatement ────────────────────────────────────────────────
120
+
121
+ describe("buildCapabilityStatement", () => {
122
+ test("includes display name, id, and description", () => {
123
+ const entry = makeSkill({
124
+ metadata: { vellum: { "display-name": "My Skill" } },
125
+ });
126
+ const result = buildCapabilityStatement(entry);
127
+ expect(result).toContain('"My Skill"');
128
+ expect(result).toContain("(test-skill)");
129
+ expect(result).toContain("A skill for testing");
130
+ });
131
+
132
+ 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);
142
+ expect(result).toContain("Use when:");
143
+ expect(result).toContain("user asks to search");
144
+ expect(result).toContain("needs web data");
145
+ });
146
+
147
+ test("works without metadata (falls back to name)", () => {
148
+ const entry = makeSkill({ metadata: undefined });
149
+ const result = buildCapabilityStatement(entry);
150
+ expect(result).toContain('"Test Skill"');
151
+ expect(result).toContain("(test-skill)");
152
+ expect(result).toContain("A skill for testing");
153
+ });
154
+
155
+ test("truncates long statements to 500 chars", () => {
156
+ const longDesc = "x".repeat(600);
157
+ const entry = makeSkill({ description: longDesc });
158
+ const result = buildCapabilityStatement(entry);
159
+ expect(result.length).toBe(500);
160
+ });
161
+ });
162
+
163
+ // ─── upsertSkillCapabilityMemory ─────────────────────────────────────────────
164
+
165
+ describe("upsertSkillCapabilityMemory", () => {
166
+ beforeEach(resetTables);
167
+
168
+ test("inserts with correct kind, subject, confidence, importance", () => {
169
+ const entry = makeSkill();
170
+ upsertSkillCapabilityMemory("test-skill", entry);
171
+
172
+ const db = getDb();
173
+ const items = db.select().from(memoryItems).all();
174
+ expect(items).toHaveLength(1);
175
+ expect(items[0].kind).toBe("capability");
176
+ expect(items[0].subject).toBe("skill:test-skill");
177
+ expect(items[0].confidence).toBe(1.0);
178
+ expect(items[0].importance).toBe(0.7);
179
+ expect(items[0].status).toBe("active");
180
+ expect(items[0].scopeId).toBe("default");
181
+
182
+ // Should also enqueue an embed_item job
183
+ const jobs = db.select().from(memoryJobs).all();
184
+ expect(jobs).toHaveLength(1);
185
+ expect(jobs[0].type).toBe("embed_item");
186
+ });
187
+
188
+ test("is idempotent (same entry only touches lastSeenAt)", () => {
189
+ const entry = makeSkill();
190
+ upsertSkillCapabilityMemory("test-skill", entry);
191
+
192
+ const db = getDb();
193
+ const before = db.select().from(memoryItems).all();
194
+ expect(before).toHaveLength(1);
195
+ const originalLastSeen = before[0].lastSeenAt;
196
+
197
+ // Upsert again
198
+ upsertSkillCapabilityMemory("test-skill", entry);
199
+
200
+ const after = db.select().from(memoryItems).all();
201
+ 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);
205
+
206
+ // Should NOT enqueue a second embed job (only 1 from initial insert)
207
+ const jobs = db.select().from(memoryJobs).all();
208
+ expect(jobs).toHaveLength(1);
209
+ });
210
+
211
+ test("updates statement when description changes", () => {
212
+ const entry = makeSkill({ description: "Original description" });
213
+ upsertSkillCapabilityMemory("test-skill", entry);
214
+
215
+ const db = getDb();
216
+ const before = db.select().from(memoryItems).all();
217
+ expect(before).toHaveLength(1);
218
+ expect(before[0].statement).toContain("Original description");
219
+
220
+ // Change description
221
+ const updatedEntry = makeSkill({ description: "Updated description" });
222
+ upsertSkillCapabilityMemory("test-skill", updatedEntry);
223
+
224
+ const after = db.select().from(memoryItems).all();
225
+ expect(after).toHaveLength(1);
226
+ expect(after[0].statement).toContain("Updated description");
227
+ expect(after[0].fingerprint).not.toBe(before[0].fingerprint);
228
+
229
+ // Should enqueue a second embed job
230
+ const jobs = db.select().from(memoryJobs).all();
231
+ expect(jobs).toHaveLength(2);
232
+ });
233
+
234
+ test("reactivates soft-deleted items", () => {
235
+ const entry = makeSkill();
236
+ upsertSkillCapabilityMemory("test-skill", entry);
237
+
238
+ const db = getDb();
239
+ // Soft-delete the item
240
+ db.update(memoryItems)
241
+ .set({ status: "deleted" })
242
+ .where(eq(memoryItems.subject, "skill:test-skill"))
243
+ .run();
244
+
245
+ const deleted = db.select().from(memoryItems).all();
246
+ expect(deleted[0].status).toBe("deleted");
247
+
248
+ // Clear jobs from initial insert
249
+ db.run("DELETE FROM memory_jobs");
250
+
251
+ // Upsert again — should reactivate
252
+ upsertSkillCapabilityMemory("test-skill", entry);
253
+
254
+ const reactivated = db.select().from(memoryItems).all();
255
+ expect(reactivated).toHaveLength(1);
256
+ expect(reactivated[0].status).toBe("active");
257
+
258
+ // Should enqueue embed job for reactivated item
259
+ const jobs = db.select().from(memoryJobs).all();
260
+ expect(jobs).toHaveLength(1);
261
+ expect(jobs[0].type).toBe("embed_item");
262
+ });
263
+
264
+ test("does not throw on DB error", () => {
265
+ // Close the DB connection to force errors, then reinitialize
266
+ resetDb();
267
+ // getDb() will create a new connection, but we can force a DB error by
268
+ // dropping the table it reads from. Use a fresh DB without initialization.
269
+ // Instead, verify the try/catch by closing and reopening:
270
+ // resetDb closes the connection; getDb lazily reconnects.
271
+ // We drop the memory_items table to force an error on the next query.
272
+ const db = getDb();
273
+ db.run("DROP TABLE IF EXISTS memory_items");
274
+
275
+ expect(() => {
276
+ upsertSkillCapabilityMemory("test-skill", makeSkill());
277
+ }).not.toThrow();
278
+
279
+ // Restore DB state for subsequent tests
280
+ resetDb();
281
+ initializeDb();
282
+ });
283
+ });
284
+
285
+ // ─── deleteSkillCapabilityMemory ─────────────────────────────────────────────
286
+
287
+ describe("deleteSkillCapabilityMemory", () => {
288
+ beforeEach(resetTables);
289
+
290
+ test("soft-deletes matching item", () => {
291
+ const entry = makeSkill();
292
+ upsertSkillCapabilityMemory("test-skill", entry);
293
+
294
+ const db = getDb();
295
+ const before = db.select().from(memoryItems).all();
296
+ expect(before).toHaveLength(1);
297
+ expect(before[0].status).toBe("active");
298
+
299
+ deleteSkillCapabilityMemory("test-skill");
300
+
301
+ const after = db.select().from(memoryItems).all();
302
+ expect(after).toHaveLength(1);
303
+ expect(after[0].status).toBe("deleted");
304
+ });
305
+
306
+ test("is no-op for missing item", () => {
307
+ // Should not throw when no matching item exists
308
+ expect(() => {
309
+ deleteSkillCapabilityMemory("nonexistent-skill");
310
+ }).not.toThrow();
311
+
312
+ const db = getDb();
313
+ const items = db.select().from(memoryItems).all();
314
+ expect(items).toHaveLength(0);
315
+ });
316
+
317
+ test("does not throw on DB error", () => {
318
+ // Close and reopen DB, then drop the table to force a query error
319
+ resetDb();
320
+ const db = getDb();
321
+ db.run("DROP TABLE IF EXISTS memory_items");
322
+
323
+ expect(() => {
324
+ deleteSkillCapabilityMemory("test-skill");
325
+ }).not.toThrow();
326
+
327
+ // Restore DB state for subsequent tests
328
+ resetDb();
329
+ initializeDb();
330
+ });
331
+ });
332
+
333
+ // ─── seedCatalogSkillMemories ─────────────────────────────────────────────
334
+
335
+ describe("seedCatalogSkillMemories", () => {
336
+ beforeEach(() => {
337
+ resetTables();
338
+ // Reset mocks to defaults
339
+ mockResolveCatalog = async () => [];
340
+ mockIsFeatureFlagEnabled = () => true;
341
+ });
342
+
343
+ test("upserts capability memories for all catalog entries", async () => {
344
+ const skills: CatalogSkill[] = [
345
+ makeSkill({ id: "skill-a", name: "Skill A", description: "Does A" }),
346
+ makeSkill({ id: "skill-b", name: "Skill B", description: "Does B" }),
347
+ makeSkill({ id: "skill-c", name: "Skill C", description: "Does C" }),
348
+ ];
349
+ mockResolveCatalog = async () => skills;
350
+
351
+ await seedCatalogSkillMemories();
352
+
353
+ const db = getDb();
354
+ const items = db
355
+ .select()
356
+ .from(memoryItems)
357
+ .where(eq(memoryItems.kind, "capability"))
358
+ .all();
359
+ expect(items).toHaveLength(3);
360
+
361
+ const subjects = items.map((i) => i.subject).sort();
362
+ expect(subjects).toEqual([
363
+ "skill:skill-a",
364
+ "skill:skill-b",
365
+ "skill:skill-c",
366
+ ]);
367
+
368
+ // All should be active
369
+ for (const item of items) {
370
+ expect(item.status).toBe("active");
371
+ }
372
+ });
373
+
374
+ test("prunes stale capabilities for skills no longer in catalog", async () => {
375
+ // First seed with three skills
376
+ const initialSkills: CatalogSkill[] = [
377
+ makeSkill({ id: "skill-a", name: "Skill A", description: "Does A" }),
378
+ makeSkill({ id: "skill-b", name: "Skill B", description: "Does B" }),
379
+ makeSkill({ id: "skill-c", name: "Skill C", description: "Does C" }),
380
+ ];
381
+ mockResolveCatalog = async () => initialSkills;
382
+ await seedCatalogSkillMemories();
383
+
384
+ const db = getDb();
385
+ const beforeItems = db
386
+ .select()
387
+ .from(memoryItems)
388
+ .where(eq(memoryItems.kind, "capability"))
389
+ .all();
390
+ expect(beforeItems).toHaveLength(3);
391
+ expect(beforeItems.every((i) => i.status === "active")).toBe(true);
392
+
393
+ // Now seed with only skill-a — skill-b and skill-c should be pruned
394
+ mockResolveCatalog = async () => [
395
+ makeSkill({ id: "skill-a", name: "Skill A", description: "Does A" }),
396
+ ];
397
+ await seedCatalogSkillMemories();
398
+
399
+ const afterItems = db
400
+ .select()
401
+ .from(memoryItems)
402
+ .where(eq(memoryItems.kind, "capability"))
403
+ .all();
404
+ expect(afterItems).toHaveLength(3); // still 3 rows, but 2 are soft-deleted
405
+
406
+ const active = afterItems.filter((i) => i.status === "active");
407
+ const deleted = afterItems.filter((i) => i.status === "deleted");
408
+
409
+ expect(active).toHaveLength(1);
410
+ expect(active[0].subject).toBe("skill:skill-a");
411
+
412
+ expect(deleted).toHaveLength(2);
413
+ const deletedSubjects = deleted.map((i) => i.subject).sort();
414
+ expect(deletedSubjects).toEqual(["skill:skill-b", "skill:skill-c"]);
415
+ });
416
+
417
+ test("handles empty catalog without errors", async () => {
418
+ // Pre-populate a skill so we can verify it gets pruned
419
+ upsertSkillCapabilityMemory(
420
+ "existing-skill",
421
+ makeSkill({ id: "existing-skill" }),
422
+ );
423
+
424
+ const db = getDb();
425
+ const beforeItems = db.select().from(memoryItems).all();
426
+ expect(beforeItems).toHaveLength(1);
427
+ expect(beforeItems[0].status).toBe("active");
428
+
429
+ // Seed with empty catalog
430
+ mockResolveCatalog = async () => [];
431
+ await seedCatalogSkillMemories();
432
+
433
+ // The existing skill should be pruned (soft-deleted)
434
+ const afterItems = db.select().from(memoryItems).all();
435
+ expect(afterItems).toHaveLength(1);
436
+ expect(afterItems[0].status).toBe("deleted");
437
+ });
438
+
439
+ test("does not throw when resolveCatalog rejects", async () => {
440
+ mockResolveCatalog = async () => {
441
+ throw new Error("Network failure");
442
+ };
443
+
444
+ // Best-effort: should not propagate the error
445
+ await expect(seedCatalogSkillMemories()).resolves.toBeUndefined();
446
+ });
447
+
448
+ test("skips skills whose feature flag is disabled", async () => {
449
+ const skills: CatalogSkill[] = [
450
+ makeSkill({
451
+ id: "unflagged-skill",
452
+ name: "Unflagged",
453
+ description: "No flag",
454
+ }),
455
+ makeSkill({
456
+ id: "flagged-skill",
457
+ name: "Flagged",
458
+ description: "Has flag",
459
+ metadata: { vellum: { "feature-flag": "my_gated_feature" } },
460
+ }),
461
+ ];
462
+ mockResolveCatalog = async () => skills;
463
+
464
+ // Disable the feature flag for the flagged skill
465
+ mockIsFeatureFlagEnabled = (key: string) =>
466
+ key !== "feature_flags.my_gated_feature.enabled";
467
+
468
+ await seedCatalogSkillMemories();
469
+
470
+ const db = getDb();
471
+ const items = db
472
+ .select()
473
+ .from(memoryItems)
474
+ .where(eq(memoryItems.kind, "capability"))
475
+ .all();
476
+
477
+ // Only the unflagged skill should have a capability row
478
+ expect(items).toHaveLength(1);
479
+ expect(items[0].subject).toBe("skill:unflagged-skill");
480
+ expect(items[0].status).toBe("active");
481
+ });
482
+
483
+ test("prunes pre-existing capability for a skill whose flag becomes disabled", async () => {
484
+ // First seed with both skills, all flags enabled
485
+ const skills: CatalogSkill[] = [
486
+ makeSkill({
487
+ id: "unflagged-skill",
488
+ name: "Unflagged",
489
+ description: "No flag",
490
+ }),
491
+ makeSkill({
492
+ id: "flagged-skill",
493
+ name: "Flagged",
494
+ description: "Has flag",
495
+ metadata: { vellum: { "feature-flag": "my_gated_feature" } },
496
+ }),
497
+ ];
498
+ mockResolveCatalog = async () => skills;
499
+ mockIsFeatureFlagEnabled = () => true;
500
+ await seedCatalogSkillMemories();
501
+
502
+ const db = getDb();
503
+ const beforeItems = db
504
+ .select()
505
+ .from(memoryItems)
506
+ .where(eq(memoryItems.kind, "capability"))
507
+ .all();
508
+ expect(beforeItems).toHaveLength(2);
509
+ expect(beforeItems.every((i) => i.status === "active")).toBe(true);
510
+
511
+ // Now disable the flag — the flagged skill should be pruned
512
+ mockIsFeatureFlagEnabled = (key: string) =>
513
+ key !== "feature_flags.my_gated_feature.enabled";
514
+ await seedCatalogSkillMemories();
515
+
516
+ const afterItems = db
517
+ .select()
518
+ .from(memoryItems)
519
+ .where(eq(memoryItems.kind, "capability"))
520
+ .all();
521
+ expect(afterItems).toHaveLength(2); // still 2 rows, but one soft-deleted
522
+
523
+ const active = afterItems.filter((i) => i.status === "active");
524
+ const deleted = afterItems.filter((i) => i.status === "deleted");
525
+
526
+ expect(active).toHaveLength(1);
527
+ expect(active[0].subject).toBe("skill:unflagged-skill");
528
+
529
+ expect(deleted).toHaveLength(1);
530
+ expect(deleted[0].subject).toBe("skill:flagged-skill");
531
+ });
532
+
533
+ test("does not throw on DB error during pruning", async () => {
534
+ mockResolveCatalog = async () => [
535
+ makeSkill({ id: "skill-a", name: "Skill A", description: "Does A" }),
536
+ ];
537
+
538
+ // Drop memory_items to force a DB error during the prune phase
539
+ resetDb();
540
+ const db = getDb();
541
+ db.run("DROP TABLE IF EXISTS memory_items");
542
+
543
+ await expect(seedCatalogSkillMemories()).resolves.toBeUndefined();
544
+
545
+ // Restore DB state for subsequent tests
546
+ resetDb();
547
+ initializeDb();
548
+ });
549
+ });
@@ -17,8 +17,7 @@ const mockConfig = {
17
17
  shellMaxTimeoutSec: 600,
18
18
  permissionTimeoutSec: 300,
19
19
  },
20
- sandbox: { enabled: false },
21
- rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
20
+ rateLimit: { maxRequestsPerMinute: 0 },
22
21
  secretDetection: {
23
22
  enabled: true,
24
23
  action: "warn" as const,