@vellumai/assistant 0.6.3 → 0.6.4

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 (667) hide show
  1. package/ARCHITECTURE.md +273 -10
  2. package/Dockerfile +2 -3
  3. package/bun.lock +5 -13
  4. package/docs/backup-troubleshooting.md +52 -0
  5. package/docs/browser-use-architecture-phase2.md +174 -0
  6. package/docs/stt-provider-onboarding.md +120 -0
  7. package/knip.json +12 -2
  8. package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
  9. package/node_modules/@vellumai/ces-contracts/package.json +3 -3
  10. package/openapi.yaml +982 -72
  11. package/package.json +4 -6
  12. package/scripts/generate-openapi.ts +0 -1
  13. package/scripts/test.sh +73 -18
  14. package/src/__tests__/agent-image-optimize.test.ts +28 -0
  15. package/src/__tests__/agent-loop.test.ts +123 -0
  16. package/src/__tests__/anthropic-provider.test.ts +263 -10
  17. package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
  18. package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
  19. package/src/__tests__/browser-fill-credential.test.ts +11 -0
  20. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  21. package/src/__tests__/browser-skill-endstate.test.ts +31 -7
  22. package/src/__tests__/btw-routes.test.ts +7 -0
  23. package/src/__tests__/call-controller.test.ts +581 -20
  24. package/src/__tests__/catalog-files.test.ts +138 -0
  25. package/src/__tests__/channel-invite-transport.test.ts +2 -2
  26. package/src/__tests__/channel-readiness-routes.test.ts +16 -20
  27. package/src/__tests__/channel-readiness-service.test.ts +12 -7
  28. package/src/__tests__/checker.test.ts +157 -10
  29. package/src/__tests__/clawhub-files.test.ts +347 -0
  30. package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
  31. package/src/__tests__/config-analysis.test.ts +100 -0
  32. package/src/__tests__/config-schema.test.ts +1013 -66
  33. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
  34. package/src/__tests__/config-watcher.test.ts +43 -8
  35. package/src/__tests__/contact-store-user-file.test.ts +512 -0
  36. package/src/__tests__/contacts-write.test.ts +197 -0
  37. package/src/__tests__/context-window-manager.test.ts +88 -0
  38. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  39. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  40. package/src/__tests__/conversation-agent-loop.test.ts +98 -2
  41. package/src/__tests__/conversation-confirmation-signals.test.ts +135 -0
  42. package/src/__tests__/conversation-error.test.ts +70 -0
  43. package/src/__tests__/conversation-history-web-search.test.ts +11 -4
  44. package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
  45. package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
  46. package/src/__tests__/conversation-list-source.test.ts +145 -0
  47. package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
  48. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  49. package/src/__tests__/conversation-queue.test.ts +901 -60
  50. package/src/__tests__/conversation-routes-disk-view.test.ts +270 -0
  51. package/src/__tests__/conversation-runtime-assembly.test.ts +55 -0
  52. package/src/__tests__/conversation-skill-tools.test.ts +7 -4
  53. package/src/__tests__/conversation-slash-commands.test.ts +33 -0
  54. package/src/__tests__/conversation-slash-queue.test.ts +89 -18
  55. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  56. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
  57. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  59. package/src/__tests__/credential-health-service.test.ts +352 -0
  60. package/src/__tests__/credential-security-invariants.test.ts +5 -3
  61. package/src/__tests__/credential-vault-unit.test.ts +379 -3
  62. package/src/__tests__/credentials-cli.test.ts +40 -16
  63. package/src/__tests__/cross-provider-web-search.test.ts +146 -35
  64. package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
  65. package/src/__tests__/device-id.test.ts +112 -0
  66. package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
  67. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
  68. package/src/__tests__/email-html-renderer.test.ts +71 -0
  69. package/src/__tests__/email-invite-adapter.test.ts +36 -32
  70. package/src/__tests__/emit-event-signal.test.ts +71 -0
  71. package/src/__tests__/extension-id-sync-guard.test.ts +75 -8
  72. package/src/__tests__/fixtures/mock-chrome-extension.ts +11 -0
  73. package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
  74. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  75. package/src/__tests__/gemini-provider.test.ts +64 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
  77. package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
  78. package/src/__tests__/gmail-archive-gate.test.ts +246 -0
  79. package/src/__tests__/gmail-preferences.test.ts +117 -0
  80. package/src/__tests__/headless-browser-interactions.test.ts +43 -0
  81. package/src/__tests__/headless-browser-mode.test.ts +614 -0
  82. package/src/__tests__/headless-browser-navigate.test.ts +142 -5
  83. package/src/__tests__/headless-browser-read-tools.test.ts +11 -0
  84. package/src/__tests__/headless-browser-snapshot.test.ts +10 -0
  85. package/src/__tests__/heartbeat-service.test.ts +70 -17
  86. package/src/__tests__/home-state-routes.test.ts +162 -0
  87. package/src/__tests__/host-bash-proxy.test.ts +0 -5
  88. package/src/__tests__/host-browser-e2e-cloud.test.ts +138 -4
  89. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +4 -4
  90. package/src/__tests__/host-browser-ws-events-e2e.test.ts +103 -0
  91. package/src/__tests__/host-cu-proxy.test.ts +0 -5
  92. package/src/__tests__/identity-intro-cache.test.ts +40 -10
  93. package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
  94. package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
  95. package/src/__tests__/llm-context-normalization.test.ts +488 -0
  96. package/src/__tests__/llm-context-route-provider.test.ts +86 -5
  97. package/src/__tests__/llm-usage-store.test.ts +363 -0
  98. package/src/__tests__/media-stream-output.test.ts +555 -0
  99. package/src/__tests__/media-stream-parser.test.ts +374 -0
  100. package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
  101. package/src/__tests__/media-stream-stt-session.test.ts +588 -0
  102. package/src/__tests__/media-turn-detector.test.ts +440 -0
  103. package/src/__tests__/message-queue.test.ts +125 -0
  104. package/src/__tests__/migration-export-http.test.ts +6 -6
  105. package/src/__tests__/migration-import-commit-http.test.ts +8 -6
  106. package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
  107. package/src/__tests__/migration-validate-http.test.ts +3 -3
  108. package/src/__tests__/mock-gateway-ipc.ts +151 -0
  109. package/src/__tests__/model-intents.test.ts +2 -2
  110. package/src/__tests__/oauth-apps-routes.test.ts +1 -0
  111. package/src/__tests__/oauth-cli.test.ts +2 -0
  112. package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
  113. package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
  114. package/src/__tests__/oauth-providers-routes.test.ts +2 -0
  115. package/src/__tests__/oauth-store.test.ts +85 -0
  116. package/src/__tests__/oauth2-gateway-transport.test.ts +249 -6
  117. package/src/__tests__/onboarding-template-contract.test.ts +6 -13
  118. package/src/__tests__/openai-provider.test.ts +176 -0
  119. package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
  120. package/src/__tests__/openai-responses-provider.test.ts +1105 -0
  121. package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
  122. package/src/__tests__/outlook-unsubscribe.test.ts +31 -2
  123. package/src/__tests__/persona-resolver.test.ts +251 -0
  124. package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
  125. package/src/__tests__/platform.test.ts +92 -1
  126. package/src/__tests__/post-turn-tool-result-truncation.test.ts +47 -0
  127. package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
  128. package/src/__tests__/pricing.test.ts +174 -0
  129. package/src/__tests__/qdrant-manager.test.ts +29 -8
  130. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
  131. package/src/__tests__/relationship-state-contract.test.ts +175 -0
  132. package/src/__tests__/relay-server.test.ts +423 -5
  133. package/src/__tests__/search-skills-unified.test.ts +118 -0
  134. package/src/__tests__/secret-scanner-executor.test.ts +4 -0
  135. package/src/__tests__/secure-keys.test.ts +107 -0
  136. package/src/__tests__/send-endpoint-busy.test.ts +5 -1
  137. package/src/__tests__/sequence-store.test.ts +1 -1
  138. package/src/__tests__/server-history-render.test.ts +49 -0
  139. package/src/__tests__/settings-routes.test.ts +201 -0
  140. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  141. package/src/__tests__/skills-file-content-endpoint.test.ts +276 -145
  142. package/src/__tests__/skills-files-catalog-fallback.test.ts +381 -93
  143. package/src/__tests__/skills.test.ts +5 -2
  144. package/src/__tests__/skillssh-files.test.ts +446 -0
  145. package/src/__tests__/slack-block-formatting.test.ts +110 -0
  146. package/src/__tests__/slack-channel-config.test.ts +564 -1
  147. package/src/__tests__/stt-catalog-parity.test.ts +282 -0
  148. package/src/__tests__/stt-stream-session.test.ts +535 -0
  149. package/src/__tests__/system-prompt.test.ts +112 -26
  150. package/src/__tests__/telephony-stt-routing.test.ts +329 -0
  151. package/src/__tests__/terminal-tools.test.ts +18 -7
  152. package/src/__tests__/test-preload.ts +18 -0
  153. package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
  154. package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
  155. package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
  156. package/src/__tests__/tool-executor.test.ts +33 -24
  157. package/src/__tests__/tool-result-truncation.test.ts +36 -0
  158. package/src/__tests__/trust-store.test.ts +7 -1
  159. package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
  160. package/src/__tests__/tts-catalog-parity.test.ts +345 -0
  161. package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
  162. package/src/__tests__/twilio-routes.test.ts +376 -0
  163. package/src/__tests__/unicode.test.ts +293 -0
  164. package/src/__tests__/update-bulletin-format.test.ts +59 -0
  165. package/src/__tests__/update-bulletin.test.ts +206 -5
  166. package/src/__tests__/usage-routes.test.ts +25 -4
  167. package/src/__tests__/user-reference.test.ts +46 -61
  168. package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
  169. package/src/__tests__/voice-config-update.test.ts +403 -0
  170. package/src/__tests__/voice-quality.test.ts +434 -19
  171. package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
  172. package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
  173. package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
  174. package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
  175. package/src/__tests__/workspace-migration-meets.test.ts +244 -0
  176. package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
  177. package/src/__tests__/workspace-policy.test.ts +2 -0
  178. package/src/agent/image-optimize.ts +24 -12
  179. package/src/agent/loop.ts +43 -3
  180. package/src/backup/__tests__/backup-key.test.ts +152 -0
  181. package/src/backup/__tests__/backup-worker.test.ts +767 -0
  182. package/src/backup/__tests__/list-snapshots.test.ts +87 -0
  183. package/src/backup/__tests__/local-writer.test.ts +218 -0
  184. package/src/backup/__tests__/offsite-writer.test.ts +641 -0
  185. package/src/backup/__tests__/paths.test.ts +300 -0
  186. package/src/backup/__tests__/restore.test.ts +498 -0
  187. package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
  188. package/src/backup/__tests__/stream-crypt.test.ts +228 -0
  189. package/src/backup/backup-key.ts +137 -0
  190. package/src/backup/backup-worker.ts +459 -0
  191. package/src/backup/list-snapshots.ts +147 -0
  192. package/src/backup/local-writer.ts +133 -0
  193. package/src/backup/offsite-writer.ts +222 -0
  194. package/src/backup/paths.ts +226 -0
  195. package/src/backup/restore.ts +322 -0
  196. package/src/backup/snapshot-lock.ts +431 -0
  197. package/src/backup/stream-crypt.ts +263 -0
  198. package/src/bundler/package-resolver.ts +4 -0
  199. package/src/calls/audio-store.ts +11 -5
  200. package/src/calls/call-controller.ts +226 -71
  201. package/src/calls/call-domain.ts +9 -0
  202. package/src/calls/call-speech-output.ts +190 -0
  203. package/src/calls/call-transport.ts +77 -0
  204. package/src/calls/media-stream-audio-transcode.ts +173 -0
  205. package/src/calls/media-stream-output.ts +660 -0
  206. package/src/calls/media-stream-parser.ts +300 -0
  207. package/src/calls/media-stream-protocol.ts +166 -0
  208. package/src/calls/media-stream-server.ts +592 -0
  209. package/src/calls/media-stream-stt-session.ts +460 -0
  210. package/src/calls/media-turn-detector.ts +230 -0
  211. package/src/calls/relay-server.ts +90 -75
  212. package/src/calls/resolve-call-tts-provider.ts +136 -0
  213. package/src/calls/telephony-stt-routing.ts +145 -0
  214. package/src/calls/tts-call-strategy.ts +161 -0
  215. package/src/calls/tts-text-sanitizer.ts +32 -16
  216. package/src/calls/twilio-routes.ts +281 -17
  217. package/src/calls/voice-quality.ts +78 -35
  218. package/src/calls/voice-session-bridge.ts +8 -1
  219. package/src/channels/types.ts +16 -0
  220. package/src/cli/__tests__/run-assistant-command.ts +11 -1
  221. package/src/cli/commands/__tests__/backup.test.ts +1165 -0
  222. package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
  223. package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
  224. package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
  225. package/src/cli/commands/__tests__/email-download.test.ts +16 -1
  226. package/src/cli/commands/__tests__/email-list.test.ts +22 -4
  227. package/src/cli/commands/__tests__/email-register.test.ts +4 -4
  228. package/src/cli/commands/__tests__/email-send.test.ts +37 -4
  229. package/src/cli/commands/__tests__/email-status.test.ts +5 -1
  230. package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
  231. package/src/cli/commands/backup.ts +993 -0
  232. package/src/cli/commands/conversations.ts +77 -0
  233. package/src/cli/commands/credentials.ts +0 -1
  234. package/src/cli/commands/domain.ts +210 -0
  235. package/src/cli/commands/email.ts +255 -3
  236. package/src/cli/commands/oauth/__tests__/connect.test.ts +12 -0
  237. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +1 -0
  238. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -0
  239. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -0
  240. package/src/cli/commands/oauth/mode.ts +12 -3
  241. package/src/cli/commands/oauth/providers.ts +15 -0
  242. package/src/cli/commands/oauth/shared.ts +2 -1
  243. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +4 -9
  244. package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
  245. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  246. package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
  247. package/src/cli/program.ts +30 -4
  248. package/src/config/__tests__/backup-schema.test.ts +134 -0
  249. package/src/config/assistant-feature-flags.ts +61 -62
  250. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +37 -1
  251. package/src/config/bundled-skills/browser/SKILL.md +30 -5
  252. package/src/config/bundled-skills/browser/TOOLS.json +123 -0
  253. package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
  254. package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
  255. package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
  256. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
  257. package/src/config/bundled-skills/contacts/SKILL.md +2 -2
  258. package/src/config/bundled-skills/gmail/SKILL.md +53 -7
  259. package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
  260. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
  261. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
  262. package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
  263. package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
  264. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
  265. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
  266. package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
  267. package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
  268. package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
  269. package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
  270. package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
  271. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
  272. package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
  273. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
  274. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  275. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
  276. package/src/config/bundled-skills/outlook/SKILL.md +2 -2
  277. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
  278. package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
  279. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
  280. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
  281. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  282. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
  283. package/src/config/bundled-skills/slack/SKILL.md +1 -0
  284. package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
  285. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
  286. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
  287. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
  288. package/src/config/bundled-tool-registry.ts +8 -0
  289. package/src/config/env-registry.ts +24 -0
  290. package/src/config/env.ts +34 -10
  291. package/src/config/feature-flag-registry.json +46 -14
  292. package/src/config/loader.ts +26 -12
  293. package/src/config/schema.ts +35 -10
  294. package/src/config/schemas/__tests__/stt.test.ts +43 -0
  295. package/src/config/schemas/analysis.ts +51 -0
  296. package/src/config/schemas/backup.ts +72 -0
  297. package/src/config/schemas/calls.ts +1 -26
  298. package/src/config/schemas/elevenlabs.ts +0 -59
  299. package/src/config/schemas/filing.ts +47 -7
  300. package/src/config/schemas/heartbeat.ts +27 -5
  301. package/src/config/schemas/host-browser.ts +47 -1
  302. package/src/config/schemas/inference.ts +1 -1
  303. package/src/config/schemas/memory-lifecycle.ts +14 -2
  304. package/src/config/schemas/services.ts +44 -0
  305. package/src/config/schemas/stt.ts +59 -0
  306. package/src/config/schemas/tts.ts +230 -0
  307. package/src/config/schemas/updates.ts +14 -0
  308. package/src/config/skills.ts +4 -0
  309. package/src/config/types.ts +4 -0
  310. package/src/contacts/contact-store.ts +56 -11
  311. package/src/contacts/contacts-write.ts +38 -1
  312. package/src/context/post-turn-tool-result-truncation.ts +3 -2
  313. package/src/context/tool-result-truncation.ts +2 -1
  314. package/src/context/window-manager.ts +45 -12
  315. package/src/credential-execution/executable-discovery.ts +12 -2
  316. package/src/credential-execution/process-manager.ts +33 -2
  317. package/src/credential-health/credential-health-service.ts +366 -0
  318. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
  319. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
  320. package/src/daemon/__tests__/conversation-tool-setup.test.ts +17 -8
  321. package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
  322. package/src/daemon/config-watcher.ts +99 -5
  323. package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
  324. package/src/daemon/conversation-agent-loop.ts +101 -24
  325. package/src/daemon/conversation-error.ts +11 -0
  326. package/src/daemon/conversation-history.ts +40 -6
  327. package/src/daemon/conversation-launch.ts +220 -0
  328. package/src/daemon/conversation-lifecycle.ts +59 -9
  329. package/src/daemon/conversation-messaging.ts +37 -3
  330. package/src/daemon/conversation-notifiers.ts +5 -0
  331. package/src/daemon/conversation-process.ts +581 -19
  332. package/src/daemon/conversation-queue-manager.ts +24 -0
  333. package/src/daemon/conversation-runtime-assembly.ts +11 -1
  334. package/src/daemon/conversation-slash.ts +36 -0
  335. package/src/daemon/conversation-surfaces.ts +94 -4
  336. package/src/daemon/conversation-tool-setup.ts +25 -0
  337. package/src/daemon/conversation-usage.ts +7 -4
  338. package/src/daemon/conversation.ts +86 -28
  339. package/src/daemon/handlers/config-slack-channel.ts +269 -94
  340. package/src/daemon/handlers/conversations.ts +4 -1
  341. package/src/daemon/handlers/shared.ts +22 -0
  342. package/src/daemon/handlers/skills.ts +321 -77
  343. package/src/daemon/host-browser-proxy.ts +2 -1
  344. package/src/daemon/lifecycle.ts +122 -25
  345. package/src/daemon/message-protocol.ts +6 -0
  346. package/src/daemon/message-types/conversations.ts +34 -1
  347. package/src/daemon/message-types/home.ts +40 -0
  348. package/src/daemon/message-types/meet.ts +143 -0
  349. package/src/daemon/message-types/messages.ts +14 -0
  350. package/src/daemon/message-types/schedules.ts +34 -2
  351. package/src/daemon/message-types/skills.ts +16 -0
  352. package/src/daemon/message-types/surfaces.ts +2 -0
  353. package/src/daemon/server.ts +347 -2
  354. package/src/daemon/shutdown-handlers.ts +32 -4
  355. package/src/daemon/shutdown-registry.ts +40 -0
  356. package/src/daemon/tool-side-effects.ts +9 -0
  357. package/src/email/html-renderer.ts +76 -0
  358. package/src/heartbeat/heartbeat-service.ts +93 -7
  359. package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
  360. package/src/home/__tests__/emit-feed-event.test.ts +169 -0
  361. package/src/home/__tests__/feed-scheduler.test.ts +194 -0
  362. package/src/home/__tests__/feed-types.test.ts +275 -0
  363. package/src/home/__tests__/feed-writer.test.ts +688 -0
  364. package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
  365. package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
  366. package/src/home/__tests__/progress-formula.test.ts +213 -0
  367. package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
  368. package/src/home/__tests__/rollup-producer.test.ts +398 -0
  369. package/src/home/assistant-feed-authoring.ts +124 -0
  370. package/src/home/emit-feed-event.ts +158 -0
  371. package/src/home/feed-scheduler.ts +247 -0
  372. package/src/home/feed-types.ts +181 -0
  373. package/src/home/feed-writer.ts +469 -0
  374. package/src/home/platform-gmail-digest.ts +163 -0
  375. package/src/home/progress-formula.ts +86 -0
  376. package/src/home/relationship-state-writer.ts +824 -0
  377. package/src/home/relationship-state.ts +143 -0
  378. package/src/home/rollup-producer.ts +384 -0
  379. package/src/hooks/runner.ts +7 -0
  380. package/src/inbound/platform-callback-registration.ts +12 -3
  381. package/src/inbound/public-ingress-urls.ts +12 -0
  382. package/src/instrument.ts +1 -1
  383. package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
  384. package/src/ipc/cli-client.ts +151 -0
  385. package/src/ipc/cli-server.ts +234 -0
  386. package/src/ipc/gateway-client.ts +180 -0
  387. package/src/ipc/routes/index.ts +5 -0
  388. package/src/ipc/routes/wake-conversation.ts +19 -0
  389. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
  390. package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
  391. package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
  392. package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
  393. package/src/memory/app-store.ts +1 -1
  394. package/src/memory/attachments-store.ts +70 -0
  395. package/src/memory/auto-analysis-enqueue.ts +127 -0
  396. package/src/memory/auto-analysis-guard.ts +27 -0
  397. package/src/memory/cleanup-schedule-state.ts +37 -0
  398. package/src/memory/conversation-analyze-job.ts +73 -0
  399. package/src/memory/conversation-crud.ts +99 -0
  400. package/src/memory/conversation-disk-view.ts +7 -0
  401. package/src/memory/conversation-group-migration.ts +34 -2
  402. package/src/memory/conversation-queries.ts +6 -5
  403. package/src/memory/db-init.ts +6 -0
  404. package/src/memory/db-maintenance.ts +108 -0
  405. package/src/memory/db.ts +1 -0
  406. package/src/memory/graph/conversation-graph-memory.ts +15 -0
  407. package/src/memory/graph/extraction.test.ts +23 -0
  408. package/src/memory/graph/extraction.ts +8 -0
  409. package/src/memory/graph/retriever.ts +27 -18
  410. package/src/memory/graph/scoring.test.ts +186 -0
  411. package/src/memory/graph/scoring.ts +31 -1
  412. package/src/memory/graph/tools.ts +1 -1
  413. package/src/memory/group-crud.ts +6 -1
  414. package/src/memory/indexer.ts +95 -16
  415. package/src/memory/job-handlers/cleanup.ts +11 -8
  416. package/src/memory/job-handlers/conversation-starters.ts +16 -10
  417. package/src/memory/jobs-store.ts +64 -4
  418. package/src/memory/jobs-worker.ts +22 -9
  419. package/src/memory/llm-usage-store.ts +92 -56
  420. package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
  421. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
  422. package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
  423. package/src/memory/migrations/index.ts +6 -0
  424. package/src/memory/migrations/registry.ts +8 -0
  425. package/src/memory/qdrant-manager.ts +43 -16
  426. package/src/memory/schema/conversations.ts +2 -0
  427. package/src/memory/schema/oauth.ts +3 -0
  428. package/src/memory/usage-buckets.ts +396 -0
  429. package/src/messaging/providers/gmail/client.ts +57 -6
  430. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
  431. package/src/messaging/providers/slack/adapter.ts +143 -38
  432. package/src/messaging/providers/slack/client.ts +16 -0
  433. package/src/messaging/providers/slack/types.ts +4 -0
  434. package/src/notifications/decision-engine.ts +3 -3
  435. package/src/notifications/signal.ts +5 -0
  436. package/src/oauth/__tests__/identity-verifier.test.ts +1 -0
  437. package/src/oauth/byo-connection.test.ts +18 -1
  438. package/src/oauth/byo-connection.ts +3 -1
  439. package/src/oauth/connect-orchestrator.ts +2 -0
  440. package/src/oauth/connection-resolver.ts +6 -2
  441. package/src/oauth/connection.ts +2 -0
  442. package/src/oauth/oauth-store.ts +9 -0
  443. package/src/oauth/platform-connection.test.ts +98 -0
  444. package/src/oauth/platform-connection.ts +52 -31
  445. package/src/oauth/seed-providers.ts +7 -0
  446. package/src/permissions/checker.ts +16 -6
  447. package/src/permissions/defaults.ts +49 -1
  448. package/src/permissions/trust-store.ts +3 -3
  449. package/src/permissions/workspace-policy.ts +3 -0
  450. package/src/platform/client.test.ts +10 -0
  451. package/src/platform/sync-identity.ts +129 -0
  452. package/src/prompts/persona-resolver.ts +126 -2
  453. package/src/prompts/system-prompt.ts +59 -18
  454. package/src/prompts/templates/BOOTSTRAP.md +5 -5
  455. package/src/prompts/templates/SOUL.md +3 -1
  456. package/src/prompts/templates/UPDATES.md +12 -0
  457. package/src/prompts/templates/channels/slack.md +20 -0
  458. package/src/prompts/update-bulletin-format.ts +26 -9
  459. package/src/prompts/update-bulletin.ts +34 -23
  460. package/src/prompts/user-reference.ts +20 -17
  461. package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
  462. package/src/providers/anthropic/client.ts +157 -61
  463. package/src/providers/fireworks/client.ts +2 -2
  464. package/src/providers/gemini/client.ts +9 -1
  465. package/src/providers/model-catalog.ts +6 -0
  466. package/src/providers/model-intents.ts +4 -4
  467. package/src/providers/ollama/client.ts +2 -2
  468. package/src/providers/openai/chat-completions-provider.ts +474 -0
  469. package/src/providers/openai/client.ts +25 -440
  470. package/src/providers/openai/responses-provider.ts +502 -0
  471. package/src/providers/openrouter/client.ts +101 -4
  472. package/src/providers/provider-secret-catalog.ts +139 -0
  473. package/src/providers/registry.ts +2 -2
  474. package/src/providers/retry.ts +14 -3
  475. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
  476. package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
  477. package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
  478. package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
  479. package/src/providers/speech-to-text/deepgram.test.ts +332 -0
  480. package/src/providers/speech-to-text/deepgram.ts +115 -0
  481. package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
  482. package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
  483. package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
  484. package/src/providers/speech-to-text/google-gemini.ts +101 -0
  485. package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
  486. package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
  487. package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
  488. package/src/providers/speech-to-text/openai-whisper.ts +63 -33
  489. package/src/providers/speech-to-text/provider-catalog.ts +306 -0
  490. package/src/providers/speech-to-text/resolve.ts +386 -6
  491. package/src/providers/types.ts +9 -0
  492. package/src/runtime/AGENTS.md +43 -1
  493. package/src/runtime/__tests__/agent-wake.test.ts +831 -0
  494. package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
  495. package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
  496. package/src/runtime/agent-wake.ts +512 -0
  497. package/src/runtime/auth/__tests__/route-policy.test.ts +40 -0
  498. package/src/runtime/auth/route-policy.ts +30 -5
  499. package/src/runtime/auth/token-service.ts +56 -1
  500. package/src/runtime/btw-sidechain.ts +2 -0
  501. package/src/runtime/capability-tokens.ts +10 -10
  502. package/src/runtime/channel-invite-transport.ts +1 -1
  503. package/src/runtime/channel-invite-transports/email.ts +14 -6
  504. package/src/runtime/channel-readiness-service.ts +12 -22
  505. package/src/runtime/chrome-extension-registry.ts +38 -2
  506. package/src/runtime/http-server.ts +395 -10
  507. package/src/runtime/http-types.ts +6 -2
  508. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +36 -0
  509. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
  510. package/src/runtime/migrations/migration-transport.ts +1 -0
  511. package/src/runtime/migrations/migration-wizard.ts +1 -0
  512. package/src/runtime/migrations/vbundle-import-analyzer.ts +77 -1
  513. package/src/runtime/migrations/vbundle-importer.ts +34 -0
  514. package/src/runtime/pending-interactions.ts +0 -11
  515. package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
  516. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
  517. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
  518. package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
  519. package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
  520. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
  521. package/src/runtime/routes/app-management-routes.ts +12 -18
  522. package/src/runtime/routes/attachment-routes.test.ts +9 -3
  523. package/src/runtime/routes/attachment-routes.ts +216 -17
  524. package/src/runtime/routes/backup-routes.ts +519 -0
  525. package/src/runtime/routes/browser-extension-pair-routes.ts +82 -23
  526. package/src/runtime/routes/btw-routes.ts +8 -6
  527. package/src/runtime/routes/contact-routes.test.ts +298 -0
  528. package/src/runtime/routes/contact-routes.ts +132 -5
  529. package/src/runtime/routes/conversation-analysis-routes.ts +22 -142
  530. package/src/runtime/routes/conversation-management-routes.ts +115 -0
  531. package/src/runtime/routes/conversation-routes.ts +367 -146
  532. package/src/runtime/routes/filing-routes.ts +93 -0
  533. package/src/runtime/routes/home-feed-routes.ts +334 -0
  534. package/src/runtime/routes/home-state-routes.ts +138 -0
  535. package/src/runtime/routes/host-browser-routes.ts +3 -14
  536. package/src/runtime/routes/identity-intro-cache.ts +7 -3
  537. package/src/runtime/routes/identity-routes.ts +3 -17
  538. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
  539. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
  540. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
  541. package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
  542. package/src/runtime/routes/integrations/slack/channel.ts +11 -3
  543. package/src/runtime/routes/integrations/slack/share.ts +45 -7
  544. package/src/runtime/routes/llm-context-normalization.ts +303 -0
  545. package/src/runtime/routes/memory-item-routes.test.ts +3 -2
  546. package/src/runtime/routes/migration-routes.ts +40 -5
  547. package/src/runtime/routes/settings-routes.ts +22 -5
  548. package/src/runtime/routes/skills-routes.ts +76 -7
  549. package/src/runtime/routes/stt-routes.ts +233 -0
  550. package/src/runtime/routes/surface-action-routes.ts +41 -2
  551. package/src/runtime/routes/tts-routes.ts +108 -24
  552. package/src/runtime/routes/usage-routes.ts +30 -2
  553. package/src/runtime/routes/user-route-dispatcher.ts +50 -5
  554. package/src/runtime/routes/user-routes.ts +13 -1
  555. package/src/runtime/routes/work-items-routes.ts +8 -1
  556. package/src/runtime/runtime-mode.ts +33 -0
  557. package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
  558. package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
  559. package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
  560. package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
  561. package/src/runtime/services/analyze-conversation.ts +344 -0
  562. package/src/runtime/services/analyze-deps-singleton.ts +32 -0
  563. package/src/runtime/services/auto-analysis-prompt.ts +55 -0
  564. package/src/runtime/skill-route-registry.ts +49 -0
  565. package/src/runtime/slack-block-formatting.ts +437 -10
  566. package/src/schedule/scheduler.ts +50 -0
  567. package/src/security/oauth2.ts +26 -4
  568. package/src/security/secure-keys.ts +25 -2
  569. package/src/security/token-manager.ts +8 -0
  570. package/src/sequence/engine.ts +23 -0
  571. package/src/sequence/types.ts +1 -1
  572. package/src/skills/catalog-files.ts +64 -2
  573. package/src/skills/category-inference.ts +122 -0
  574. package/src/skills/clawhub-files.ts +213 -0
  575. package/src/skills/clawhub.ts +84 -23
  576. package/src/skills/skill-file-provider.ts +40 -0
  577. package/src/skills/skillssh-files.ts +395 -0
  578. package/src/skills/skillssh-registry.ts +4 -4
  579. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
  580. package/src/stt/__tests__/types.test.ts +89 -0
  581. package/src/stt/daemon-batch-transcriber.ts +195 -0
  582. package/src/stt/stt-stream-session.ts +499 -0
  583. package/src/stt/types.ts +330 -0
  584. package/src/stt/wav-encoder.test.ts +373 -0
  585. package/src/stt/wav-encoder.ts +175 -0
  586. package/src/subagent/manager.ts +38 -14
  587. package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
  588. package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
  589. package/src/tools/browser/browser-execution.ts +1163 -23
  590. package/src/tools/browser/browser-manager.ts +45 -0
  591. package/src/tools/browser/browser-mode-constants.ts +12 -0
  592. package/src/tools/browser/browser-mode.ts +92 -0
  593. package/src/tools/browser/browser-status-constants.ts +33 -0
  594. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +393 -0
  595. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +29 -0
  596. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1648 -32
  597. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +264 -0
  598. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +183 -17
  599. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +254 -21
  600. package/src/tools/browser/cdp-client/errors.ts +15 -0
  601. package/src/tools/browser/cdp-client/extension-cdp-client.ts +39 -16
  602. package/src/tools/browser/cdp-client/factory.ts +797 -87
  603. package/src/tools/browser/cdp-client/index.ts +16 -2
  604. package/src/tools/browser/cdp-client/types.ts +68 -0
  605. package/src/tools/credentials/vault.ts +35 -6
  606. package/src/tools/network/web-fetch.ts +5 -2
  607. package/src/tools/network/web-search.ts +5 -2
  608. package/src/tools/shared/shell-output.ts +3 -1
  609. package/src/tools/side-effects.ts +2 -0
  610. package/src/tools/skills/sandbox-runner.ts +3 -2
  611. package/src/tools/terminal/safe-env.ts +10 -2
  612. package/src/tools/terminal/shell.ts +15 -4
  613. package/src/tools/tool-manifest.ts +21 -0
  614. package/src/tools/types.ts +17 -0
  615. package/src/tools/ui-surface/definitions.ts +6 -1
  616. package/src/tts/__tests__/provider-adapters.test.ts +834 -0
  617. package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
  618. package/src/tts/__tests__/provider-catalog.test.ts +183 -0
  619. package/src/tts/__tests__/provider-registry.test.ts +90 -0
  620. package/src/tts/provider-catalog.ts +201 -0
  621. package/src/tts/provider-registry.ts +73 -0
  622. package/src/tts/providers/deepgram-provider.ts +219 -0
  623. package/src/tts/providers/elevenlabs-provider.ts +211 -0
  624. package/src/tts/providers/fish-audio-provider.ts +183 -0
  625. package/src/tts/providers/index.ts +42 -0
  626. package/src/tts/providers/register-builtins.ts +130 -0
  627. package/src/tts/synthesize-text.ts +110 -0
  628. package/src/tts/tts-config-resolver.ts +78 -0
  629. package/src/tts/types.ts +153 -0
  630. package/src/types/onboarding-context.ts +7 -0
  631. package/src/util/abort-reasons.ts +58 -0
  632. package/src/util/device-id.ts +32 -16
  633. package/src/util/errors.ts +9 -1
  634. package/src/util/platform.ts +54 -10
  635. package/src/util/pricing.ts +66 -3
  636. package/src/util/spawn.ts +1 -1
  637. package/src/util/truncate.ts +4 -2
  638. package/src/util/unicode.ts +201 -0
  639. package/src/version.ts +19 -24
  640. package/src/watcher/engine.ts +23 -0
  641. package/src/watcher/watcher-store.ts +31 -0
  642. package/src/workspace/migrations/003-seed-device-id.ts +9 -3
  643. package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
  644. package/src/workspace/migrations/029-seed-pkb.ts +1 -1
  645. package/src/workspace/migrations/031-drop-user-md.ts +317 -0
  646. package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
  647. package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
  648. package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
  649. package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
  650. package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
  651. package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
  652. package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
  653. package/src/workspace/migrations/registry.ts +16 -0
  654. package/src/workspace/top-level-renderer.ts +13 -1
  655. package/src/workspace/turn-commit.ts +31 -0
  656. package/src/__tests__/email-cli.test.ts +0 -297
  657. package/src/__tests__/email-service-config-fallback.test.ts +0 -102
  658. package/src/cli/commands/browser-relay.ts +0 -466
  659. package/src/email/guardrails.ts +0 -221
  660. package/src/email/provider.ts +0 -117
  661. package/src/email/providers/agentmail.ts +0 -361
  662. package/src/email/providers/index.ts +0 -65
  663. package/src/email/service.ts +0 -384
  664. package/src/email/types.ts +0 -126
  665. package/src/prompts/templates/USER.md +0 -13
  666. package/src/providers/speech-to-text/types.ts +0 -17
  667. package/src/runtime/routes/browser-cdp-routes.ts +0 -229
