@vellumai/assistant 0.4.56 → 0.4.57

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 (450) 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 +185 -173
  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 +237 -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__/gateway-only-enforcement.test.ts +2 -2
  67. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  68. package/src/__tests__/gemini-image-service.test.ts +4 -4
  69. package/src/__tests__/gemini-provider.test.ts +6 -9
  70. package/src/__tests__/guardian-binding-drift-heal.test.ts +128 -0
  71. package/src/__tests__/guardian-dispatch.test.ts +0 -1
  72. package/src/__tests__/host-shell-tool.test.ts +6 -6
  73. package/src/__tests__/http-user-message-parity.test.ts +2 -2
  74. package/src/__tests__/intent-routing.test.ts +51 -99
  75. package/src/__tests__/invite-routes-http.test.ts +5 -0
  76. package/src/__tests__/list-messages-attachments.test.ts +1 -1
  77. package/src/__tests__/managed-proxy-context.test.ts +2 -5
  78. package/src/__tests__/managed-skill-lifecycle.test.ts +8 -8
  79. package/src/__tests__/media-generate-image.test.ts +32 -15
  80. package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
  81. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +1 -1
  82. package/src/__tests__/memory-lifecycle-e2e.test.ts +24 -18
  83. package/src/__tests__/memory-recall-quality.test.ts +4 -3
  84. package/src/__tests__/memory-regressions.test.ts +86 -90
  85. package/src/__tests__/migration-cross-version-compatibility.test.ts +32 -32
  86. package/src/__tests__/migration-export-http.test.ts +26 -27
  87. package/src/__tests__/migration-import-commit-http.test.ts +165 -37
  88. package/src/__tests__/migration-import-preflight-http.test.ts +81 -20
  89. package/src/__tests__/migration-validate-http.test.ts +16 -16
  90. package/src/__tests__/model-intents.test.ts +1 -1
  91. package/src/__tests__/no-domain-routing-in-prompt-guard.test.ts +1 -1
  92. package/src/__tests__/notification-broadcaster.test.ts +1 -1
  93. package/src/__tests__/notification-decision-fallback.test.ts +2 -2
  94. package/src/__tests__/notification-decision-identity.test.ts +8 -9
  95. package/src/__tests__/notification-decision-strategy.test.ts +1 -1
  96. package/src/__tests__/notification-deep-link.test.ts +1 -1
  97. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  98. package/src/__tests__/notification-schedule-dedup.test.ts +7 -7
  99. package/src/__tests__/oauth-store.test.ts +1 -3
  100. package/src/__tests__/oauth2-gateway-transport.test.ts +6 -1
  101. package/src/__tests__/onboarding-template-contract.test.ts +23 -59
  102. package/src/__tests__/provider-error-scenarios.test.ts +154 -0
  103. package/src/__tests__/provider-fail-open-selection.test.ts +2 -2
  104. package/src/__tests__/provider-managed-proxy-integration.test.ts +8 -9
  105. package/src/__tests__/provider-registry-ollama.test.ts +5 -2
  106. package/src/__tests__/qdrant-manager.test.ts +7 -7
  107. package/src/__tests__/ratelimit.test.ts +0 -74
  108. package/src/__tests__/recording-handler.test.ts +0 -1
  109. package/src/__tests__/require-fresh-approval.test.ts +1 -1
  110. package/src/__tests__/runtime-attachment-metadata.test.ts +1 -1
  111. package/src/__tests__/runtime-events-sse-parity.test.ts +1 -1
  112. package/src/__tests__/runtime-events-sse.test.ts +1 -1
  113. package/src/__tests__/scheduler-recurrence.test.ts +46 -2
  114. package/src/__tests__/schema-transforms.test.ts +114 -54
  115. package/src/__tests__/secret-onetime-send.test.ts +20 -0
  116. package/src/__tests__/secret-routes-managed-proxy.test.ts +5 -2
  117. package/src/__tests__/secret-scanner-executor.test.ts +1 -2
  118. package/src/__tests__/send-endpoint-busy.test.ts +63 -4
  119. package/src/__tests__/send-notification-tool.test.ts +2 -2
  120. package/src/__tests__/shell-credential-ref.test.ts +0 -1
  121. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -2
  122. package/src/__tests__/skill-memory.test.ts +547 -0
  123. package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -2
  124. package/src/__tests__/slack-app-setup-skill-regression.test.ts +37 -0
  125. package/src/__tests__/slack-channel-config.test.ts +109 -94
  126. package/src/__tests__/swarm-conversation-integration.test.ts +2 -2
  127. package/src/__tests__/swarm-recursion.test.ts +2 -2
  128. package/src/__tests__/swarm-tool.test.ts +2 -2
  129. package/src/__tests__/system-prompt.test.ts +19 -66
  130. package/src/__tests__/telegram-config.test.ts +121 -0
  131. package/src/__tests__/terminal-tools.test.ts +1 -1
  132. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -2
  133. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  134. package/src/__tests__/tool-executor-shell-integration.test.ts +1 -1
  135. package/src/__tests__/tool-executor.test.ts +1 -1
  136. package/src/__tests__/trace-emitter.test.ts +8 -1
  137. package/src/__tests__/trust-store.test.ts +7 -8
  138. package/src/__tests__/twilio-routes.test.ts +1 -18
  139. package/src/__tests__/user-reference.test.ts +82 -2
  140. package/src/__tests__/vbundle-pax-and-symlink.test.ts +196 -0
  141. package/src/__tests__/verification-control-plane-policy.test.ts +1 -1
  142. package/src/approvals/guardian-request-resolvers.ts +3 -3
  143. package/src/avatar/ascii-renderer.ts +2 -2
  144. package/src/avatar/png-renderer.ts +2 -2
  145. package/src/avatar/resvg-lazy.ts +21 -0
  146. package/src/calls/guardian-dispatch.ts +1 -1
  147. package/src/calls/relay-access-wait.ts +2 -2
  148. package/src/calls/twilio-rest.ts +0 -248
  149. package/src/cli/AGENTS.md +5 -8
  150. package/src/cli/__tests__/notifications.test.ts +5 -5
  151. package/src/cli/commands/avatar.ts +64 -2
  152. package/src/cli/commands/conversations.ts +131 -1
  153. package/src/cli/commands/credentials.ts +2 -0
  154. package/src/cli/commands/notifications.ts +3 -3
  155. package/src/cli.ts +10 -0
  156. package/src/config/bundled-skills/acp/SKILL.md +5 -5
  157. package/src/config/bundled-skills/acp/TOOLS.json +6 -6
  158. package/src/config/bundled-skills/app-builder/SKILL.md +42 -42
  159. package/src/config/bundled-skills/app-builder/TOOLS.json +10 -10
  160. package/src/config/bundled-skills/browser/SKILL.md +15 -15
  161. package/src/config/bundled-skills/browser/TOOLS.json +14 -14
  162. package/src/config/bundled-skills/chatgpt-import/SKILL.md +2 -2
  163. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +1 -1
  164. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
  165. package/src/config/bundled-skills/claude-code/SKILL.md +5 -5
  166. package/src/config/bundled-skills/computer-use/SKILL.md +2 -2
  167. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -15
  168. package/src/config/bundled-skills/contacts/SKILL.md +3 -3
  169. package/src/config/bundled-skills/contacts/TOOLS.json +4 -4
  170. package/src/config/bundled-skills/document/SKILL.md +4 -4
  171. package/src/config/bundled-skills/document/TOOLS.json +2 -2
  172. package/src/config/bundled-skills/followups/TOOLS.json +3 -3
  173. package/src/config/bundled-skills/gmail/SKILL.md +32 -32
  174. package/src/config/bundled-skills/gmail/TOOLS.json +16 -16
  175. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +1 -1
  176. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
  177. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -1
  178. package/src/config/bundled-skills/google-calendar/TOOLS.json +5 -5
  179. package/src/config/bundled-skills/google-calendar/types.ts +1 -1
  180. package/src/config/bundled-skills/heartbeat/SKILL.md +43 -0
  181. package/src/config/bundled-skills/image-studio/SKILL.md +3 -3
  182. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -3
  183. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +16 -12
  184. package/src/config/bundled-skills/media-processing/SKILL.md +40 -40
  185. package/src/config/bundled-skills/media-processing/TOOLS.json +8 -8
  186. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +2 -2
  187. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +1 -1
  188. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +5 -5
  189. package/src/config/bundled-skills/media-processing/services/gemini-video.ts +2 -2
  190. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -2
  191. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -2
  192. package/src/config/bundled-skills/media-processing/services/reduce.ts +3 -3
  193. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
  194. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +1 -1
  195. package/src/config/bundled-skills/messaging/SKILL.md +29 -25
  196. package/src/config/bundled-skills/messaging/TOOLS.json +11 -11
  197. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
  198. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  199. package/src/config/bundled-skills/notifications/SKILL.md +3 -3
  200. package/src/config/bundled-skills/notifications/TOOLS.json +2 -2
  201. package/src/config/bundled-skills/notifications/tools/send-notification.ts +3 -3
  202. package/src/config/bundled-skills/orchestration/SKILL.md +1 -1
  203. package/src/config/bundled-skills/orchestration/TOOLS.json +1 -1
  204. package/src/config/bundled-skills/phone-calls/SKILL.md +18 -14
  205. package/src/config/bundled-skills/phone-calls/TOOLS.json +3 -3
  206. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +2 -2
  207. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +2 -2
  208. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +1 -1
  209. package/src/config/bundled-skills/playbooks/TOOLS.json +4 -4
  210. package/src/config/bundled-skills/schedule/SKILL.md +26 -26
  211. package/src/config/bundled-skills/schedule/TOOLS.json +5 -5
  212. package/src/config/bundled-skills/screen-watch/SKILL.md +3 -3
  213. package/src/config/bundled-skills/screen-watch/TOOLS.json +1 -1
  214. package/src/config/bundled-skills/sequences/SKILL.md +2 -2
  215. package/src/config/bundled-skills/sequences/TOOLS.json +10 -10
  216. package/src/config/bundled-skills/sequences/tools/sequence-analytics.ts +2 -2
  217. package/src/config/bundled-skills/sequences/tools/sequence-enroll.ts +2 -2
  218. package/src/config/bundled-skills/sequences/tools/sequence-enrollment-list.ts +1 -1
  219. package/src/config/bundled-skills/sequences/tools/sequence-get.ts +1 -1
  220. package/src/config/bundled-skills/sequences/tools/sequence-import.ts +3 -3
  221. package/src/config/bundled-skills/sequences/tools/sequence-list.ts +1 -1
  222. package/src/config/bundled-skills/sequences/tools/sequence-update.ts +1 -1
  223. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  224. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -1
  225. package/src/config/bundled-skills/skill-management/TOOLS.json +5 -5
  226. package/src/config/bundled-skills/skills-catalog/SKILL.md +84 -0
  227. package/src/config/bundled-skills/slack/SKILL.md +2 -2
  228. package/src/config/bundled-skills/slack/TOOLS.json +8 -8
  229. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +3 -3
  230. package/src/config/bundled-skills/subagent/TOOLS.json +5 -5
  231. package/src/config/bundled-skills/tasks/SKILL.md +1 -1
  232. package/src/config/bundled-skills/tasks/TOOLS.json +9 -9
  233. package/src/config/bundled-skills/transcribe/SKILL.md +5 -5
  234. package/src/config/bundled-skills/transcribe/TOOLS.json +1 -1
  235. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +10 -10
  236. package/src/config/bundled-skills/watcher/SKILL.md +4 -4
  237. package/src/config/bundled-skills/watcher/TOOLS.json +5 -5
  238. package/src/config/feature-flag-registry.json +33 -17
  239. package/src/config/schemas/sandbox.ts +1 -1
  240. package/src/config/schemas/services.ts +13 -3
  241. package/src/config/schemas/timeouts.ts +0 -10
  242. package/src/contacts/contact-store.ts +63 -0
  243. package/src/contacts/contacts-write.ts +1 -1
  244. package/src/daemon/assistant-attachments.ts +2 -2
  245. package/src/daemon/conversation-agent-loop-handlers.ts +2 -2
  246. package/src/daemon/conversation-agent-loop.ts +7 -30
  247. package/src/daemon/conversation-error.ts +24 -0
  248. package/src/daemon/conversation-memory.ts +8 -7
  249. package/src/daemon/conversation-runtime-assembly.ts +139 -274
  250. package/src/daemon/conversation-slash.ts +7 -26
  251. package/src/daemon/conversation-surfaces.ts +14 -0
  252. package/src/daemon/conversation-tool-setup.ts +9 -8
  253. package/src/daemon/conversation.ts +2 -0
  254. package/src/daemon/daemon-control.ts +1 -1
  255. package/src/daemon/date-context.ts +10 -83
  256. package/src/daemon/handlers/config-channels.ts +12 -2
  257. package/src/daemon/handlers/config-slack-channel.ts +7 -1
  258. package/src/daemon/handlers/config-telegram.ts +6 -1
  259. package/src/daemon/handlers/conversations.ts +2 -2
  260. package/src/daemon/handlers/skills.ts +4 -0
  261. package/src/daemon/lifecycle.ts +28 -4
  262. package/src/daemon/providers-setup.ts +1 -1
  263. package/src/daemon/server.ts +1 -5
  264. package/src/daemon/shutdown-handlers.ts +9 -3
  265. package/src/daemon/tool-side-effects.ts +40 -0
  266. package/src/daemon/trace-emitter.ts +25 -2
  267. package/src/events/domain-events.ts +1 -1
  268. package/src/events/tool-permission-telemetry-listener.ts +46 -0
  269. package/src/inbound/platform-callback-registration.ts +0 -18
  270. package/src/media/app-icon-generator.ts +15 -8
  271. package/src/media/avatar-router.ts +15 -8
  272. package/src/media/gemini-image-service.ts +125 -21
  273. package/src/memory/attachments-store.ts +3 -3
  274. package/src/memory/channel-verification-sessions.ts +6 -6
  275. package/src/memory/conversation-crud.ts +196 -1
  276. package/src/memory/{thread-starters-cadence.ts → conversation-starters-cadence.ts} +9 -42
  277. package/src/memory/conversation-title-service.ts +2 -3
  278. package/src/memory/db-init.ts +25 -1
  279. package/src/memory/invite-store.ts +4 -4
  280. package/src/memory/items-extractor.ts +4 -4
  281. package/src/memory/job-handlers/{thread-starters.ts → conversation-starters.ts} +123 -38
  282. package/src/memory/jobs-store.ts +3 -2
  283. package/src/memory/jobs-worker.ts +7 -5
  284. package/src/memory/lifecycle-events-store.ts +63 -0
  285. package/src/memory/migrations/172-rename-created-by-session-id.ts +27 -0
  286. package/src/memory/migrations/173-rename-source-session-id.ts +16 -0
  287. package/src/memory/migrations/174-rename-thread-starters-table.ts +52 -0
  288. package/src/memory/migrations/175-create-lifecycle-events.ts +15 -0
  289. package/src/memory/migrations/176-drop-capability-card-state.ts +36 -0
  290. package/src/memory/migrations/177-create-trace-events-table.ts +40 -0
  291. package/src/memory/migrations/index.ts +6 -0
  292. package/src/memory/migrations/registry.ts +13 -0
  293. package/src/memory/retriever.test.ts +223 -96
  294. package/src/memory/retriever.ts +115 -138
  295. package/src/memory/schema/calls.ts +1 -1
  296. package/src/memory/schema/contacts.ts +1 -1
  297. package/src/memory/schema/infrastructure.ts +29 -0
  298. package/src/memory/schema/memory-core.ts +7 -17
  299. package/src/memory/schema/notifications.ts +1 -1
  300. package/src/memory/search/formatting.ts +23 -6
  301. package/src/memory/search/lexical.ts +2 -0
  302. package/src/memory/search/semantic.ts +2 -0
  303. package/src/memory/search/staleness.ts +1 -0
  304. package/src/memory/search/types.ts +4 -0
  305. package/src/memory/task-memory-cleanup.ts +96 -6
  306. package/src/memory/trace-event-store.ts +148 -0
  307. package/src/notifications/README.md +1 -1
  308. package/src/notifications/decision-engine.ts +2 -2
  309. package/src/notifications/emit-signal.ts +4 -4
  310. package/src/notifications/events-store.ts +4 -4
  311. package/src/notifications/signal.ts +1 -1
  312. package/src/oauth/manual-token-connection.ts +49 -25
  313. package/src/permissions/checker.ts +6 -5
  314. package/src/permissions/defaults.ts +4 -4
  315. package/src/prompts/__tests__/build-cli-reference-section.test.ts +9 -90
  316. package/src/prompts/cache-boundary.ts +8 -0
  317. package/src/prompts/system-prompt.ts +105 -634
  318. package/src/prompts/templates/BOOTSTRAP.md +166 -33
  319. package/src/prompts/templates/IDENTITY.md +8 -23
  320. package/src/prompts/templates/SOUL.md +20 -41
  321. package/src/prompts/templates/USER.md +3 -19
  322. package/src/prompts/user-reference.ts +14 -16
  323. package/src/providers/anthropic/client.ts +46 -2
  324. package/src/providers/gemini/client.ts +6 -9
  325. package/src/providers/managed-proxy/constants.ts +1 -7
  326. package/src/providers/managed-proxy/context.ts +0 -1
  327. package/src/providers/model-intents.ts +5 -5
  328. package/src/providers/openai/client.ts +10 -1
  329. package/src/providers/openrouter/client.ts +1 -0
  330. package/src/providers/ratelimit.ts +0 -35
  331. package/src/providers/registry.ts +3 -5
  332. package/src/providers/retry.ts +18 -1
  333. package/src/runtime/access-request-helper.ts +1 -1
  334. package/src/runtime/auth/route-policy.ts +7 -0
  335. package/src/runtime/channel-verification-service.ts +1 -1
  336. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  337. package/src/runtime/guardian-vellum-migration.ts +63 -1
  338. package/src/runtime/http-server.ts +8 -4
  339. package/src/runtime/migrations/vbundle-builder.ts +212 -32
  340. package/src/runtime/migrations/vbundle-import-analyzer.ts +74 -8
  341. package/src/runtime/migrations/vbundle-importer.ts +66 -1
  342. package/src/runtime/migrations/vbundle-validator.ts +17 -3
  343. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +4 -4
  344. package/src/runtime/routes/attachment-routes.ts +2 -2
  345. package/src/runtime/routes/btw-routes.ts +9 -0
  346. package/src/runtime/routes/channel-verification-routes.ts +19 -2
  347. package/src/runtime/routes/conversation-management-routes.ts +55 -1
  348. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  349. package/src/runtime/routes/conversation-routes.ts +49 -5
  350. package/src/runtime/routes/conversation-starter-routes.ts +207 -0
  351. package/src/runtime/routes/guardian-bootstrap-routes.ts +13 -9
  352. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -1
  353. package/src/runtime/routes/inbound-stages/verification-intercept.ts +1 -1
  354. package/src/runtime/routes/migration-routes.ts +25 -13
  355. package/src/runtime/routes/secret-routes.ts +18 -0
  356. package/src/runtime/routes/settings-routes.ts +8 -8
  357. package/src/runtime/routes/telemetry-routes.ts +53 -0
  358. package/src/runtime/routes/trace-event-routes.ts +62 -0
  359. package/src/runtime/tool-grant-request-helper.ts +1 -1
  360. package/src/runtime/verification-outbound-actions.ts +47 -31
  361. package/src/security/encrypted-store.ts +263 -78
  362. package/src/skills/catalog-install.ts +10 -0
  363. package/src/skills/managed-store.ts +2 -0
  364. package/src/skills/skill-memory.ts +220 -0
  365. package/src/subagent/manager.ts +1 -4
  366. package/src/telemetry/types.ts +10 -1
  367. package/src/telemetry/usage-telemetry-reporter.test.ts +1 -1
  368. package/src/telemetry/usage-telemetry-reporter.ts +51 -4
  369. package/src/tools/AGENTS.md +11 -11
  370. package/src/tools/acp/spawn.ts +1 -1
  371. package/src/tools/apps/executors.ts +8 -8
  372. package/src/tools/apps/registry.ts +1 -1
  373. package/src/tools/assets/materialize.ts +6 -6
  374. package/src/tools/assets/search.ts +10 -10
  375. package/src/tools/browser/__tests__/auth-cache.test.ts +2 -2
  376. package/src/tools/browser/__tests__/auth-detector.test.ts +4 -4
  377. package/src/tools/browser/auth-detector.ts +6 -6
  378. package/src/tools/browser/browser-execution.ts +13 -13
  379. package/src/tools/browser/browser-manager.ts +3 -3
  380. package/src/tools/browser/chrome-cdp.ts +5 -5
  381. package/src/tools/browser/jit-auth.ts +2 -2
  382. package/src/tools/browser/network-recorder.test.ts +2 -2
  383. package/src/tools/browser/network-recorder.ts +3 -3
  384. package/src/tools/browser/runtime-check.ts +3 -3
  385. package/src/tools/claude-code/claude-code.ts +2 -2
  386. package/src/tools/computer-use/definitions.ts +18 -18
  387. package/src/tools/credential-execution/make-authenticated-request.ts +4 -4
  388. package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -3
  389. package/src/tools/credential-execution/run-authenticated-command.ts +4 -4
  390. package/src/tools/credentials/broker-types.ts +5 -5
  391. package/src/tools/credentials/broker.ts +15 -15
  392. package/src/tools/credentials/metadata-store.ts +2 -2
  393. package/src/tools/credentials/resolve.ts +1 -1
  394. package/src/tools/credentials/selection.ts +1 -1
  395. package/src/tools/credentials/tool-policy.ts +1 -1
  396. package/src/tools/credentials/vault.ts +115 -25
  397. package/src/tools/execution-target.ts +2 -2
  398. package/src/tools/executor.ts +7 -7
  399. package/src/tools/filesystem/edit.ts +2 -2
  400. package/src/tools/filesystem/read.ts +1 -1
  401. package/src/tools/filesystem/write.ts +1 -1
  402. package/src/tools/host-filesystem/edit.ts +2 -1
  403. package/src/tools/host-filesystem/read.ts +2 -1
  404. package/src/tools/host-filesystem/write.ts +1 -1
  405. package/src/tools/host-terminal/host-shell.ts +9 -8
  406. package/src/tools/mcp/mcp-tool-factory.ts +7 -6
  407. package/src/tools/memory/definitions.ts +6 -5
  408. package/src/tools/memory/handlers.test.ts +1 -1
  409. package/src/tools/network/__tests__/web-search.test.ts +3 -3
  410. package/src/tools/network/domain-normalize.ts +2 -2
  411. package/src/tools/network/script-proxy/session-manager.ts +10 -10
  412. package/src/tools/network/web-fetch.ts +1 -1
  413. package/src/tools/network/web-search.ts +3 -3
  414. package/src/tools/permission-checker.ts +8 -8
  415. package/src/tools/registry.ts +7 -7
  416. package/src/tools/schedule/list.ts +2 -2
  417. package/src/tools/schema-transforms.ts +31 -21
  418. package/src/tools/secret-detection-handler.ts +1 -1
  419. package/src/tools/sensitive-output-placeholders.ts +1 -1
  420. package/src/tools/shared/filesystem/edit-engine.ts +1 -1
  421. package/src/tools/shared/filesystem/file-ops-service.ts +3 -3
  422. package/src/tools/shared/filesystem/image-read.ts +25 -5
  423. package/src/tools/shared/filesystem/path-policy.ts +2 -2
  424. package/src/tools/shared/shell-output.ts +1 -1
  425. package/src/tools/side-effects.ts +1 -1
  426. package/src/tools/skills/execute.ts +1 -1
  427. package/src/tools/skills/load.ts +3 -3
  428. package/src/tools/skills/sandbox-runner.ts +3 -3
  429. package/src/tools/subagent/read.ts +1 -1
  430. package/src/tools/subagent/spawn.ts +2 -2
  431. package/src/tools/swarm/delegate.ts +3 -3
  432. package/src/tools/system/request-permission.ts +5 -4
  433. package/src/tools/terminal/backends/native.ts +4 -4
  434. package/src/tools/terminal/parser.ts +6 -6
  435. package/src/tools/terminal/sandbox-diagnostics.ts +1 -1
  436. package/src/tools/terminal/shell.ts +16 -16
  437. package/src/tools/tool-approval-handler.ts +21 -12
  438. package/src/tools/tool-manifest.ts +4 -4
  439. package/src/tools/types.ts +3 -3
  440. package/src/tools/ui-surface/definitions.ts +9 -37
  441. package/src/tools/watcher/list.ts +1 -1
  442. package/src/util/logger.ts +7 -2
  443. package/src/util/retry.ts +29 -1
  444. package/src/workspace/migrations/007-web-search-provider-rename.ts +37 -0
  445. package/src/workspace/migrations/registry.ts +2 -0
  446. package/src/__tests__/cli-help-reference-sync.test.ts +0 -26
  447. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -190
  448. package/src/cli/reference.ts +0 -38
  449. package/src/memory/job-handlers/capability-cards.ts +0 -420
  450. 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,8 @@
