@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
@@ -0,0 +1,169 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { describe, expect, test } from "bun:test";
3
+
4
+ import { drizzle } from "drizzle-orm/bun-sqlite";
5
+
6
+ import { migrateDropCapabilityCardState } from "../memory/migrations/176-drop-capability-card-state.js";
7
+ import * as schema from "../memory/schema.js";
8
+
9
+ function createTestDb() {
10
+ const sqlite = new Database(":memory:");
11
+ sqlite.exec("PRAGMA journal_mode=WAL");
12
+ sqlite.exec("PRAGMA foreign_keys = ON");
13
+ return drizzle(sqlite, { schema });
14
+ }
15
+
16
+ type TestDb = ReturnType<typeof createTestDb>;
17
+
18
+ function getRawSqlite(db: TestDb): Database {
19
+ return (db as unknown as { $client: Database }).$client;
20
+ }
21
+
22
+ function createLegacyCapabilityCardTables(raw: Database) {
23
+ raw.exec(/*sql*/ `
24
+ CREATE TABLE memory_checkpoints (
25
+ key TEXT PRIMARY KEY,
26
+ value TEXT NOT NULL,
27
+ updated_at INTEGER NOT NULL
28
+ )
29
+ `);
30
+
31
+ raw.exec(/*sql*/ `
32
+ CREATE TABLE memory_jobs (
33
+ id TEXT PRIMARY KEY,
34
+ type TEXT NOT NULL,
35
+ payload TEXT NOT NULL,
36
+ status TEXT NOT NULL,
37
+ attempts INTEGER NOT NULL DEFAULT 0,
38
+ deferrals INTEGER NOT NULL DEFAULT 0,
39
+ run_after INTEGER NOT NULL,
40
+ last_error TEXT,
41
+ started_at INTEGER,
42
+ created_at INTEGER NOT NULL,
43
+ updated_at INTEGER NOT NULL
44
+ )
45
+ `);
46
+
47
+ raw.exec(/*sql*/ `
48
+ CREATE TABLE conversation_starters (
49
+ id TEXT PRIMARY KEY,
50
+ label TEXT NOT NULL,
51
+ prompt TEXT NOT NULL,
52
+ generation_batch INTEGER NOT NULL,
53
+ scope_id TEXT NOT NULL DEFAULT 'default',
54
+ card_type TEXT NOT NULL DEFAULT 'chip',
55
+ created_at INTEGER NOT NULL
56
+ )
57
+ `);
58
+
59
+ raw.exec(/*sql*/ `
60
+ CREATE TABLE capability_card_categories (
61
+ scope_id TEXT NOT NULL,
62
+ category TEXT NOT NULL,
63
+ relevance REAL NOT NULL,
64
+ generation_batch INTEGER NOT NULL,
65
+ created_at INTEGER NOT NULL,
66
+ PRIMARY KEY (scope_id, category)
67
+ )
68
+ `);
69
+ }
70
+
71
+ describe("migrateDropCapabilityCardState", () => {
72
+ test("removes legacy capability-card rows, jobs, checkpoints, and table", () => {
73
+ const db = createTestDb();
74
+ const raw = getRawSqlite(db);
75
+ const now = Date.now();
76
+
77
+ createLegacyCapabilityCardTables(raw);
78
+
79
+ raw.exec(/*sql*/ `
80
+ INSERT INTO conversation_starters (id, label, prompt, generation_batch, scope_id, card_type, created_at)
81
+ VALUES
82
+ ('chip-1', 'Prep tomorrow', 'Prep tomorrow', 1, 'default', 'chip', ${now}),
83
+ ('card-1', 'Do this first', 'Do this first', 1, 'default', 'card', ${now})
84
+ `);
85
+ raw.exec(/*sql*/ `
86
+ INSERT INTO memory_jobs (id, type, payload, status, attempts, deferrals, run_after, created_at, updated_at)
87
+ VALUES
88
+ ('job-chip', 'generate_conversation_starters', '{}', 'pending', 0, 0, ${now}, ${now}, ${now}),
89
+ ('job-card', 'generate_capability_cards', '{}', 'pending', 0, 0, ${now}, ${now}, ${now})
90
+ `);
91
+ raw.exec(/*sql*/ `
92
+ INSERT INTO memory_checkpoints (key, value, updated_at)
93
+ VALUES
94
+ ('capability_cards:generation_batch', '7', ${now}),
95
+ ('other_checkpoint', '1', ${now})
96
+ `);
97
+ raw.exec(/*sql*/ `
98
+ INSERT INTO capability_card_categories (scope_id, category, relevance, generation_batch, created_at)
99
+ VALUES ('default', 'communication', 0.9, 1, ${now})
100
+ `);
101
+
102
+ migrateDropCapabilityCardState(db);
103
+
104
+ const starterRows = raw
105
+ .query(`SELECT id, card_type FROM conversation_starters ORDER BY id`)
106
+ .all() as Array<{ id: string; card_type: string }>;
107
+ expect(starterRows).toEqual([{ id: "chip-1", card_type: "chip" }]);
108
+
109
+ const jobTypes = raw
110
+ .query(`SELECT type FROM memory_jobs ORDER BY id`)
111
+ .all() as Array<{
112
+ type: string;
113
+ }>;
114
+ expect(jobTypes).toEqual([{ type: "generate_conversation_starters" }]);
115
+
116
+ const checkpointKeys = raw
117
+ .query(`SELECT key FROM memory_checkpoints ORDER BY key`)
118
+ .all() as Array<{ key: string }>;
119
+ expect(checkpointKeys.map((row) => row.key)).toEqual([
120
+ "migration_drop_capability_card_state_v1",
121
+ "other_checkpoint",
122
+ ]);
123
+
124
+ const migrationCheckpoint = raw
125
+ .query(
126
+ `SELECT value FROM memory_checkpoints WHERE key = 'migration_drop_capability_card_state_v1'`,
127
+ )
128
+ .get() as { value: string } | null;
129
+ expect(migrationCheckpoint?.value).toBe("1");
130
+
131
+ const legacyTable = raw
132
+ .query(
133
+ `SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'capability_card_categories'`,
134
+ )
135
+ .get();
136
+ expect(legacyTable).toBeNull();
137
+ });
138
+
139
+ test("is idempotent when run more than once", () => {
140
+ const db = createTestDb();
141
+ const raw = getRawSqlite(db);
142
+ const now = Date.now();
143
+
144
+ createLegacyCapabilityCardTables(raw);
145
+ raw.exec(/*sql*/ `
146
+ INSERT INTO conversation_starters (id, label, prompt, generation_batch, scope_id, card_type, created_at)
147
+ VALUES ('card-1', 'Do this first', 'Do this first', 1, 'default', 'card', ${now})
148
+ `);
149
+ raw.exec(/*sql*/ `
150
+ INSERT INTO memory_jobs (id, type, payload, status, attempts, deferrals, run_after, created_at, updated_at)
151
+ VALUES ('job-card', 'generate_capability_cards', '{}', 'pending', 0, 0, ${now}, ${now}, ${now})
152
+ `);
153
+
154
+ migrateDropCapabilityCardState(db);
155
+ migrateDropCapabilityCardState(db);
156
+
157
+ const starterCount = raw
158
+ .query(`SELECT COUNT(*) AS count FROM conversation_starters`)
159
+ .get() as { count: number };
160
+ expect(starterCount.count).toBe(0);
161
+
162
+ const jobCount = raw
163
+ .query(
164
+ `SELECT COUNT(*) AS count FROM memory_jobs WHERE type = 'generate_capability_cards'`,
165
+ )
166
+ .get() as { count: number };
167
+ expect(jobCount.count).toBe(0);
168
+ });
169
+ });
@@ -53,7 +53,6 @@ mock.module("../util/logger.js", () => ({
53
53
 
54
54
  mock.module("../config/loader.js", () => ({
55
55
  getConfig: () => ({
56
- sandbox: { enabled: false, backend: "native" },
57
56
  assistantFeatureFlagValues: {
58
57
  "feature_flags.browser.enabled": true,
59
58
  },
@@ -66,9 +65,9 @@ mock.module("../config/loader.js", () => ({
66
65
  "image-generation": {
67
66
  mode: "your-own",
68
67
  provider: "gemini",
69
- model: "gemini-2.5-flash-image",
68
+ model: "gemini-3.1-flash-image-preview",
70
69
  },
71
- "web-search": { mode: "your-own", provider: "anthropic-native" },
70
+ "web-search": { mode: "your-own", provider: "inference-provider-native" },
72
71
  },
73
72
  }),
74
73
  loadConfig: () => ({}),
@@ -83,7 +82,7 @@ mock.module("../config/loader.js", () => ({
83
82
 
84
83
  const { buildSystemPrompt } = await import("../prompts/system-prompt.js");
85
84
 
86
- describe("Dynamic Skill Authoring Workflow prompt section", () => {
85
+ describe("Dynamic Skill Authoring Workflow moved to tool descriptions", () => {
87
86
  beforeEach(() => {
88
87
  mkdirSync(TEST_DIR, { recursive: true });
89
88
  });
@@ -94,36 +93,11 @@ describe("Dynamic Skill Authoring Workflow prompt section", () => {
94
93
  }
95
94
  });
96
95
 
97
- test("buildSystemPrompt includes dynamic skill workflow section", () => {
96
+ test("system prompt no longer contains Dynamic Skill Authoring section", () => {
98
97
  writeFileSync(join(TEST_DIR, "IDENTITY.md"), "I am Vellum.");
99
98
  const result = buildSystemPrompt();
100
- expect(result).toContain("## Dynamic Skill Authoring Workflow");
101
- });
102
-
103
- test("workflow section mentions scaffold and delete tools and bun run workflow", () => {
104
- writeFileSync(join(TEST_DIR, "IDENTITY.md"), "I am Vellum.");
105
- const result = buildSystemPrompt();
106
- expect(result).toContain("bun run /tmp/vellum-eval/snippet.ts");
107
- expect(result).toContain("scaffold_managed_skill");
108
- expect(result).toContain("delete_managed_skill");
109
- });
110
-
111
- test("workflow section includes user confirmation warning", () => {
112
- writeFileSync(join(TEST_DIR, "IDENTITY.md"), "I am Vellum.");
113
- const result = buildSystemPrompt();
114
- expect(result).toContain("explicit user confirmation");
115
- });
116
-
117
- test("workflow section includes retry limit guidance", () => {
118
- writeFileSync(join(TEST_DIR, "IDENTITY.md"), "I am Vellum.");
119
- const result = buildSystemPrompt();
120
- expect(result).toContain("3 attempts");
121
- });
122
-
123
- test("workflow section includes conversation eviction note", () => {
124
- writeFileSync(join(TEST_DIR, "IDENTITY.md"), "I am Vellum.");
125
- const result = buildSystemPrompt();
126
- expect(result).toContain("recreated session");
99
+ expect(result).not.toContain("## Dynamic Skill Authoring Workflow");
100
+ expect(result).not.toContain("### Community Skills Discovery");
127
101
  });
128
102
 
129
103
  test("prompt still includes available skills catalog when skills exist", () => {
@@ -138,8 +112,7 @@ describe("Dynamic Skill Authoring Workflow prompt section", () => {
138
112
 
139
113
  const result = buildSystemPrompt();
140
114
  expect(result).toContain("## Available Skills");
141
- expect(result).toContain('id="test-skill"');
142
- expect(result).toContain("## Dynamic Skill Authoring Workflow");
115
+ expect(result).toContain("**test-skill**");
143
116
  });
144
117
 
145
118
  test("prompt is additive with IDENTITY/SOUL/USER files", () => {
@@ -151,21 +124,14 @@ describe("Dynamic Skill Authoring Workflow prompt section", () => {
151
124
  expect(result).toContain("Identity here");
152
125
  expect(result).toContain("Soul here");
153
126
  expect(result).toContain("User here");
154
- expect(result).toContain("## Dynamic Skill Authoring Workflow");
155
- });
156
-
157
- test("workflow section includes skill_load instruction", () => {
158
- writeFileSync(join(TEST_DIR, "IDENTITY.md"), "I am Vellum.");
159
- const result = buildSystemPrompt();
160
- expect(result).toContain("skill_load");
161
127
  });
162
128
 
163
- test("browser skill has activation hints in available_skills XML instead of dedicated section", () => {
129
+ test("browser skill has activation hints in skills catalog instead of dedicated section", () => {
164
130
  writeFileSync(join(TEST_DIR, "IDENTITY.md"), "I am Vellum.");
165
131
  const result = buildSystemPrompt();
166
- // Browser routing moved from dedicated section to frontmatter hints
132
+ // Browser routing moved from dedicated section to inline hints in catalog bullet
167
133
  expect(result).not.toContain("Browser Skill Prerequisite");
168
- expect(result).toContain('id="browser"');
169
- expect(result).toContain("hints=");
134
+ expect(result).toContain("**browser**");
135
+ expect(result).toContain("Load first if you need browser_* tools");
170
136
  });
171
137
  });
@@ -116,7 +116,7 @@ describe("emitNotificationSignal routing intent re-persistence", () => {
116
116
  const result = await emitNotificationSignal({
117
117
  sourceEventName: "schedule.notify",
118
118
  sourceChannel: "scheduler",
119
- sourceSessionId: "rem-1",
119
+ sourceContextId: "rem-1",
120
120
  attentionHints: {
121
121
  requiresAction: true,
122
122
  urgency: "high",
@@ -162,7 +162,7 @@ describe("emitNotificationSignal routing intent re-persistence", () => {
162
162
  await emitNotificationSignal({
163
163
  sourceEventName: "schedule.notify",
164
164
  sourceChannel: "scheduler",
165
- sourceSessionId: "rem-2",
165
+ sourceContextId: "rem-2",
166
166
  attentionHints: {
167
167
  requiresAction: false,
168
168
  urgency: "medium",
@@ -198,7 +198,7 @@ describe("emitNotificationSignal routing intent re-persistence", () => {
198
198
  await emitNotificationSignal({
199
199
  sourceEventName: "schedule.notify",
200
200
  sourceChannel: "scheduler",
201
- sourceSessionId: "rem-3",
201
+ sourceContextId: "rem-3",
202
202
  attentionHints: {
203
203
  requiresAction: false,
204
204
  urgency: "medium",
@@ -1,4 +1,4 @@
1
- import { randomBytes } from "node:crypto";
1
+ import { createCipheriv, pbkdf2Sync, randomBytes } from "node:crypto";
2
2
  import {
3
3
  chmodSync,
4
4
  existsSync,
@@ -7,9 +7,10 @@ import {
7
7
  readFileSync,
8
8
  rmSync,
9
9
  statSync,
10
+ unlinkSync,
10
11
  writeFileSync,
11
12
  } from "node:fs";
12
- import { tmpdir } from "node:os";
13
+ import { hostname, tmpdir, userInfo } from "node:os";
13
14
  import { join } from "node:path";
14
15
  import {
15
16
  afterAll,
@@ -23,7 +24,7 @@ import {
23
24
  } from "bun:test";
24
25
 
25
26
  // ---------------------------------------------------------------------------
26
- // Mock only the logger (not platform we use _setStorePath instead)
27
+ // Mock only the logger (not platform -- we use _setStorePath instead)
27
28
  // ---------------------------------------------------------------------------
28
29
 
29
30
  mock.module("../util/logger.js", () => ({
@@ -34,6 +35,7 @@ mock.module("../util/logger.js", () => ({
34
35
  }));
35
36
 
36
37
  import {
38
+ _setStoreKeyPath,
37
39
  _setStorePath,
38
40
  deleteKey,
39
41
  getKey,
@@ -50,6 +52,80 @@ const TEST_DIR = join(
50
52
  `vellum-enc-test-${randomBytes(4).toString("hex")}`,
51
53
  );
52
54
  const STORE_PATH = join(TEST_DIR, "keys.enc");
55
+ const STORE_KEY_PATH = join(TEST_DIR, "store.key");
56
+
57
+ // ---------------------------------------------------------------------------
58
+ // Legacy v1 helpers (for migration tests)
59
+ // ---------------------------------------------------------------------------
60
+
61
+ const ALGORITHM = "aes-256-gcm";
62
+ const KEY_LENGTH = 32;
63
+ const IV_LENGTH = 16;
64
+ const AUTH_TAG_LENGTH = 16;
65
+ const PBKDF2_ITERATIONS = process.env.BUN_TEST === "1" ? 1 : 100_000;
66
+ const SALT_LENGTH = 32;
67
+
68
+ /** Local copy of the legacy machine entropy derivation (the export was removed). */
69
+ function legacyMachineEntropy(): string {
70
+ const parts: string[] = [];
71
+ try {
72
+ parts.push(hostname());
73
+ } catch {
74
+ parts.push("unknown-host");
75
+ }
76
+ try {
77
+ parts.push(userInfo().username);
78
+ } catch {
79
+ parts.push("unknown-user");
80
+ }
81
+ parts.push(process.platform);
82
+ parts.push(process.arch);
83
+ try {
84
+ parts.push(userInfo().homedir);
85
+ } catch {
86
+ parts.push("/tmp");
87
+ }
88
+ return parts.join(":");
89
+ }
90
+
91
+ function legacyDeriveKey(salt: Buffer): Buffer {
92
+ const entropy = legacyMachineEntropy();
93
+ return pbkdf2Sync(entropy, salt, PBKDF2_ITERATIONS, KEY_LENGTH, "sha512");
94
+ }
95
+
96
+ function legacyEncrypt(
97
+ plaintext: string,
98
+ key: Buffer,
99
+ ): { iv: string; tag: string; data: string } {
100
+ const iv = randomBytes(IV_LENGTH);
101
+ const cipher = createCipheriv(ALGORITHM, key, iv, {
102
+ authTagLength: AUTH_TAG_LENGTH,
103
+ });
104
+ const encrypted = Buffer.concat([
105
+ cipher.update(plaintext, "utf-8"),
106
+ cipher.final(),
107
+ ]);
108
+ const tag = cipher.getAuthTag();
109
+ return {
110
+ iv: iv.toString("hex"),
111
+ tag: tag.toString("hex"),
112
+ data: encrypted.toString("hex"),
113
+ };
114
+ }
115
+
116
+ /** Write a v1 store file directly (bypassing the module's writeStore). */
117
+ function writeV1Store(
118
+ path: string,
119
+ salt: Buffer,
120
+ entries: Record<string, { iv: string; tag: string; data: string }>,
121
+ ): void {
122
+ const store = {
123
+ version: 1,
124
+ salt: salt.toString("hex"),
125
+ entries,
126
+ };
127
+ writeFileSync(path, JSON.stringify(store, null, 2), { mode: 0o600 });
128
+ }
53
129
 
54
130
  // ---------------------------------------------------------------------------
55
131
  // Tests
@@ -66,10 +142,12 @@ describe("encrypted-store", () => {
66
142
  rmSync(join(TEST_DIR, entry), { recursive: true, force: true });
67
143
  }
68
144
  _setStorePath(STORE_PATH);
145
+ _setStoreKeyPath(STORE_KEY_PATH);
69
146
  });
70
147
 
71
148
  afterEach(() => {
72
149
  _setStorePath(null);
150
+ _setStoreKeyPath(null);
73
151
  });
74
152
 
75
153
  afterAll(() => {
@@ -173,16 +251,15 @@ describe("encrypted-store", () => {
173
251
  });
174
252
 
175
253
  // -----------------------------------------------------------------------
176
- // Store format
254
+ // Store format (v2)
177
255
  // -----------------------------------------------------------------------
178
256
  describe("store format", () => {
179
- test("store file is valid JSON with version, salt, and entries", () => {
257
+ test("store file is valid JSON with version 2 and entries (no salt)", () => {
180
258
  setKey("test", "value");
181
259
  const raw = readFileSync(STORE_PATH, "utf-8");
182
260
  const parsed = JSON.parse(raw);
183
- expect(parsed.version).toBe(1);
184
- expect(typeof parsed.salt).toBe("string");
185
- expect(parsed.salt.length).toBe(64); // 32 bytes = 64 hex chars
261
+ expect(parsed.version).toBe(2);
262
+ expect(parsed.salt).toBeUndefined();
186
263
  expect(typeof parsed.entries).toBe("object");
187
264
  expect(parsed.entries.test).toBeDefined();
188
265
  });
@@ -223,6 +300,163 @@ describe("encrypted-store", () => {
223
300
  });
224
301
  });
225
302
 
303
+ // -----------------------------------------------------------------------
304
+ // v2 format and store.key
305
+ // -----------------------------------------------------------------------
306
+ describe("v2 format and store.key", () => {
307
+ test("fresh store creates v2 format and store.key", () => {
308
+ setKey("test", "value");
309
+
310
+ // Verify keys.enc is v2 with no salt
311
+ const raw = readFileSync(STORE_PATH, "utf-8");
312
+ const parsed = JSON.parse(raw);
313
+ expect(parsed.version).toBe(2);
314
+ expect(parsed.salt).toBeUndefined();
315
+
316
+ // Verify store.key exists with exactly 32 bytes and 0o600 perms
317
+ expect(existsSync(STORE_KEY_PATH)).toBe(true);
318
+ const keyBuf = readFileSync(STORE_KEY_PATH);
319
+ expect(keyBuf.length).toBe(32);
320
+ const mode = statSync(STORE_KEY_PATH).mode & 0o777;
321
+ expect(mode).toBe(0o600);
322
+ });
323
+
324
+ test("v2 round-trip", () => {
325
+ // Set multiple values and read them back
326
+ setKey("key-a", "value-a");
327
+ setKey("key-b", "value-b");
328
+ setKey("key-c", "value-c");
329
+
330
+ expect(getKey("key-a")).toBe("value-a");
331
+ expect(getKey("key-b")).toBe("value-b");
332
+ expect(getKey("key-c")).toBe("value-c");
333
+
334
+ // Delete one and verify others remain
335
+ deleteKey("key-b");
336
+ expect(getKey("key-b")).toBeUndefined();
337
+ expect(getKey("key-a")).toBe("value-a");
338
+ expect(getKey("key-c")).toBe("value-c");
339
+ });
340
+
341
+ test("v2 store without store.key returns undefined", () => {
342
+ setKey("test", "value");
343
+ // Delete store.key
344
+ unlinkSync(STORE_KEY_PATH);
345
+ expect(getKey("test")).toBeUndefined();
346
+ });
347
+ });
348
+
349
+ // -----------------------------------------------------------------------
350
+ // v1 -> v2 migration
351
+ // -----------------------------------------------------------------------
352
+ describe("v1 to v2 migration", () => {
353
+ test("v1 to v2 migration preserves entries", () => {
354
+ // Create a v1 store using legacy encryption
355
+ const salt = randomBytes(SALT_LENGTH);
356
+ const legacyKey = legacyDeriveKey(salt);
357
+ const entries: Record<string, { iv: string; tag: string; data: string }> =
358
+ {};
359
+ entries["api-key"] = legacyEncrypt("secret-123", legacyKey);
360
+ entries["other-key"] = legacyEncrypt("secret-456", legacyKey);
361
+ writeV1Store(STORE_PATH, salt, entries);
362
+
363
+ // Access via getKey -- should trigger migration
364
+ const val1 = getKey("api-key");
365
+ expect(val1).toBe("secret-123");
366
+
367
+ const val2 = getKey("other-key");
368
+ expect(val2).toBe("secret-456");
369
+
370
+ // Store should now be v2
371
+ const raw = readFileSync(STORE_PATH, "utf-8");
372
+ const parsed = JSON.parse(raw);
373
+ expect(parsed.version).toBe(2);
374
+ expect(parsed.salt).toBeUndefined();
375
+
376
+ // store.key should exist
377
+ expect(existsSync(STORE_KEY_PATH)).toBe(true);
378
+ });
379
+
380
+ test("migration is idempotent", () => {
381
+ // Create a v1 store
382
+ const salt = randomBytes(SALT_LENGTH);
383
+ const legacyKey = legacyDeriveKey(salt);
384
+ const entries: Record<string, { iv: string; tag: string; data: string }> =
385
+ {};
386
+ entries["my-key"] = legacyEncrypt("my-value", legacyKey);
387
+ writeV1Store(STORE_PATH, salt, entries);
388
+
389
+ // First access triggers migration
390
+ const val1 = getKey("my-key");
391
+ expect(val1).toBe("my-value");
392
+
393
+ // Second access should work the same (already migrated)
394
+ const val2 = getKey("my-key");
395
+ expect(val2).toBe("my-value");
396
+
397
+ // Should still be v2
398
+ const raw = readFileSync(STORE_PATH, "utf-8");
399
+ expect(JSON.parse(raw).version).toBe(2);
400
+ });
401
+
402
+ test("migration skips corrupt entries", () => {
403
+ // Create a v1 store with one good entry and one tampered entry
404
+ const salt = randomBytes(SALT_LENGTH);
405
+ const legacyKey = legacyDeriveKey(salt);
406
+ const entries: Record<string, { iv: string; tag: string; data: string }> =
407
+ {};
408
+ entries["good"] = legacyEncrypt("good-value", legacyKey);
409
+ entries["bad"] = legacyEncrypt("bad-value", legacyKey);
410
+ // Tamper with the bad entry
411
+ const badData = entries["bad"].data;
412
+ entries["bad"].data =
413
+ badData[0] === "0" ? "1" + badData.slice(1) : "0" + badData.slice(1);
414
+ writeV1Store(STORE_PATH, salt, entries);
415
+
416
+ // Trigger migration via getKey
417
+ const goodVal = getKey("good");
418
+ expect(goodVal).toBe("good-value");
419
+
420
+ // Bad entry should be gone (not migrated)
421
+ const badVal = getKey("bad");
422
+ expect(badVal).toBeUndefined();
423
+
424
+ // Store should be v2
425
+ const raw = readFileSync(STORE_PATH, "utf-8");
426
+ const parsed = JSON.parse(raw);
427
+ expect(parsed.version).toBe(2);
428
+ expect(parsed.entries["good"]).toBeDefined();
429
+ expect(parsed.entries["bad"]).toBeUndefined();
430
+ });
431
+
432
+ test("partial migration recovery", () => {
433
+ // Simulate crash between store.key write and store rewrite:
434
+ // write v1 store + store.key but leave store as v1
435
+ const salt = randomBytes(SALT_LENGTH);
436
+ const legacyKey = legacyDeriveKey(salt);
437
+ const entries: Record<string, { iv: string; tag: string; data: string }> =
438
+ {};
439
+ entries["test-key"] = legacyEncrypt("test-value", legacyKey);
440
+ writeV1Store(STORE_PATH, salt, entries);
441
+
442
+ // Pre-write store.key (simulating partial migration)
443
+ const preKey = randomBytes(32);
444
+ writeFileSync(STORE_KEY_PATH, preKey, { mode: 0o600 });
445
+
446
+ // Migration should complete using the existing store.key
447
+ const val = getKey("test-key");
448
+ expect(val).toBe("test-value");
449
+
450
+ // Verify the store.key was reused (not regenerated)
451
+ const afterKey = readFileSync(STORE_KEY_PATH);
452
+ expect(afterKey.equals(preKey)).toBe(true);
453
+
454
+ // Store should now be v2
455
+ const raw = readFileSync(STORE_PATH, "utf-8");
456
+ expect(JSON.parse(raw).version).toBe(2);
457
+ });
458
+ });
459
+
226
460
  // -----------------------------------------------------------------------
227
461
  // Error handling
228
462
  // -----------------------------------------------------------------------
@@ -273,7 +507,9 @@ describe("encrypted-store", () => {
273
507
  test("setKey creates directory if missing", () => {
274
508
  // Point to a path in a non-existent subdirectory
275
509
  const nestedPath = join(TEST_DIR, "sub", "dir", "keys.enc");
510
+ const nestedKeyPath = join(TEST_DIR, "sub", "dir", "store.key");
276
511
  _setStorePath(nestedPath);
512
+ _setStoreKeyPath(nestedKeyPath);
277
513
  const result = setKey("test", "value");
278
514
  expect(result).toBe(true);
279
515
  expect(getKey("test")).toBe("value");
@@ -311,7 +547,7 @@ describe("encrypted-store", () => {
311
547
  setKey("test", "value");
312
548
  // Loosen permissions
313
549
  chmodSync(STORE_PATH, 0o644);
314
- // Write again should re-enforce 0600
550
+ // Write again -- should re-enforce 0600
315
551
  setKey("test2", "value2");
316
552
  const mode = statSync(STORE_PATH).mode & 0o777;
317
553
  expect(mode).toBe(0o600);
@@ -352,16 +588,14 @@ describe("encrypted-store", () => {
352
588
  expect(getKey("__proto__")).toBeUndefined();
353
589
  });
354
590
 
355
- test("salt is preserved across set operations", () => {
591
+ test("store.key is reused across set operations", () => {
356
592
  setKey("key1", "val1");
357
- const raw1 = readFileSync(STORE_PATH, "utf-8");
358
- const salt1 = JSON.parse(raw1).salt;
593
+ const key1 = readFileSync(STORE_KEY_PATH);
359
594
 
360
595
  setKey("key2", "val2");
361
- const raw2 = readFileSync(STORE_PATH, "utf-8");
362
- const salt2 = JSON.parse(raw2).salt;
596
+ const key2 = readFileSync(STORE_KEY_PATH);
363
597
 
364
- expect(salt1).toBe(salt2);
598
+ expect(key1.equals(key2)).toBe(true);
365
599
  });
366
600
  });
367
601
  });
@@ -328,20 +328,19 @@ describe("ephemeral-permissions", () => {
328
328
  testConfig.permissions.mode = "workspace";
329
329
  });
330
330
 
331
- test("workspace mode prompts for workspace-scoped file_write (medium risk)", async () => {
331
+ test("workspace mode auto-allows workspace-scoped file_write (low risk)", async () => {
332
332
  const filePath = join(testDir, "workspace-test-file.txt");
333
333
  const result = await check("file_write", { path: filePath }, testDir);
334
- expect(result.decision).toBe("prompt");
335
- expect(result.reason).toContain("medium risk");
334
+ expect(result.decision).toBe("allow");
336
335
  });
337
336
 
338
- test("workspace mode still prompts for file_write outside workspace", async () => {
337
+ test("workspace mode auto-allows file_write outside workspace (low risk)", async () => {
339
338
  const result = await check(
340
339
  "file_write",
341
340
  { path: "/etc/config" },
342
341
  testDir,
343
342
  );
344
- expect(result.decision).toBe("prompt");
343
+ expect(result.decision).toBe("allow");
345
344
  });
346
345
 
347
346
  test("explicit deny rule overrides workspace mode auto-allow", async () => {