@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
@@ -79,7 +79,7 @@ const {
79
79
  } = await import("../prompts/system-prompt.js");
80
80
 
81
81
  /**
82
- * Extract just the workspace-file content (IDENTITY.md, SOUL.md, USER.md,
82
+ * Extract just the workspace-file content (IDENTITY.md, SOUL.md,
83
83
  * BOOTSTRAP.md) from the full system prompt, stripping all static
84
84
  * instruction sections, configuration, and skills catalog.
85
85
  *
@@ -123,6 +123,7 @@ describe("buildSystemPrompt", () => {
123
123
  "BOOTSTRAP.md",
124
124
  "UPDATES.md",
125
125
  "skills",
126
+ "users",
126
127
  ]) {
127
128
  const p = join(TEST_DIR, name);
128
129
  if (existsSync(p)) rmSync(p, { recursive: true, force: true });
@@ -249,31 +250,80 @@ describe("buildSystemPrompt", () => {
249
250
  expect(result).not.toContain("incident-response");
250
251
  });
251
252
 
252
- test("appends USER.md after base prompt", () => {
253
- writeFileSync(join(TEST_DIR, "IDENTITY.md"), "Base prompt");
254
- writeFileSync(join(TEST_DIR, "USER.md"), "# User\n\nName: Alice");
253
+ test("builds prompt without error when USER.md does not exist on disk", () => {
254
+ // Persona content now flows through options.userPersona (resolved via
255
+ // resolveGuardianPersona upstream). buildSystemPrompt must never read
256
+ // USER.md from disk — verify it returns a well-formed prompt when the
257
+ // file is absent.
258
+ writeFileSync(join(TEST_DIR, "IDENTITY.md"), "Identity");
259
+ writeFileSync(join(TEST_DIR, "SOUL.md"), "Soul");
255
260
  const result = buildSystemPrompt();
256
- expect(basePrompt(result)).toBe("Base prompt\n\n# User\n\nName: Alice");
261
+ expect(basePrompt(result)).toBe("Identity\n\nSoul");
257
262
  });
258
263
 
259
- test("appends USER.md after IDENTITY + SOUL", () => {
264
+ test("does not read USER.md content from disk even when the file is present", () => {
265
+ // USER.md has been removed from PROMPT_FILES and the fallback read
266
+ // path. A stale file on disk must not leak into the prompt.
260
267
  writeFileSync(join(TEST_DIR, "IDENTITY.md"), "Identity");
261
- writeFileSync(join(TEST_DIR, "SOUL.md"), "Soul");
262
- writeFileSync(join(TEST_DIR, "USER.md"), "User info");
268
+ writeFileSync(
269
+ join(TEST_DIR, "USER.md"),
270
+ "stale user content that should be ignored",
271
+ );
263
272
  const result = buildSystemPrompt();
264
- expect(basePrompt(result)).toBe("Identity\n\nSoul\n\nUser info");
273
+ expect(result).not.toContain("stale user content");
274
+ expect(basePrompt(result)).toBe("Identity");
265
275
  });
266
276
 
267
- test("USER.md alone becomes the prompt", () => {
268
- writeFileSync(join(TEST_DIR, "USER.md"), "Just user");
269
- const result = buildSystemPrompt();
270
- expect(basePrompt(result)).toBe("Just user");
277
+ test("uses options.userPersona instead of USER.md", () => {
278
+ writeFileSync(join(TEST_DIR, "IDENTITY.md"), "Identity");
279
+ writeFileSync(join(TEST_DIR, "SOUL.md"), "Soul");
280
+ const result = buildSystemPrompt({
281
+ userPersona: "# User persona\n\nName: Alice",
282
+ });
283
+ expect(basePrompt(result)).toBe(
284
+ "Identity\n\nSoul\n\n# User persona\n\nName: Alice",
285
+ );
271
286
  });
272
287
 
273
- test("ignores empty USER.md", () => {
274
- writeFileSync(join(TEST_DIR, "USER.md"), " \n ");
275
- const result = buildSystemPrompt();
276
- expect(basePrompt(result)).toBe("");
288
+ describe("BOOTSTRAP.md user persona placeholder", () => {
289
+ test("substitutes {{USER_PERSONA_FILE}} with users/<slug>.md when userSlug is provided", () => {
290
+ writeFileSync(
291
+ join(TEST_DIR, "BOOTSTRAP.md"),
292
+ "# First run\n\nSave facts to users/{{USER_PERSONA_FILE}} immediately.",
293
+ );
294
+ const result = buildSystemPrompt({ userSlug: "alice" });
295
+ expect(result).toContain("users/alice.md");
296
+ expect(result).not.toContain("{{USER_PERSONA_FILE}}");
297
+ });
298
+
299
+ test("falls back to users/default.md when userSlug is omitted", () => {
300
+ writeFileSync(
301
+ join(TEST_DIR, "BOOTSTRAP.md"),
302
+ "# First run\n\nSave facts to users/{{USER_PERSONA_FILE}} immediately.",
303
+ );
304
+ const result = buildSystemPrompt();
305
+ expect(result).toContain("users/default.md");
306
+ expect(result).not.toContain("{{USER_PERSONA_FILE}}");
307
+ });
308
+
309
+ test("substitutes the unmodified bundled BOOTSTRAP.md template", () => {
310
+ // Copy the real bundled BOOTSTRAP.md into the test workspace so we
311
+ // verify substitution against the actual template the daemon ships.
312
+ const bundled = readFileSync(
313
+ join(
314
+ import.meta.dirname,
315
+ "..",
316
+ "prompts",
317
+ "templates",
318
+ "BOOTSTRAP.md",
319
+ ),
320
+ "utf-8",
321
+ );
322
+ writeFileSync(join(TEST_DIR, "BOOTSTRAP.md"), bundled);
323
+ const result = buildSystemPrompt({ userSlug: "alice" });
324
+ expect(result).toContain("users/alice.md");
325
+ expect(result).not.toContain("{{USER_PERSONA_FILE}}");
326
+ });
277
327
  });
278
328
 
279
329
  describe("app-builder tool ownership guidance", () => {
@@ -541,17 +591,20 @@ describe("ensurePromptFiles", () => {
541
591
  "SOUL.md",
542
592
  "USER.md",
543
593
  "BOOTSTRAP.md",
594
+ "BOOTSTRAP-REFERENCE.md",
595
+ "HEARTBEAT.md",
544
596
  "conversations",
597
+ "users",
545
598
  ]) {
546
599
  const p = join(TEST_DIR, name);
547
600
  if (existsSync(p)) rmSync(p, { recursive: true, force: true });
548
601
  }
549
602
  });
550
603
 
551
- test("creates all 3 files from templates when none exist", () => {
604
+ test("creates SOUL.md and IDENTITY.md from templates when none exist", () => {
552
605
  ensurePromptFiles();
553
606
 
554
- for (const file of ["SOUL.md", "IDENTITY.md", "USER.md"]) {
607
+ for (const file of ["SOUL.md", "IDENTITY.md"]) {
555
608
  const dest = join(TEST_DIR, file);
556
609
  expect(existsSync(dest)).toBe(true);
557
610
  const content = readFileSync(dest, "utf-8");
@@ -559,6 +612,24 @@ describe("ensurePromptFiles", () => {
559
612
  }
560
613
  });
561
614
 
615
+ test("does not seed USER.md", () => {
616
+ // USER.md is no longer part of the seeded prompt files — persona
617
+ // content lives in users/<slug>.md and is resolved via the guardian
618
+ // persona path.
619
+ ensurePromptFiles();
620
+
621
+ expect(existsSync(join(TEST_DIR, "USER.md"))).toBe(false);
622
+ });
623
+
624
+ test("seeds users/default.md persona template", () => {
625
+ ensurePromptFiles();
626
+
627
+ const defaultPersonaPath = join(TEST_DIR, "users", "default.md");
628
+ expect(existsSync(defaultPersonaPath)).toBe(true);
629
+ const content = readFileSync(defaultPersonaPath, "utf-8");
630
+ expect(content.length).toBeGreaterThan(0);
631
+ });
632
+
562
633
  test("does not overwrite existing files", () => {
563
634
  const customContent = "My custom identity";
564
635
  writeFileSync(join(TEST_DIR, "IDENTITY.md"), customContent);
@@ -568,9 +639,8 @@ describe("ensurePromptFiles", () => {
568
639
  const content = readFileSync(join(TEST_DIR, "IDENTITY.md"), "utf-8");
569
640
  expect(content).toBe(customContent);
570
641
 
571
- // Other files should be created
642
+ // The other seeded file should be created
572
643
  expect(existsSync(join(TEST_DIR, "SOUL.md"))).toBe(true);
573
- expect(existsSync(join(TEST_DIR, "USER.md"))).toBe(true);
574
644
  });
575
645
 
576
646
  test("handles missing template gracefully (warn, no crash)", () => {
@@ -595,7 +665,6 @@ describe("ensurePromptFiles", () => {
595
665
  // BOOTSTRAP.md was deleted by the user.
596
666
  writeFileSync(join(TEST_DIR, "IDENTITY.md"), "My identity");
597
667
  writeFileSync(join(TEST_DIR, "SOUL.md"), "My soul");
598
- writeFileSync(join(TEST_DIR, "USER.md"), "My user");
599
668
 
600
669
  ensurePromptFiles();
601
670
 
@@ -613,11 +682,25 @@ describe("ensurePromptFiles", () => {
613
682
  expect(existsSync(bootstrapPath)).toBe(false);
614
683
  });
615
684
 
685
+ test("does not treat a workspace with populated users/ as a first run", () => {
686
+ // Upgraded workspaces may have dropped USER.md but still carry a
687
+ // populated users/ directory. Presence of users/<slug>.md signals an
688
+ // existing install, so BOOTSTRAP.md must not be re-seeded even when
689
+ // SOUL.md and IDENTITY.md are absent (they will be freshly seeded from
690
+ // templates, but onboarding should not re-trigger).
691
+ mkdirSync(join(TEST_DIR, "users"), { recursive: true });
692
+ writeFileSync(join(TEST_DIR, "users", "sidd.md"), "# Sidd persona");
693
+
694
+ ensurePromptFiles();
695
+
696
+ const bootstrapPath = join(TEST_DIR, "BOOTSTRAP.md");
697
+ expect(existsSync(bootstrapPath)).toBe(false);
698
+ });
699
+
616
700
  test("auto-deletes stale BOOTSTRAP.md when prior conversations exist", () => {
617
701
  // Simulate a non-first-run workspace: core files + BOOTSTRAP.md still present
618
702
  writeFileSync(join(TEST_DIR, "IDENTITY.md"), "My identity");
619
703
  writeFileSync(join(TEST_DIR, "SOUL.md"), "My soul");
620
- writeFileSync(join(TEST_DIR, "USER.md"), "My user");
621
704
  writeFileSync(join(TEST_DIR, "BOOTSTRAP.md"), "# Stale bootstrap");
622
705
 
623
706
  // Create a conversations directory with at least one entry
@@ -630,15 +713,18 @@ describe("ensurePromptFiles", () => {
630
713
  expect(existsSync(join(TEST_DIR, "BOOTSTRAP.md"))).toBe(false);
631
714
  });
632
715
 
633
- test("keeps BOOTSTRAP.md on first run even if conversations dir exists", () => {
634
- // First run: no core files exist, BOOTSTRAP.md should be created and kept
716
+ test("does not seed BOOTSTRAP.md when conversations exist even if core files are missing", () => {
717
+ // An upgraded workspace might have dropped SOUL.md/IDENTITY.md (they
718
+ // will be re-seeded from templates) but still carries prior
719
+ // conversations. Existing conversation history signals a non-fresh
720
+ // install, so onboarding must not re-trigger.
635
721
  const convDir = join(TEST_DIR, "conversations");
636
722
  mkdirSync(convDir, { recursive: true });
637
723
  writeFileSync(join(convDir, "conv-001.json"), "{}");
638
724
 
639
725
  ensurePromptFiles();
640
726
 
641
- expect(existsSync(join(TEST_DIR, "BOOTSTRAP.md"))).toBe(true);
727
+ expect(existsSync(join(TEST_DIR, "BOOTSTRAP.md"))).toBe(false);
642
728
  });
643
729
 
644
730
  test("keeps BOOTSTRAP.md when no conversations exist yet", () => {
@@ -0,0 +1,329 @@
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Mocks — must be declared before any subject imports
5
+ // ---------------------------------------------------------------------------
6
+
7
+ // -- Logger mock ----------------------------------------------------------
8
+
9
+ mock.module("../util/logger.js", () => ({
10
+ getLogger: () =>
11
+ new Proxy({} as Record<string, unknown>, {
12
+ get: () => () => {},
13
+ }),
14
+ }));
15
+
16
+ // -- Config mock ----------------------------------------------------------
17
+
18
+ let mockConfig: Record<string, unknown> = {};
19
+
20
+ mock.module("../config/loader.js", () => ({
21
+ getConfig: () => mockConfig,
22
+ loadConfig: () => mockConfig,
23
+ }));
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Subject import (after mocks)
27
+ // ---------------------------------------------------------------------------
28
+
29
+ import {
30
+ type ConversationRelayNativeStrategy,
31
+ type MediaStreamCustomStrategy,
32
+ resolveTelephonySttRouting,
33
+ } from "../calls/telephony-stt-routing.js";
34
+ import {
35
+ getProviderEntry,
36
+ listProviderEntries,
37
+ } from "../providers/speech-to-text/provider-catalog.js";
38
+ import type { SttProviderId } from "../stt/types.js";
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Helpers
42
+ // ---------------------------------------------------------------------------
43
+
44
+ function buildConfig(overrides: {
45
+ provider?: string;
46
+ }): Record<string, unknown> {
47
+ return {
48
+ services: {
49
+ stt: {
50
+ mode: "your-own",
51
+ provider: overrides.provider ?? "deepgram",
52
+ providers: {},
53
+ },
54
+ },
55
+ };
56
+ }
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Tests — Provider-to-strategy mapping (catalog-driven)
60
+ // ---------------------------------------------------------------------------
61
+
62
+ describe("resolveTelephonySttRouting", () => {
63
+ beforeEach(() => {
64
+ mockConfig = buildConfig({});
65
+ });
66
+
67
+ // -----------------------------------------------------------------------
68
+ // Deepgram → conversation-relay-native (from catalog)
69
+ // -----------------------------------------------------------------------
70
+
71
+ describe("deepgram", () => {
72
+ test("resolves to conversation-relay-native with Deepgram transcriptionProvider", () => {
73
+ mockConfig = buildConfig({ provider: "deepgram" });
74
+
75
+ const result = resolveTelephonySttRouting();
76
+
77
+ expect(result.status).toBe("resolved");
78
+ if (result.status !== "resolved") return;
79
+
80
+ expect(result.strategy.strategy).toBe("conversation-relay-native");
81
+ const strategy = result.strategy as ConversationRelayNativeStrategy;
82
+ expect(strategy.providerId).toBe("deepgram");
83
+ expect(strategy.transcriptionProvider).toBe("Deepgram");
84
+ });
85
+
86
+ test("defaults speechModel to nova-3", () => {
87
+ mockConfig = buildConfig({ provider: "deepgram" });
88
+
89
+ const result = resolveTelephonySttRouting();
90
+
91
+ expect(result.status).toBe("resolved");
92
+ if (result.status !== "resolved") return;
93
+
94
+ const strategy = result.strategy as ConversationRelayNativeStrategy;
95
+ expect(strategy.speechModel).toBe("nova-3");
96
+ });
97
+
98
+ test("speechModel matches catalog telephonyRouting.twilioNativeMapping.defaultSpeechModel", () => {
99
+ mockConfig = buildConfig({ provider: "deepgram" });
100
+ const entry = getProviderEntry("deepgram");
101
+
102
+ const result = resolveTelephonySttRouting();
103
+
104
+ expect(result.status).toBe("resolved");
105
+ if (result.status !== "resolved") return;
106
+
107
+ const strategy = result.strategy as ConversationRelayNativeStrategy;
108
+ expect(strategy.speechModel).toBe(
109
+ entry?.telephonyRouting.twilioNativeMapping?.defaultSpeechModel,
110
+ );
111
+ });
112
+ });
113
+
114
+ // -----------------------------------------------------------------------
115
+ // Google Gemini → conversation-relay-native (from catalog)
116
+ // -----------------------------------------------------------------------
117
+
118
+ describe("google-gemini", () => {
119
+ test("resolves to conversation-relay-native with Google transcriptionProvider", () => {
120
+ mockConfig = buildConfig({ provider: "google-gemini" });
121
+
122
+ const result = resolveTelephonySttRouting();
123
+
124
+ expect(result.status).toBe("resolved");
125
+ if (result.status !== "resolved") return;
126
+
127
+ expect(result.strategy.strategy).toBe("conversation-relay-native");
128
+ const strategy = result.strategy as ConversationRelayNativeStrategy;
129
+ expect(strategy.providerId).toBe("google-gemini");
130
+ expect(strategy.transcriptionProvider).toBe("Google");
131
+ });
132
+
133
+ test("leaves speechModel undefined (uses provider default)", () => {
134
+ mockConfig = buildConfig({ provider: "google-gemini" });
135
+
136
+ const result = resolveTelephonySttRouting();
137
+
138
+ expect(result.status).toBe("resolved");
139
+ if (result.status !== "resolved") return;
140
+
141
+ const strategy = result.strategy as ConversationRelayNativeStrategy;
142
+ expect(strategy.speechModel).toBeUndefined();
143
+ });
144
+
145
+ test("speechModel matches catalog telephonyRouting.twilioNativeMapping.defaultSpeechModel", () => {
146
+ mockConfig = buildConfig({ provider: "google-gemini" });
147
+ const entry = getProviderEntry("google-gemini");
148
+
149
+ const result = resolveTelephonySttRouting();
150
+
151
+ expect(result.status).toBe("resolved");
152
+ if (result.status !== "resolved") return;
153
+
154
+ const strategy = result.strategy as ConversationRelayNativeStrategy;
155
+ expect(strategy.speechModel).toBe(
156
+ entry?.telephonyRouting.twilioNativeMapping?.defaultSpeechModel,
157
+ );
158
+ });
159
+ });
160
+
161
+ // -----------------------------------------------------------------------
162
+ // OpenAI Whisper → media-stream-custom (from catalog)
163
+ // -----------------------------------------------------------------------
164
+
165
+ describe("openai-whisper", () => {
166
+ test("resolves to media-stream-custom strategy", () => {
167
+ mockConfig = buildConfig({ provider: "openai-whisper" });
168
+
169
+ const result = resolveTelephonySttRouting();
170
+
171
+ expect(result.status).toBe("resolved");
172
+ if (result.status !== "resolved") return;
173
+
174
+ expect(result.strategy.strategy).toBe("media-stream-custom");
175
+ const strategy = result.strategy as MediaStreamCustomStrategy;
176
+ expect(strategy.providerId).toBe("openai-whisper");
177
+ });
178
+
179
+ test("media-stream-custom strategy does not include speechModel", () => {
180
+ mockConfig = buildConfig({ provider: "openai-whisper" });
181
+
182
+ const result = resolveTelephonySttRouting();
183
+
184
+ expect(result.status).toBe("resolved");
185
+ if (result.status !== "resolved") return;
186
+
187
+ // media-stream-custom has no speechModel property
188
+ expect(result.strategy.strategy).toBe("media-stream-custom");
189
+ expect("speechModel" in result.strategy).toBe(false);
190
+ });
191
+ });
192
+
193
+ // -----------------------------------------------------------------------
194
+ // Unknown / malformed provider handling
195
+ // -----------------------------------------------------------------------
196
+
197
+ describe("unknown provider handling", () => {
198
+ test("returns unknown-provider for a provider not in the catalog", () => {
199
+ mockConfig = buildConfig({ provider: "nonexistent-provider" as string });
200
+
201
+ const result = resolveTelephonySttRouting();
202
+
203
+ expect(result.status).toBe("unknown-provider");
204
+ if (result.status !== "unknown-provider") return;
205
+
206
+ expect(result.providerId).toBe("nonexistent-provider");
207
+ expect(result.reason).toContain("nonexistent-provider");
208
+ expect(result.reason).toContain("not in the provider catalog");
209
+ });
210
+
211
+ test("returns unknown-provider for empty-string provider", () => {
212
+ mockConfig = buildConfig({ provider: "" as string });
213
+
214
+ const result = resolveTelephonySttRouting();
215
+
216
+ expect(result.status).toBe("unknown-provider");
217
+ });
218
+ });
219
+
220
+ // -----------------------------------------------------------------------
221
+ // Strategy discrimination correctness
222
+ // -----------------------------------------------------------------------
223
+
224
+ describe("strategy discrimination", () => {
225
+ test("conversation-relay-native strategies always have transcriptionProvider", () => {
226
+ for (const provider of ["deepgram", "google-gemini"]) {
227
+ mockConfig = buildConfig({ provider });
228
+
229
+ const result = resolveTelephonySttRouting();
230
+ expect(result.status).toBe("resolved");
231
+ if (result.status !== "resolved") return;
232
+
233
+ expect(result.strategy.strategy).toBe("conversation-relay-native");
234
+ const strategy = result.strategy as ConversationRelayNativeStrategy;
235
+ expect(strategy.transcriptionProvider).toBeDefined();
236
+ expect(strategy.transcriptionProvider.length).toBeGreaterThan(0);
237
+ }
238
+ });
239
+
240
+ test("media-stream-custom strategies never have transcriptionProvider", () => {
241
+ mockConfig = buildConfig({ provider: "openai-whisper" });
242
+
243
+ const result = resolveTelephonySttRouting();
244
+ expect(result.status).toBe("resolved");
245
+ if (result.status !== "resolved") return;
246
+
247
+ expect(result.strategy.strategy).toBe("media-stream-custom");
248
+ expect("transcriptionProvider" in result.strategy).toBe(false);
249
+ });
250
+
251
+ test("all resolved strategies include the original providerId", () => {
252
+ const providers: SttProviderId[] = [
253
+ "deepgram",
254
+ "google-gemini",
255
+ "openai-whisper",
256
+ ];
257
+ for (const provider of providers) {
258
+ mockConfig = buildConfig({ provider });
259
+
260
+ const result = resolveTelephonySttRouting();
261
+ expect(result.status).toBe("resolved");
262
+ if (result.status !== "resolved") return;
263
+
264
+ expect(result.strategy.providerId).toBe(provider);
265
+ }
266
+ });
267
+ });
268
+
269
+ // -----------------------------------------------------------------------
270
+ // Catalog-driven mapping verification
271
+ // -----------------------------------------------------------------------
272
+
273
+ describe("catalog-driven mapping", () => {
274
+ test("every catalog entry with conversation-relay-native routing resolves to that strategy", () => {
275
+ const nativeEntries = listProviderEntries().filter(
276
+ (e) => e.telephonyRouting.strategyKind === "conversation-relay-native",
277
+ );
278
+ expect(nativeEntries.length).toBeGreaterThan(0);
279
+
280
+ for (const entry of nativeEntries) {
281
+ mockConfig = buildConfig({ provider: entry.id });
282
+
283
+ const result = resolveTelephonySttRouting();
284
+ expect(result.status).toBe("resolved");
285
+ if (result.status !== "resolved") return;
286
+
287
+ expect(result.strategy.strategy).toBe("conversation-relay-native");
288
+ const strategy = result.strategy as ConversationRelayNativeStrategy;
289
+ expect(strategy.transcriptionProvider).toBe(
290
+ entry.telephonyRouting.twilioNativeMapping!.provider,
291
+ );
292
+ expect(strategy.speechModel).toBe(
293
+ entry.telephonyRouting.twilioNativeMapping!.defaultSpeechModel,
294
+ );
295
+ }
296
+ });
297
+
298
+ test("every catalog entry with media-stream-custom routing resolves to that strategy", () => {
299
+ const customEntries = listProviderEntries().filter(
300
+ (e) => e.telephonyRouting.strategyKind === "media-stream-custom",
301
+ );
302
+ expect(customEntries.length).toBeGreaterThan(0);
303
+
304
+ for (const entry of customEntries) {
305
+ mockConfig = buildConfig({ provider: entry.id });
306
+
307
+ const result = resolveTelephonySttRouting();
308
+ expect(result.status).toBe("resolved");
309
+ if (result.status !== "resolved") return;
310
+
311
+ expect(result.strategy.strategy).toBe("media-stream-custom");
312
+ expect(result.strategy.providerId).toBe(entry.id);
313
+ }
314
+ });
315
+
316
+ test("routing module contains no hardcoded provider-to-Twilio map", async () => {
317
+ // Read the source file and verify the hardcoded map was removed.
318
+ // This is a structural assertion: the catalog is the sole source of truth.
319
+ const sourceFile = Bun.file(
320
+ new URL("../calls/telephony-stt-routing.ts", import.meta.url).pathname,
321
+ );
322
+ const source = await sourceFile.text();
323
+
324
+ expect(source).not.toContain("TWILIO_NATIVE_PROVIDER_MAP");
325
+ expect(source).not.toContain("new Map<SttProviderId");
326
+ expect(source).not.toContain("DEEPGRAM_DEFAULT_SPEECH_MODEL");
327
+ });
328
+ });
329
+ });
@@ -450,8 +450,10 @@ describe("buildSanitizedEnv", () => {
450
450
  delete process.env.LC_ALL;
451
451
 
452
452
  const env = buildSanitizedEnv();
453
- expect(env.LANG).toBe("C.UTF-8");
454
- expect(env.LC_ALL).toBe("C.UTF-8");
453
+ const expectedLocale =
454
+ process.platform === "darwin" ? "en_US.UTF-8" : "C.UTF-8";
455
+ expect(env.LANG).toBe(expectedLocale);
456
+ expect(env.LC_ALL).toBe(expectedLocale);
455
457
  });
456
458
 
457
459
  test("injects INTERNAL_GATEWAY_BASE_URL from gateway config", () => {
@@ -530,12 +532,21 @@ describe("Native sandbox backend", () => {
530
532
  expect(result.sandboxed).toBe(true);
531
533
  });
532
534
 
533
- test("rejects working dir with SBPL metacharacters", () => {
535
+ test("escapes working dir with SBPL metacharacters", () => {
536
+ // SBPL metacharacters (", (, ), ;, \) are backslash-escaped inside the
537
+ // profile string rather than rejected, so wrap() should succeed.
534
538
  const backend = new NativeBackend();
535
- expect(() => backend.wrap("echo hi", '/tmp/foo"bar')).toThrow(ToolError);
536
- expect(() => backend.wrap("echo hi", "/tmp/foo(bar")).toThrow(ToolError);
537
- expect(() => backend.wrap("echo hi", "/tmp/foo;bar")).toThrow(ToolError);
538
- expect(() => backend.wrap("echo hi", "/tmp/foo\\bar")).toThrow(ToolError);
539
+ expect(backend.wrap("echo hi", '/tmp/foo"bar').sandboxed).toBe(true);
540
+ expect(backend.wrap("echo hi", "/tmp/foo(bar").sandboxed).toBe(true);
541
+ expect(backend.wrap("echo hi", "/tmp/foo;bar").sandboxed).toBe(true);
542
+ expect(backend.wrap("echo hi", "/tmp/foo\\bar").sandboxed).toBe(true);
543
+ });
544
+
545
+ test("rejects working dir with newline characters", () => {
546
+ // Newlines/CRs cannot appear in real paths and would break the profile.
547
+ const backend = new NativeBackend();
548
+ expect(() => backend.wrap("echo hi", "/tmp/foo\nbar")).toThrow(ToolError);
549
+ expect(() => backend.wrap("echo hi", "/tmp/foo\rbar")).toThrow(ToolError);
539
550
  });
540
551
 
541
552
  test("accepts working dir with safe special characters", () => {
@@ -16,7 +16,9 @@ import { tmpdir } from "node:os";
16
16
  import { join } from "node:path";
17
17
  import { afterAll } from "bun:test";
18
18
 
19
+ import { installGatewayIpcMock } from "../__tests__/mock-gateway-ipc.js";
19
20
  import { resetDb } from "../memory/db-connection.js";
21
+ import { _setStorePath } from "../security/encrypted-store.js";
20
22
 
21
23
  const testDir = realpathSync(
22
24
  mkdtempSync(join(tmpdir(), "vellum-test-workspace-")),
@@ -25,6 +27,22 @@ process.env.VELLUM_WORKSPACE_DIR = testDir;
25
27
  process.env.VELLUM_PLATFORM_URL = "https://test-platform.vellum.ai";
26
28
  process.exitCode = 0;
27
29
 
30
+ // Isolate the encrypted credential store per test file. Without this,
31
+ // parallel test processes all read/write the same ~/.vellum/protected/keys.enc,
32
+ // causing races when one file deletes a key while another sets it.
33
+ _setStorePath(join(testDir, "keys.enc"));
34
+
35
+ // Mock gateway IPC so no test accidentally connects to a real gateway socket.
36
+ // Tests that need to control IPC responses use mockGatewayIpc() / resetMockGatewayIpc().
37
+ installGatewayIpcMock();
38
+
39
+ // Force-close any DB connection inherited from the parent process (e.g. when
40
+ // the test runner is spawned by the running assistant via a pre-push hook).
41
+ // Without this, the db singleton in db-connection.ts may still point at the
42
+ // real ~/.vellum/workspace database, and test cleanup (DELETE FROM …) would
43
+ // wipe production data — contacts, channels, credentials, etc.
44
+ resetDb();
45
+
28
46
  // Prevent tests from routing credential writes through the real CES
29
47
  // (Credential Execution Service). Without this, setSecureKeyAsync() in
30
48
  // containerized environments writes to the live credential store.
@@ -1,11 +1,13 @@
1
1
  import type { Message } from "../../providers/types.js";
2
2
 
3
- /** The 14 browser tool names provided by the browser skill. */
3
+ /** The browser tool names provided by the browser skill. */
4
4
  export const BROWSER_TOOL_NAMES = [
5
5
  "browser_navigate",
6
6
  "browser_snapshot",
7
7
  "browser_screenshot",
8
8
  "browser_close",
9
+ "browser_attach",
10
+ "browser_detach",
9
11
  "browser_click",
10
12
  "browser_type",
11
13
  "browser_press_key",
@@ -16,6 +18,7 @@ export const BROWSER_TOOL_NAMES = [
16
18
  "browser_extract",
17
19
  "browser_wait_for_download",
18
20
  "browser_fill_credential",
21
+ "browser_status",
19
22
  ] as const;
20
23
 
21
24
  /** Number of browser tools provided by the skill. */