1
- import { randomBytes } from "node:crypto";
1
+ import {
2
+ createCipheriv,
3
+ pbkdf2Sync,
4
+ randomBytes,
5
+ } from "node:crypto";
2
6
  import {
3
7
  chmodSync,
4
8
  existsSync,
@@ -7,9 +11,10 @@ import {
7
11
  readFileSync,
8
12
  rmSync,
9
13
  statSync,
14
+ unlinkSync,
10
15
  writeFileSync,
11
16
  } from "node:fs";
12
- import { tmpdir } from "node:os";
17
+ import { hostname, tmpdir, userInfo } from "node:os";
13
18
  import { join } from "node:path";
14
19
  import {
15
20
  afterAll,
@@ -23,7 +28,7 @@ import {
23
28
  } from "bun:test";
24
29
 
25
30
  // ---------------------------------------------------------------------------
26
- // Mock only the logger (not platform we use _setStorePath instead)
31
+ // Mock only the logger (not platform -- we use _setStorePath instead)
27
32
  // ---------------------------------------------------------------------------
28
33
 
29
34
  mock.module("../util/logger.js", () => ({
@@ -34,6 +39,7 @@ mock.module("../util/logger.js", () => ({
34
39
  }));
35
40
 
36
41
  import {
42
+ _setStoreKeyPath,
37
43
  _setStorePath,
38
44
  deleteKey,
39
45
  getKey,
@@ -50,6 +56,68 @@ const TEST_DIR = join(
50
56
  `vellum-enc-test-${randomBytes(4).toString("hex")}`,
51
57
  );
52
58
  const STORE_PATH = join(TEST_DIR, "keys.enc");
59
+ const STORE_KEY_PATH = join(TEST_DIR, "store.key");
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Legacy v1 helpers (for migration tests)
63
+ // ---------------------------------------------------------------------------
64
+
65
+ const ALGORITHM = "aes-256-gcm";
66
+ const KEY_LENGTH = 32;
67
+ const IV_LENGTH = 16;
68
+ const AUTH_TAG_LENGTH = 16;
69
+ const PBKDF2_ITERATIONS = process.env.BUN_TEST === "1" ? 1 : 100_000;
70
+ const SALT_LENGTH = 32;
71
+
72
+ /** Local copy of the legacy machine entropy derivation (the export was removed). */
73
+ function legacyMachineEntropy(): string {
74
+ const parts: string[] = [];
75
+ try { parts.push(hostname()); } catch { parts.push("unknown-host"); }
76
+ try { parts.push(userInfo().username); } catch { parts.push("unknown-user"); }
77
+ parts.push(process.platform);
78
+ parts.push(process.arch);
79
+ try { parts.push(userInfo().homedir); } catch { parts.push("/tmp"); }
80
+ return parts.join(":");
81
+ }
82
+
83
+ function legacyDeriveKey(salt: Buffer): Buffer {
84
+ const entropy = legacyMachineEntropy();
85
+ return pbkdf2Sync(entropy, salt, PBKDF2_ITERATIONS, KEY_LENGTH, "sha512");
86
+ }
87
+
88
+ function legacyEncrypt(
89
+ plaintext: string,
90
+ key: Buffer,
91
+ ): { iv: string; tag: string; data: string } {
92
+ const iv = randomBytes(IV_LENGTH);
93
+ const cipher = createCipheriv(ALGORITHM, key, iv, {
94
+ authTagLength: AUTH_TAG_LENGTH,
95
+ });
96
+ const encrypted = Buffer.concat([
97
+ cipher.update(plaintext, "utf-8"),
98
+ cipher.final(),
99
+ ]);
100
+ const tag = cipher.getAuthTag();
101
+ return {
102
+ iv: iv.toString("hex"),
103
+ tag: tag.toString("hex"),
104
+ data: encrypted.toString("hex"),
105
+ };
106
+ }
107
+
108
+ /** Write a v1 store file directly (bypassing the module's writeStore). */
109
+ function writeV1Store(
110
+ path: string,
111
+ salt: Buffer,
112
+ entries: Record<string, { iv: string; tag: string; data: string }>,
113
+ ): void {
114
+ const store = {
115
+ version: 1,
116
+ salt: salt.toString("hex"),
117
+ entries,
118
+ };
119
+ writeFileSync(path, JSON.stringify(store, null, 2), { mode: 0o600 });
120
+ }
53
121
 
54
122
  // ---------------------------------------------------------------------------
55
123
  // Tests
@@ -66,10 +134,12 @@ describe("encrypted-store", () => {
66
134
  rmSync(join(TEST_DIR, entry), { recursive: true, force: true });
67
135
  }
68
136
  _setStorePath(STORE_PATH);
137
+ _setStoreKeyPath(STORE_KEY_PATH);
69
138
  });
70
139
 
71
140
  afterEach(() => {
72
141
  _setStorePath(null);
142
+ _setStoreKeyPath(null);
73
143
  });
74
144
 
75
145
  afterAll(() => {
@@ -173,16 +243,15 @@ describe("encrypted-store", () => {
173
243
  });
174
244
 
175
245
  // -----------------------------------------------------------------------
176
- // Store format
246
+ // Store format (v2)
177
247
  // -----------------------------------------------------------------------
178
248
  describe("store format", () => {
179
- test("store file is valid JSON with version, salt, and entries", () => {
249
+ test("store file is valid JSON with version 2 and entries (no salt)", () => {
180
250
  setKey("test", "value");
181
251
  const raw = readFileSync(STORE_PATH, "utf-8");
182
252
  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
253
+ expect(parsed.version).toBe(2);
254
+ expect(parsed.salt).toBeUndefined();
186
255
  expect(typeof parsed.entries).toBe("object");
187
256
  expect(parsed.entries.test).toBeDefined();
188
257
  });
@@ -223,6 +292,159 @@ describe("encrypted-store", () => {
223
292
  });
224
293
  });
225
294
 
295
+ // -----------------------------------------------------------------------
296
+ // v2 format and store.key
297
+ // -----------------------------------------------------------------------
298
+ describe("v2 format and store.key", () => {
299
+ test("fresh store creates v2 format and store.key", () => {
300
+ setKey("test", "value");
301
+
302
+ // Verify keys.enc is v2 with no salt
303
+ const raw = readFileSync(STORE_PATH, "utf-8");
304
+ const parsed = JSON.parse(raw);
305
+ expect(parsed.version).toBe(2);
306
+ expect(parsed.salt).toBeUndefined();
307
+
308
+ // Verify store.key exists with exactly 32 bytes and 0o600 perms
309
+ expect(existsSync(STORE_KEY_PATH)).toBe(true);
310
+ const keyBuf = readFileSync(STORE_KEY_PATH);
311
+ expect(keyBuf.length).toBe(32);
312
+ const mode = statSync(STORE_KEY_PATH).mode & 0o777;
313
+ expect(mode).toBe(0o600);
314
+ });
315
+
316
+ test("v2 round-trip", () => {
317
+ // Set multiple values and read them back
318
+ setKey("key-a", "value-a");
319
+ setKey("key-b", "value-b");
320
+ setKey("key-c", "value-c");
321
+
322
+ expect(getKey("key-a")).toBe("value-a");
323
+ expect(getKey("key-b")).toBe("value-b");
324
+ expect(getKey("key-c")).toBe("value-c");
325
+
326
+ // Delete one and verify others remain
327
+ deleteKey("key-b");
328
+ expect(getKey("key-b")).toBeUndefined();
329
+ expect(getKey("key-a")).toBe("value-a");
330
+ expect(getKey("key-c")).toBe("value-c");
331
+ });
332
+
333
+ test("v2 store without store.key returns undefined", () => {
334
+ setKey("test", "value");
335
+ // Delete store.key
336
+ unlinkSync(STORE_KEY_PATH);
337
+ expect(getKey("test")).toBeUndefined();
338
+ });
339
+ });
340
+
341
+ // -----------------------------------------------------------------------
342
+ // v1 -> v2 migration
343
+ // -----------------------------------------------------------------------
344
+ describe("v1 to v2 migration", () => {
345
+ test("v1 to v2 migration preserves entries", () => {
346
+ // Create a v1 store using legacy encryption
347
+ const salt = randomBytes(SALT_LENGTH);
348
+ const legacyKey = legacyDeriveKey(salt);
349
+ const entries: Record<string, { iv: string; tag: string; data: string }> = {};
350
+ entries["api-key"] = legacyEncrypt("secret-123", legacyKey);
351
+ entries["other-key"] = legacyEncrypt("secret-456", legacyKey);
352
+ writeV1Store(STORE_PATH, salt, entries);
353
+
354
+ // Access via getKey -- should trigger migration
355
+ const val1 = getKey("api-key");
356
+ expect(val1).toBe("secret-123");
357
+
358
+ const val2 = getKey("other-key");
359
+ expect(val2).toBe("secret-456");
360
+
361
+ // Store should now be v2
362
+ const raw = readFileSync(STORE_PATH, "utf-8");
363
+ const parsed = JSON.parse(raw);
364
+ expect(parsed.version).toBe(2);
365
+ expect(parsed.salt).toBeUndefined();
366
+
367
+ // store.key should exist
368
+ expect(existsSync(STORE_KEY_PATH)).toBe(true);
369
+ });
370
+
371
+ test("migration is idempotent", () => {
372
+ // Create a v1 store
373
+ const salt = randomBytes(SALT_LENGTH);
374
+ const legacyKey = legacyDeriveKey(salt);
375
+ const entries: Record<string, { iv: string; tag: string; data: string }> = {};
376
+ entries["my-key"] = legacyEncrypt("my-value", legacyKey);
377
+ writeV1Store(STORE_PATH, salt, entries);
378
+
379
+ // First access triggers migration
380
+ const val1 = getKey("my-key");
381
+ expect(val1).toBe("my-value");
382
+
383
+ // Second access should work the same (already migrated)
384
+ const val2 = getKey("my-key");
385
+ expect(val2).toBe("my-value");
386
+
387
+ // Should still be v2
388
+ const raw = readFileSync(STORE_PATH, "utf-8");
389
+ expect(JSON.parse(raw).version).toBe(2);
390
+ });
391
+
392
+ test("migration skips corrupt entries", () => {
393
+ // Create a v1 store with one good entry and one tampered entry
394
+ const salt = randomBytes(SALT_LENGTH);
395
+ const legacyKey = legacyDeriveKey(salt);
396
+ const entries: Record<string, { iv: string; tag: string; data: string }> = {};
397
+ entries["good"] = legacyEncrypt("good-value", legacyKey);
398
+ entries["bad"] = legacyEncrypt("bad-value", legacyKey);
399
+ // Tamper with the bad entry
400
+ const badData = entries["bad"].data;
401
+ entries["bad"].data =
402
+ badData[0] === "0" ? "1" + badData.slice(1) : "0" + badData.slice(1);
403
+ writeV1Store(STORE_PATH, salt, entries);
404
+
405
+ // Trigger migration via getKey
406
+ const goodVal = getKey("good");
407
+ expect(goodVal).toBe("good-value");
408
+
409
+ // Bad entry should be gone (not migrated)
410
+ const badVal = getKey("bad");
411
+ expect(badVal).toBeUndefined();
412
+
413
+ // Store should be v2
414
+ const raw = readFileSync(STORE_PATH, "utf-8");
415
+ const parsed = JSON.parse(raw);
416
+ expect(parsed.version).toBe(2);
417
+ expect(parsed.entries["good"]).toBeDefined();
418
+ expect(parsed.entries["bad"]).toBeUndefined();
419
+ });
420
+
421
+ test("partial migration recovery", () => {
422
+ // Simulate crash between store.key write and store rewrite:
423
+ // write v1 store + store.key but leave store as v1
424
+ const salt = randomBytes(SALT_LENGTH);
425
+ const legacyKey = legacyDeriveKey(salt);
426
+ const entries: Record<string, { iv: string; tag: string; data: string }> = {};
427
+ entries["test-key"] = legacyEncrypt("test-value", legacyKey);
428
+ writeV1Store(STORE_PATH, salt, entries);
429
+
430
+ // Pre-write store.key (simulating partial migration)
431
+ const preKey = randomBytes(32);
432
+ writeFileSync(STORE_KEY_PATH, preKey, { mode: 0o600 });
433
+
434
+ // Migration should complete using the existing store.key
435
+ const val = getKey("test-key");
436
+ expect(val).toBe("test-value");
437
+
438
+ // Verify the store.key was reused (not regenerated)
439
+ const afterKey = readFileSync(STORE_KEY_PATH);
440
+ expect(afterKey.equals(preKey)).toBe(true);
441
+
442
+ // Store should now be v2
443
+ const raw = readFileSync(STORE_PATH, "utf-8");
444
+ expect(JSON.parse(raw).version).toBe(2);
445
+ });
446
+ });
447
+
226
448
  // -----------------------------------------------------------------------
227
449
  // Error handling
228
450
  // -----------------------------------------------------------------------
@@ -273,7 +495,9 @@ describe("encrypted-store", () => {
273
495
  test("setKey creates directory if missing", () => {
274
496
  // Point to a path in a non-existent subdirectory
275
497
  const nestedPath = join(TEST_DIR, "sub", "dir", "keys.enc");
498
+ const nestedKeyPath = join(TEST_DIR, "sub", "dir", "store.key");
276
499
  _setStorePath(nestedPath);
500
+ _setStoreKeyPath(nestedKeyPath);
277
501
  const result = setKey("test", "value");
278
502
  expect(result).toBe(true);
279
503
  expect(getKey("test")).toBe("value");
@@ -311,7 +535,7 @@ describe("encrypted-store", () => {
311
535
  setKey("test", "value");
312
536
  // Loosen permissions
313
537
  chmodSync(STORE_PATH, 0o644);
314
- // Write again should re-enforce 0600
538
+ // Write again -- should re-enforce 0600
315
539
  setKey("test2", "value2");
316
540
  const mode = statSync(STORE_PATH).mode & 0o777;
317
541
  expect(mode).toBe(0o600);
@@ -352,16 +576,14 @@ describe("encrypted-store", () => {
352
576
  expect(getKey("__proto__")).toBeUndefined();
353
577
  });
354
578
 
355
- test("salt is preserved across set operations", () => {
579
+ test("store.key is reused across set operations", () => {
356
580
  setKey("key1", "val1");
357
- const raw1 = readFileSync(STORE_PATH, "utf-8");
358
- const salt1 = JSON.parse(raw1).salt;
581
+ const key1 = readFileSync(STORE_KEY_PATH);
359
582
 
360
583
  setKey("key2", "val2");
361
- const raw2 = readFileSync(STORE_PATH, "utf-8");
362
- const salt2 = JSON.parse(raw2).salt;
584
+ const key2 = readFileSync(STORE_KEY_PATH);
363
585
 
364
- expect(salt1).toBe(salt2);
586
+ expect(key1.equals(key2)).toBe(true);
365
587
  });
366
588
  });
367
589
  });
@@ -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 () => {
@@ -33,18 +33,18 @@ describe("EventBus", () => {
33
33
 
34
34
  bus.onAny((event) => {
35
35
  seenType = event.type;
36
- if (event.type === "daemon.session.created") {
36
+ if (event.type === "daemon.conversation.created") {
37
37
  seenConversationId = event.payload.conversationId;
38
38
  }
39
39
  expect(typeof event.emittedAtMs).toBe("number");
40
40
  });
41
41
 
42
- await bus.emit("daemon.session.created", {
42
+ await bus.emit("daemon.conversation.created", {
43
43
  conversationId: "conv-2",
44
44
  createdAtMs: Date.now(),
45
45
  });
46
46
 
47
- expect(seenType).toBe("daemon.session.created");
47
+ expect(seenType).toBe("daemon.conversation.created");
48
48
  expect(seenConversationId).toBe("conv-2");
49
49
  });
50
50