@@ -0,0 +1,368 @@
1
+ /**
2
+ * Tests for workspace migration `031-drop-user-md`.
3
+ *
4
+ * Validates the five behavioral contracts spelled out in the plan:
5
+ * 1. Fresh install (no guardian) — no-op.
6
+ * 2. Pre-017 customized USER.md, guardian has no userFile — backfill slug,
7
+ * copy content into users/<slug>.md, delete USER.md.
8
+ * 3. Post-017 state (users/<slug>.md already populated, USER.md still on disk
9
+ * as template) — migration does NOT overwrite users/<slug>.md, deletes USER.md.
10
+ * 4. Idempotent re-run — running twice has no additional effect.
11
+ * 5. Guardian with missing users/ directory — migration creates the directory.
12
+ *
13
+ * The migration imports `findGuardianForChannel`, `listGuardianChannels`,
14
+ * and `generateUserFileSlug` from `contacts/contact-store.js`, and calls
15
+ * `getDb()` to persist backfilled slugs. These tests stub the contact
16
+ * store and DB layer so no real DB is exercised.
17
+ */
18
+
19
+ import {
20
+ existsSync,
21
+ mkdirSync,
22
+ mkdtempSync,
23
+ readFileSync,
24
+ rmSync,
25
+ writeFileSync,
26
+ } from "node:fs";
27
+ import { tmpdir } from "node:os";
28
+ import { join } from "node:path";
29
+ import {
30
+ afterAll,
31
+ afterEach,
32
+ beforeAll,
33
+ beforeEach,
34
+ describe,
35
+ expect,
36
+ mock,
37
+ test,
38
+ } from "bun:test";
39
+
40
+ // ── Mock state ────────────────────────────────────────────────────
41
+
42
+ interface MockContact {
43
+ id: string;
44
+ displayName: string;
45
+ userFile: string | null;
46
+ }
47
+
48
+ let mockVellumGuardian: {
49
+ contact: MockContact;
50
+ channel: Record<string, unknown>;
51
+ } | null = null;
52
+ let mockAnyGuardian: {
53
+ contact: MockContact;
54
+ channels: Record<string, unknown>[];
55
+ } | null = null;
56
+ let mockSlugOverride: ((displayName: string) => string) | null = null;
57
+
58
+ // Records drizzle `.update(contacts).set({userFile: ...}).where(...).run()` calls.
59
+ let updatedUserFiles: Array<{ contactId: string; userFile: string }> = [];
60
+
61
+ // ── Mock modules (must precede migration import) ──────────────────
62
+
63
+ mock.module("../contacts/contact-store.js", () => ({
64
+ findGuardianForChannel: (channelType: string) =>
65
+ channelType === "vellum" ? mockVellumGuardian : null,
66
+ listGuardianChannels: () => mockAnyGuardian,
67
+ generateUserFileSlug: (displayName: string) => {
68
+ if (mockSlugOverride) return mockSlugOverride(displayName);
69
+ const base =
70
+ displayName
71
+ .toLowerCase()
72
+ .replace(/[^a-z0-9]+/g, "-")
73
+ .replace(/^-+|-+$/g, "") || "user";
74
+ return `${base}.md`;
75
+ },
76
+ }));
77
+
78
+ // Minimal drizzle-compatible stub for the single `update` call in the
79
+ // migration. The migration builds:
80
+ // db.update(contacts).set({ userFile: slug }).where(eq(contacts.id, guardian.id)).run();
81
+ // The stub captures the payload into `updatedUserFiles` and also mutates
82
+ // the active mock guardian in place so downstream reads observe the new slug.
83
+ mock.module("../memory/db.js", () => ({
84
+ getDb: () => ({
85
+ update: () => ({
86
+ set: (values: { userFile: string }) => ({
87
+ where: () => ({
88
+ run: () => {
89
+ const guardian =
90
+ mockVellumGuardian?.contact ?? mockAnyGuardian?.contact ?? null;
91
+ if (guardian) {
92
+ guardian.userFile = values.userFile;
93
+ updatedUserFiles.push({
94
+ contactId: guardian.id,
95
+ userFile: values.userFile,
96
+ });
97
+ }
98
+ },
99
+ }),
100
+ }),
101
+ }),
102
+ }),
103
+ }));
104
+
105
+ // drizzle-orm's `eq()` is invoked by the migration; stub it out so we
106
+ // don't need the real module loaded for unit tests.
107
+ mock.module("drizzle-orm", () => ({
108
+ eq: (_col: unknown, value: unknown) => ({ col: _col, value }),
109
+ }));
110
+
111
+ // Stub the schema import so drizzle operand construction doesn't touch
112
+ // the real sqlite schema (which pulls in the DB).
113
+ mock.module("../memory/schema/contacts.js", () => ({
114
+ contacts: { id: "id", userFile: "userFile" },
115
+ }));
116
+
117
+ // Import AFTER mocks so the migration binds to the stubs above.
118
+ import { dropUserMdMigration } from "../workspace/migrations/031-drop-user-md.js";
119
+
120
+ // ── Test workspace scaffold ───────────────────────────────────────
121
+
122
+ let testRoot: string;
123
+ let workspaceDir: string;
124
+
125
+ function templateContent(): string {
126
+ return `_ Lines starting with _ are comments - they won't appear in the system prompt
127
+
128
+ # USER.md
129
+
130
+ Store details about your user here. Edit freely - build this over time as you learn about them. Don't be pushy about seeking details, but when you learn something, write it down. More context makes you more useful.
131
+
132
+ - Preferred name/reference:
133
+ - Pronouns:
134
+ - Locale:
135
+ - Work role:
136
+ - Goals:
137
+ - Hobbies/fun:
138
+ - Daily tools:
139
+ `;
140
+ }
141
+
142
+ function customizedContent(): string {
143
+ return `_ Lines starting with _ are comments - they won't appear in the system prompt
144
+
145
+ # USER.md
146
+
147
+ - Preferred name/reference: Sidd
148
+ - Pronouns: he/him
149
+ - Work role: Engineer
150
+ - Daily tools: Vellum, vim, tmux
151
+ `;
152
+ }
153
+
154
+ beforeAll(() => {
155
+ testRoot = mkdtempSync(join(tmpdir(), "drop-user-md-test-"));
156
+ });
157
+
158
+ afterAll(() => {
159
+ rmSync(testRoot, { recursive: true, force: true });
160
+ });
161
+
162
+ beforeEach(() => {
163
+ workspaceDir = mkdtempSync(join(testRoot, "ws-"));
164
+ mockVellumGuardian = null;
165
+ mockAnyGuardian = null;
166
+ mockSlugOverride = null;
167
+ updatedUserFiles = [];
168
+ });
169
+
170
+ afterEach(() => {
171
+ rmSync(workspaceDir, { recursive: true, force: true });
172
+ });
173
+
174
+ // ── Tests ─────────────────────────────────────────────────────────
175
+
176
+ describe("workspace migration 031-drop-user-md", () => {
177
+ test("has the correct id and description", () => {
178
+ expect(dropUserMdMigration.id).toBe("031-drop-user-md");
179
+ expect(dropUserMdMigration.description).toContain(
180
+ "Delete legacy workspace-root USER.md",
181
+ );
182
+ });
183
+
184
+ test("fresh install (no guardian, no USER.md) is a no-op", () => {
185
+ // No guardian stubbed in, no USER.md on disk.
186
+ dropUserMdMigration.run(workspaceDir);
187
+
188
+ expect(existsSync(join(workspaceDir, "USER.md"))).toBe(false);
189
+ expect(existsSync(join(workspaceDir, "users"))).toBe(false);
190
+ expect(updatedUserFiles).toEqual([]);
191
+ });
192
+
193
+ test("pre-017 customized USER.md with guardian missing userFile backfills slug and migrates content", () => {
194
+ // Guardian exists on the 'vellum' channel but has no userFile.
195
+ mockVellumGuardian = {
196
+ contact: {
197
+ id: "guardian-1",
198
+ displayName: "Sidd",
199
+ userFile: null,
200
+ },
201
+ channel: { type: "vellum" },
202
+ };
203
+
204
+ const userMdPath = join(workspaceDir, "USER.md");
205
+ const content = customizedContent();
206
+ writeFileSync(userMdPath, content, "utf-8");
207
+
208
+ dropUserMdMigration.run(workspaceDir);
209
+
210
+ // Backfill happened: drizzle update was called with the generated slug.
211
+ expect(updatedUserFiles).toHaveLength(1);
212
+ expect(updatedUserFiles[0].contactId).toBe("guardian-1");
213
+ expect(updatedUserFiles[0].userFile).toBe("sidd.md");
214
+
215
+ // Content was migrated into users/sidd.md.
216
+ const destPath = join(workspaceDir, "users", "sidd.md");
217
+ expect(existsSync(destPath)).toBe(true);
218
+ expect(readFileSync(destPath, "utf-8")).toBe(content);
219
+
220
+ // Legacy USER.md was deleted.
221
+ expect(existsSync(userMdPath)).toBe(false);
222
+ });
223
+
224
+ test("post-017 users/<slug>.md already populated, USER.md still on disk as template — does not overwrite dest, deletes USER.md", () => {
225
+ // Guardian already has a userFile from a prior 017 run.
226
+ mockVellumGuardian = {
227
+ contact: {
228
+ id: "guardian-2",
229
+ displayName: "Sidd",
230
+ userFile: "sidd.md",
231
+ },
232
+ channel: { type: "vellum" },
233
+ };
234
+
235
+ // Pre-populated persona file (post-017 state).
236
+ const usersDir = join(workspaceDir, "users");
237
+ mkdirSync(usersDir, { recursive: true });
238
+ const destPath = join(usersDir, "sidd.md");
239
+ const existingPersona = "# Sidd's Profile\n\n- Loves kayaking\n";
240
+ writeFileSync(destPath, existingPersona, "utf-8");
241
+
242
+ // Leftover template-shape USER.md at workspace root.
243
+ const userMdPath = join(workspaceDir, "USER.md");
244
+ writeFileSync(userMdPath, templateContent(), "utf-8");
245
+
246
+ dropUserMdMigration.run(workspaceDir);
247
+
248
+ // users/sidd.md is untouched.
249
+ expect(readFileSync(destPath, "utf-8")).toBe(existingPersona);
250
+
251
+ // USER.md is gone.
252
+ expect(existsSync(userMdPath)).toBe(false);
253
+
254
+ // No slug backfill necessary.
255
+ expect(updatedUserFiles).toEqual([]);
256
+ });
257
+
258
+ test("idempotent: second run is a no-op after the first run deleted USER.md", () => {
259
+ mockVellumGuardian = {
260
+ contact: {
261
+ id: "guardian-3",
262
+ displayName: "Alice",
263
+ userFile: "alice.md",
264
+ },
265
+ channel: { type: "vellum" },
266
+ };
267
+
268
+ const userMdPath = join(workspaceDir, "USER.md");
269
+ writeFileSync(userMdPath, customizedContent(), "utf-8");
270
+
271
+ // First run: migrates content and deletes USER.md.
272
+ dropUserMdMigration.run(workspaceDir);
273
+ expect(existsSync(userMdPath)).toBe(false);
274
+ const destPath = join(workspaceDir, "users", "alice.md");
275
+ expect(existsSync(destPath)).toBe(true);
276
+ const afterFirst = readFileSync(destPath, "utf-8");
277
+
278
+ // Second run: no USER.md remains, users/alice.md already has content,
279
+ // so the destination is not rewritten and USER.md is still absent.
280
+ dropUserMdMigration.run(workspaceDir);
281
+ expect(existsSync(userMdPath)).toBe(false);
282
+ expect(existsSync(destPath)).toBe(true);
283
+ expect(readFileSync(destPath, "utf-8")).toBe(afterFirst);
284
+ });
285
+
286
+ test("guardian exists but users/ directory is missing — migration creates the directory", () => {
287
+ mockVellumGuardian = {
288
+ contact: {
289
+ id: "guardian-4",
290
+ displayName: "Bob",
291
+ userFile: "bob.md",
292
+ },
293
+ channel: { type: "vellum" },
294
+ };
295
+
296
+ // USER.md present but no users/ dir yet.
297
+ const userMdPath = join(workspaceDir, "USER.md");
298
+ writeFileSync(userMdPath, customizedContent(), "utf-8");
299
+ expect(existsSync(join(workspaceDir, "users"))).toBe(false);
300
+
301
+ dropUserMdMigration.run(workspaceDir);
302
+
303
+ expect(existsSync(join(workspaceDir, "users"))).toBe(true);
304
+ const destPath = join(workspaceDir, "users", "bob.md");
305
+ expect(existsSync(destPath)).toBe(true);
306
+ expect(readFileSync(destPath, "utf-8")).toBe(customizedContent());
307
+ expect(existsSync(userMdPath)).toBe(false);
308
+ });
309
+
310
+ // ─── Bonus coverage for edge cases ───────────────────────────────
311
+
312
+ test("falls back to listGuardianChannels when no vellum-channel guardian exists", () => {
313
+ mockVellumGuardian = null;
314
+ mockAnyGuardian = {
315
+ contact: {
316
+ id: "guardian-5",
317
+ displayName: "Carol",
318
+ userFile: "carol.md",
319
+ },
320
+ channels: [{ type: "telegram" }],
321
+ };
322
+
323
+ const userMdPath = join(workspaceDir, "USER.md");
324
+ writeFileSync(userMdPath, customizedContent(), "utf-8");
325
+
326
+ dropUserMdMigration.run(workspaceDir);
327
+
328
+ const destPath = join(workspaceDir, "users", "carol.md");
329
+ expect(existsSync(destPath)).toBe(true);
330
+ expect(readFileSync(destPath, "utf-8")).toBe(customizedContent());
331
+ expect(existsSync(userMdPath)).toBe(false);
332
+ });
333
+
334
+ test("template-shaped USER.md with no destination file — seeds scaffold and deletes USER.md", () => {
335
+ mockVellumGuardian = {
336
+ contact: {
337
+ id: "guardian-6",
338
+ displayName: "Dana",
339
+ userFile: "dana.md",
340
+ },
341
+ channel: { type: "vellum" },
342
+ };
343
+
344
+ const userMdPath = join(workspaceDir, "USER.md");
345
+ writeFileSync(userMdPath, templateContent(), "utf-8");
346
+
347
+ dropUserMdMigration.run(workspaceDir);
348
+
349
+ // USER.md is gone.
350
+ expect(existsSync(userMdPath)).toBe(false);
351
+
352
+ // The destination was scaffolded with the guardian persona template
353
+ // (parity with ensureGuardianPersonaFile for new installs).
354
+ const destPath = join(workspaceDir, "users", "dana.md");
355
+ expect(existsSync(destPath)).toBe(true);
356
+ const content = readFileSync(destPath, "utf-8");
357
+ expect(content).toContain("# User Profile");
358
+ expect(content).toContain("Preferred name/reference:");
359
+ // Not the legacy template header.
360
+ expect(content).not.toContain("# USER.md");
361
+ });
362
+
363
+ test("down() is a no-op (deletion is irreversible)", () => {
364
+ // Should not throw.
365
+ dropUserMdMigration.down(workspaceDir);
366
+ expect(existsSync(join(workspaceDir, "USER.md"))).toBe(false);
367
+ });
368
+ });
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Tests for workspace migration `037-create-meets-dir`.
3
+ *
4
+ * The migration creates `<workspace>/meets/` (if missing) and seeds a
5
+ * `.keep` sentinel file. It must be idempotent and must not touch other
6
+ * workspace files.
7
+ */
8
+
9
+ import {
10
+ existsSync,
11
+ mkdirSync,
12
+ mkdtempSync,
13
+ readdirSync,
14
+ readFileSync,
15
+ rmSync,
16
+ statSync,
17
+ writeFileSync,
18
+ } from "node:fs";
19
+ import { tmpdir } from "node:os";
20
+ import { join } from "node:path";
21
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
22
+
23
+ import { createMeetsDirMigration } from "../workspace/migrations/037-create-meets-dir.js";
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Helpers
27
+ // ---------------------------------------------------------------------------
28
+
29
+ let workspaceDir: string;
30
+
31
+ beforeEach(() => {
32
+ workspaceDir = mkdtempSync(join(tmpdir(), "vellum-migration-037-test-"));
33
+ });
34
+
35
+ afterEach(() => {
36
+ if (existsSync(workspaceDir)) {
37
+ rmSync(workspaceDir, { recursive: true, force: true });
38
+ }
39
+ });
40
+
41
+ /**
42
+ * Snapshot the (relative path -> mtimeMs, content) for every file inside a
43
+ * directory so we can assert a migration did not touch it.
44
+ */
45
+ function snapshotTree(
46
+ root: string,
47
+ ): Record<string, { mtimeMs: number; content: string }> {
48
+ const out: Record<string, { mtimeMs: number; content: string }> = {};
49
+ function walk(dir: string): void {
50
+ if (!existsSync(dir)) return;
51
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
52
+ const abs = join(dir, entry.name);
53
+ if (entry.isDirectory()) {
54
+ walk(abs);
55
+ } else if (entry.isFile()) {
56
+ const rel = abs.slice(root.length + 1);
57
+ out[rel] = {
58
+ mtimeMs: statSync(abs).mtimeMs,
59
+ content: readFileSync(abs, "utf-8"),
60
+ };
61
+ }
62
+ }
63
+ }
64
+ walk(root);
65
+ return out;
66
+ }
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Tests
70
+ // ---------------------------------------------------------------------------
71
+
72
+ describe("037-create-meets-dir migration", () => {
73
+ test("has correct id and description", () => {
74
+ expect(createMeetsDirMigration.id).toBe("037-create-meets-dir");
75
+ expect(createMeetsDirMigration.description).toContain("meets/");
76
+ });
77
+
78
+ test("creates meets/ with .keep sentinel file on a fresh workspace", () => {
79
+ createMeetsDirMigration.run(workspaceDir);
80
+
81
+ const meetsDir = join(workspaceDir, "meets");
82
+ expect(existsSync(meetsDir)).toBe(true);
83
+ expect(statSync(meetsDir).isDirectory()).toBe(true);
84
+
85
+ const keepPath = join(meetsDir, ".keep");
86
+ expect(existsSync(keepPath)).toBe(true);
87
+ expect(statSync(keepPath).isFile()).toBe(true);
88
+ });
89
+
90
+ test("is idempotent — running twice does not error and does not overwrite .keep", () => {
91
+ createMeetsDirMigration.run(workspaceDir);
92
+
93
+ const keepPath = join(workspaceDir, "meets", ".keep");
94
+ expect(existsSync(keepPath)).toBe(true);
95
+
96
+ const firstContent = readFileSync(keepPath, "utf-8");
97
+ const firstMtime = statSync(keepPath).mtimeMs;
98
+
99
+ // Second run — should not throw, should not rewrite the .keep file.
100
+ createMeetsDirMigration.run(workspaceDir);
101
+
102
+ expect(existsSync(keepPath)).toBe(true);
103
+ expect(readFileSync(keepPath, "utf-8")).toBe(firstContent);
104
+ expect(statSync(keepPath).mtimeMs).toBe(firstMtime);
105
+ });
106
+
107
+ test("does not error when meets/ already exists with user content", () => {
108
+ // Simulate a pre-existing meets/ directory with a user meeting inside.
109
+ const meetsDir = join(workspaceDir, "meets");
110
+ const meetingDir = join(meetsDir, "meeting-abc");
111
+ mkdirSync(meetingDir, { recursive: true });
112
+ writeFileSync(join(meetingDir, "meta.json"), "{}", "utf-8");
113
+
114
+ createMeetsDirMigration.run(workspaceDir);
115
+
116
+ // Pre-existing content is preserved.
117
+ expect(existsSync(join(meetingDir, "meta.json"))).toBe(true);
118
+ expect(readFileSync(join(meetingDir, "meta.json"), "utf-8")).toBe("{}");
119
+
120
+ // .keep sentinel was added at the meets/ root.
121
+ expect(existsSync(join(meetsDir, ".keep"))).toBe(true);
122
+ });
123
+
124
+ test("does not overwrite a user-customized .keep file", () => {
125
+ const meetsDir = join(workspaceDir, "meets");
126
+ mkdirSync(meetsDir, { recursive: true });
127
+ const keepPath = join(meetsDir, ".keep");
128
+ const customContent = "# my notes about meets/\n";
129
+ writeFileSync(keepPath, customContent, "utf-8");
130
+
131
+ createMeetsDirMigration.run(workspaceDir);
132
+
133
+ expect(readFileSync(keepPath, "utf-8")).toBe(customContent);
134
+ });
135
+
136
+ test("does not touch other workspace files", () => {
137
+ // Pre-populate unrelated workspace files.
138
+ writeFileSync(
139
+ join(workspaceDir, "config.json"),
140
+ JSON.stringify({ hello: "world" }, null, 2),
141
+ "utf-8",
142
+ );
143
+ mkdirSync(join(workspaceDir, "users"), { recursive: true });
144
+ writeFileSync(
145
+ join(workspaceDir, "users", "guardian.md"),
146
+ "# Guardian\n",
147
+ "utf-8",
148
+ );
149
+ mkdirSync(join(workspaceDir, "pkb"), { recursive: true });
150
+ writeFileSync(
151
+ join(workspaceDir, "pkb", "INDEX.md"),
152
+ "# Knowledge Base\n",
153
+ "utf-8",
154
+ );
155
+
156
+ const before = snapshotTree(workspaceDir);
157
+
158
+ createMeetsDirMigration.run(workspaceDir);
159
+
160
+ const after = snapshotTree(workspaceDir);
161
+
162
+ // Everything that existed before should still exist with identical
163
+ // content and mtime (nothing touched).
164
+ for (const [rel, prev] of Object.entries(before)) {
165
+ expect(after[rel]).toBeDefined();
166
+ expect(after[rel].content).toBe(prev.content);
167
+ expect(after[rel].mtimeMs).toBe(prev.mtimeMs);
168
+ }
169
+
170
+ // Only the .keep under meets/ should be new.
171
+ const added = Object.keys(after).filter((rel) => !(rel in before));
172
+ expect(added).toEqual([join("meets", ".keep")]);
173
+ });
174
+
175
+ test("second run on a populated meets/ leaves user content untouched", () => {
176
+ createMeetsDirMigration.run(workspaceDir);
177
+
178
+ const meetsDir = join(workspaceDir, "meets");
179
+ const meetingDir = join(meetsDir, "meeting-xyz");
180
+ mkdirSync(meetingDir, { recursive: true });
181
+ const transcriptPath = join(meetingDir, "transcript.jsonl");
182
+ writeFileSync(
183
+ transcriptPath,
184
+ '{"t":0,"text":"hello"}\n',
185
+ "utf-8",
186
+ );
187
+ const before = snapshotTree(workspaceDir);
188
+
189
+ createMeetsDirMigration.run(workspaceDir);
190
+
191
+ const after = snapshotTree(workspaceDir);
192
+ expect(Object.keys(after).sort()).toEqual(Object.keys(before).sort());
193
+ for (const [rel, prev] of Object.entries(before)) {
194
+ expect(after[rel].content).toBe(prev.content);
195
+ expect(after[rel].mtimeMs).toBe(prev.mtimeMs);
196
+ }
197
+ });
198
+
199
+ // ─── down() ─────────────────────────────────────────────────────────────
200
+
201
+ describe("down()", () => {
202
+ test("removes seeded .keep and empty meets/ directory after a forward run", () => {
203
+ createMeetsDirMigration.run(workspaceDir);
204
+ expect(existsSync(join(workspaceDir, "meets", ".keep"))).toBe(true);
205
+
206
+ createMeetsDirMigration.down(workspaceDir);
207
+
208
+ expect(existsSync(join(workspaceDir, "meets", ".keep"))).toBe(false);
209
+ expect(existsSync(join(workspaceDir, "meets"))).toBe(false);
210
+ });
211
+
212
+ test("preserves user-created meeting content on down()", () => {
213
+ createMeetsDirMigration.run(workspaceDir);
214
+
215
+ const meetsDir = join(workspaceDir, "meets");
216
+ const meetingDir = join(meetsDir, "meeting-abc");
217
+ mkdirSync(meetingDir, { recursive: true });
218
+ writeFileSync(join(meetingDir, "meta.json"), "{}", "utf-8");
219
+
220
+ createMeetsDirMigration.down(workspaceDir);
221
+
222
+ // .keep is removed but user content and the meets/ dir itself remain
223
+ // since the directory is non-empty.
224
+ expect(existsSync(join(meetsDir, ".keep"))).toBe(false);
225
+ expect(existsSync(meetsDir)).toBe(true);
226
+ expect(existsSync(join(meetingDir, "meta.json"))).toBe(true);
227
+ });
228
+
229
+ test("idempotent — down() twice does not throw", () => {
230
+ createMeetsDirMigration.run(workspaceDir);
231
+ createMeetsDirMigration.down(workspaceDir);
232
+ // Second call — should not throw.
233
+ createMeetsDirMigration.down(workspaceDir);
234
+ expect(existsSync(join(workspaceDir, "meets"))).toBe(false);
235
+ });
236
+
237
+ test("no-op when meets/ does not exist", () => {
238
+ expect(existsSync(join(workspaceDir, "meets"))).toBe(false);
239
+ createMeetsDirMigration.down(workspaceDir);
240
+ // Should not throw and should not create anything.
241
+ expect(existsSync(join(workspaceDir, "meets"))).toBe(false);
242
+ });
243
+ });
244
+ });
@@ -11,7 +11,6 @@ const writeFileSyncFn = mock(
11
11
  );
12
12
  const mkdirSyncFn = mock((_path: string, _opts?: object) => {});
13
13
  const homedirFn = mock((): string => "/mock-home");
14
- const getDeviceIdBaseDirFn = mock((): string => "/mock-home");
15
14
 
16
15
  // ---------------------------------------------------------------------------
17
16
  // Mock modules — before importing module under test
@@ -28,10 +27,6 @@ mock.module("node:os", () => ({
28
27
  homedir: homedirFn,
29
28
  }));
30
29
 
31
- mock.module("../util/device-id.js", () => ({
32
- getDeviceIdBaseDir: getDeviceIdBaseDirFn,
33
- }));
34
-
35
30
  // Import after mocking
36
31
  import { seedDeviceIdMigration } from "../workspace/migrations/003-seed-device-id.js";
37
32
 
@@ -69,7 +64,7 @@ describe("003-seed-device-id migration", () => {
69
64
  writeFileSyncFn.mockClear();
70
65
  mkdirSyncFn.mockClear();
71
66
  homedirFn.mockReturnValue("/mock-home");
72
- getDeviceIdBaseDirFn.mockReturnValue("/mock-home");
67
+ delete process.env.IS_CONTAINERIZED;
73
68
  });
74
69
 
75
70
  test("no-op when device.json already has a deviceId", () => {
@@ -271,14 +266,13 @@ describe("003-seed-device-id migration", () => {
271
266
  expect(parsed.deviceId).toBe("install-legacy");
272
267
  });
273
268
 
274
- test("reads lockfile from homedir even when getDeviceIdBaseDir differs", () => {
275
- const customBase = "/custom-base";
276
- getDeviceIdBaseDirFn.mockReturnValue(customBase);
269
+ test("containerized: writes device.json under /home/assistant while reading lockfile from homedir", () => {
270
+ process.env.IS_CONTAINERIZED = "true";
277
271
  homedirFn.mockReturnValue("/mock-home");
278
272
 
279
- const customDevicePath = `${customBase}/.vellum/device.json`;
273
+ const containerDevicePath = "/home/assistant/.vellum/device.json";
280
274
 
281
- // Lockfile is at homedir, NOT at customBase
275
+ // Lockfile is at homedir, NOT under /home/assistant
282
276
  existsSyncFn.mockImplementation((path: string) => path === LOCK_PATH);
283
277
  readFileSyncFn.mockImplementation((path: string, _enc: string) => {
284
278
  if (path === LOCK_PATH) {
@@ -301,23 +295,23 @@ describe("003-seed-device-id migration", () => {
301
295
  string,
302
296
  object,
303
297
  ];
304
- // device.json is written under getDeviceIdBaseDir, not homedir
305
- expect(path).toBe(customDevicePath);
298
+ expect(path).toBe(containerDevicePath);
306
299
  const parsed = JSON.parse(data);
307
300
  expect(parsed.deviceId).toBe("install-custom");
308
301
  });
309
302
 
310
- test("ignores lockfile under getDeviceIdBaseDir when it differs from homedir", () => {
311
- const customBase = "/custom-base";
312
- getDeviceIdBaseDirFn.mockReturnValue(customBase);
303
+ test("containerized: ignores lockfile under /home/assistant", () => {
304
+ process.env.IS_CONTAINERIZED = "true";
313
305
  homedirFn.mockReturnValue("/mock-home");
314
306
 
315
- // Only a lockfile under customBase exists — should be ignored since
307
+ // Only a lockfile under /home/assistant exists — should be ignored since
316
308
  // the migration always reads the lockfile from homedir().
317
- const customLockPath = `${customBase}/.vellum.lock.json`;
318
- existsSyncFn.mockImplementation((path: string) => path === customLockPath);
309
+ const containerLockPath = "/home/assistant/.vellum.lock.json";
310
+ existsSyncFn.mockImplementation(
311
+ (path: string) => path === containerLockPath,
312
+ );
319
313
  readFileSyncFn.mockImplementation((path: string, _enc: string) => {
320
- if (path === customLockPath) {
314
+ if (path === containerLockPath) {
321
315
  return makeLockfile([
322
316
  {
323
317
  name: "custom",