@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,440 @@
1
+ import { afterEach, beforeEach, describe, expect, jest, test } from "bun:test";
2
+
3
+ import { MediaTurnDetector } from "../calls/media-turn-detector.js";
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Helpers
7
+ // ---------------------------------------------------------------------------
8
+
9
+ /**
10
+ * Advance fake timers by `ms` milliseconds. Uses Bun's `jest.advanceTimersByTime`.
11
+ */
12
+ function advance(ms: number): void {
13
+ jest.advanceTimersByTime(ms);
14
+ }
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Tests
18
+ // ---------------------------------------------------------------------------
19
+
20
+ describe("MediaTurnDetector", () => {
21
+ beforeEach(() => {
22
+ jest.useFakeTimers();
23
+ });
24
+
25
+ afterEach(() => {
26
+ jest.useRealTimers();
27
+ });
28
+
29
+ // ── Basic lifecycle ──────────────────────────────────────────────
30
+
31
+ test("starts inactive", () => {
32
+ const detector = new MediaTurnDetector();
33
+ expect(detector.isActive).toBe(false);
34
+ detector.dispose();
35
+ });
36
+
37
+ test("transitions to active on first chunk", () => {
38
+ const detector = new MediaTurnDetector();
39
+ detector.onMediaChunk();
40
+ expect(detector.isActive).toBe(true);
41
+ detector.dispose();
42
+ });
43
+
44
+ // ── Silence detection ────────────────────────────────────────────
45
+
46
+ test("fires onTurnEnd with 'silence' after silence threshold", () => {
47
+ const onTurnStart = jest.fn();
48
+ const onTurnEnd = jest.fn();
49
+
50
+ const detector = new MediaTurnDetector(
51
+ { silenceThresholdMs: 500 },
52
+ { onTurnStart, onTurnEnd },
53
+ );
54
+
55
+ detector.onMediaChunk();
56
+ expect(onTurnStart).toHaveBeenCalledTimes(1);
57
+ expect(detector.isActive).toBe(true);
58
+
59
+ // Advance past the silence threshold
60
+ advance(600);
61
+
62
+ expect(onTurnEnd).toHaveBeenCalledTimes(1);
63
+ expect(onTurnEnd).toHaveBeenCalledWith("silence", expect.any(Number));
64
+ expect(detector.isActive).toBe(false);
65
+
66
+ detector.dispose();
67
+ });
68
+
69
+ test("resets silence timer on subsequent chunks", () => {
70
+ const onTurnEnd = jest.fn();
71
+
72
+ const detector = new MediaTurnDetector(
73
+ { silenceThresholdMs: 500 },
74
+ { onTurnEnd },
75
+ );
76
+
77
+ detector.onMediaChunk();
78
+
79
+ // 300ms in — silence timer has NOT fired yet
80
+ advance(300);
81
+ expect(onTurnEnd).not.toHaveBeenCalled();
82
+
83
+ // New chunk resets the 500ms silence timer
84
+ detector.onMediaChunk();
85
+
86
+ // Another 300ms — still within the reset window
87
+ advance(300);
88
+ expect(onTurnEnd).not.toHaveBeenCalled();
89
+
90
+ // 250ms more (550ms since last chunk) — past threshold
91
+ advance(250);
92
+ expect(onTurnEnd).toHaveBeenCalledTimes(1);
93
+ expect(onTurnEnd).toHaveBeenCalledWith("silence", expect.any(Number));
94
+
95
+ detector.dispose();
96
+ });
97
+
98
+ // ── Max duration ─────────────────────────────────────────────────
99
+
100
+ test("fires onTurnEnd with 'max-duration' when hard cap is reached", () => {
101
+ const onTurnEnd = jest.fn();
102
+
103
+ const detector = new MediaTurnDetector(
104
+ { silenceThresholdMs: 500, maxTurnDurationMs: 2000 },
105
+ { onTurnEnd },
106
+ );
107
+
108
+ detector.onMediaChunk();
109
+
110
+ // Keep feeding chunks so the silence timer never fires
111
+ for (let i = 0; i < 8; i++) {
112
+ advance(200);
113
+ detector.onMediaChunk();
114
+ }
115
+
116
+ // At 1600ms, still active. Advance to 2000ms.
117
+ advance(400);
118
+
119
+ expect(onTurnEnd).toHaveBeenCalledTimes(1);
120
+ expect(onTurnEnd).toHaveBeenCalledWith("max-duration", expect.any(Number));
121
+ expect(detector.isActive).toBe(false);
122
+
123
+ detector.dispose();
124
+ });
125
+
126
+ // ── Turn restart ─────────────────────────────────────────────────
127
+
128
+ test("can start a new turn after silence ends the previous one", () => {
129
+ const onTurnStart = jest.fn();
130
+ const onTurnEnd = jest.fn();
131
+
132
+ const detector = new MediaTurnDetector(
133
+ { silenceThresholdMs: 500 },
134
+ { onTurnStart, onTurnEnd },
135
+ );
136
+
137
+ // First turn
138
+ detector.onMediaChunk();
139
+ expect(onTurnStart).toHaveBeenCalledTimes(1);
140
+ advance(600);
141
+ expect(onTurnEnd).toHaveBeenCalledTimes(1);
142
+ expect(detector.isActive).toBe(false);
143
+
144
+ // Second turn
145
+ detector.onMediaChunk();
146
+ expect(onTurnStart).toHaveBeenCalledTimes(2);
147
+ expect(detector.isActive).toBe(true);
148
+
149
+ advance(600);
150
+ expect(onTurnEnd).toHaveBeenCalledTimes(2);
151
+
152
+ detector.dispose();
153
+ });
154
+
155
+ // ── forceEnd ─────────────────────────────────────────────────────
156
+
157
+ test("forceEnd ends the current turn immediately", () => {
158
+ const onTurnEnd = jest.fn();
159
+
160
+ const detector = new MediaTurnDetector(
161
+ { silenceThresholdMs: 500 },
162
+ { onTurnEnd },
163
+ );
164
+
165
+ detector.onMediaChunk();
166
+ expect(detector.isActive).toBe(true);
167
+
168
+ detector.forceEnd();
169
+ expect(onTurnEnd).toHaveBeenCalledTimes(1);
170
+ expect(onTurnEnd).toHaveBeenCalledWith("silence", expect.any(Number));
171
+ expect(detector.isActive).toBe(false);
172
+
173
+ detector.dispose();
174
+ });
175
+
176
+ test("forceEnd is a no-op when inactive", () => {
177
+ const onTurnEnd = jest.fn();
178
+
179
+ const detector = new MediaTurnDetector(
180
+ { silenceThresholdMs: 500 },
181
+ { onTurnEnd },
182
+ );
183
+
184
+ detector.forceEnd();
185
+ expect(onTurnEnd).not.toHaveBeenCalled();
186
+ expect(detector.isActive).toBe(false);
187
+
188
+ detector.dispose();
189
+ });
190
+
191
+ // ── dispose ──────────────────────────────────────────────────────
192
+
193
+ test("dispose prevents further callbacks", () => {
194
+ const onTurnStart = jest.fn();
195
+ const onTurnEnd = jest.fn();
196
+
197
+ const detector = new MediaTurnDetector(
198
+ { silenceThresholdMs: 500 },
199
+ { onTurnStart, onTurnEnd },
200
+ );
201
+
202
+ detector.onMediaChunk();
203
+ expect(onTurnStart).toHaveBeenCalledTimes(1);
204
+
205
+ detector.dispose();
206
+
207
+ // Silence timer should have been cleared — advancing should not
208
+ // trigger onTurnEnd.
209
+ advance(1000);
210
+ expect(onTurnEnd).not.toHaveBeenCalled();
211
+
212
+ // Further chunks should be ignored.
213
+ detector.onMediaChunk();
214
+ expect(onTurnStart).toHaveBeenCalledTimes(1);
215
+ });
216
+
217
+ test("dispose + forceEnd is a no-op", () => {
218
+ const onTurnEnd = jest.fn();
219
+
220
+ const detector = new MediaTurnDetector(
221
+ { silenceThresholdMs: 500 },
222
+ { onTurnEnd },
223
+ );
224
+
225
+ detector.onMediaChunk();
226
+ detector.dispose();
227
+ detector.forceEnd();
228
+ expect(onTurnEnd).not.toHaveBeenCalled();
229
+ });
230
+
231
+ // ── Default config ───────────────────────────────────────────────
232
+
233
+ test("uses default thresholds when config is omitted", () => {
234
+ const onTurnEnd = jest.fn();
235
+ const detector = new MediaTurnDetector({}, { onTurnEnd });
236
+
237
+ detector.onMediaChunk();
238
+
239
+ // Default silence threshold is 800ms
240
+ advance(700);
241
+ expect(onTurnEnd).not.toHaveBeenCalled();
242
+ advance(200);
243
+ expect(onTurnEnd).toHaveBeenCalledTimes(1);
244
+
245
+ detector.dispose();
246
+ });
247
+
248
+ // ── onTurnStart only fires once per turn ─────────────────────────
249
+
250
+ test("onTurnStart fires only once even with many chunks", () => {
251
+ const onTurnStart = jest.fn();
252
+ const detector = new MediaTurnDetector(
253
+ { silenceThresholdMs: 500 },
254
+ { onTurnStart },
255
+ );
256
+
257
+ detector.onMediaChunk();
258
+ detector.onMediaChunk();
259
+ detector.onMediaChunk();
260
+ detector.onMediaChunk();
261
+
262
+ expect(onTurnStart).toHaveBeenCalledTimes(1);
263
+
264
+ detector.dispose();
265
+ });
266
+
267
+ // ── Speech-aware segmentation ─────────────────────────────────────
268
+
269
+ describe("speech-aware segmentation", () => {
270
+ test("continuous chunk flow with speech->silence transition ends the turn", () => {
271
+ const onTurnStart = jest.fn();
272
+ const onTurnEnd = jest.fn();
273
+
274
+ const detector = new MediaTurnDetector(
275
+ { silenceThresholdMs: 500 },
276
+ { onTurnStart, onTurnEnd },
277
+ );
278
+
279
+ // Speech chunks — resets silence timer on each
280
+ detector.onMediaChunk(true);
281
+ expect(onTurnStart).toHaveBeenCalledTimes(1);
282
+
283
+ advance(100);
284
+ detector.onMediaChunk(true);
285
+ advance(100);
286
+ detector.onMediaChunk(true);
287
+
288
+ // Transition to silence — continuous silent chunks should NOT
289
+ // reset the silence timer, so the turn ends after the threshold.
290
+ advance(100);
291
+ detector.onMediaChunk(false);
292
+ advance(100);
293
+ detector.onMediaChunk(false);
294
+ advance(100);
295
+ detector.onMediaChunk(false);
296
+
297
+ // Not yet past threshold from last speech chunk (300ms of silence
298
+ // plus whatever the timer started at when silence began)
299
+ expect(onTurnEnd).not.toHaveBeenCalled();
300
+
301
+ // Advance past the silence threshold from the last speech chunk
302
+ advance(500);
303
+
304
+ expect(onTurnEnd).toHaveBeenCalledTimes(1);
305
+ expect(onTurnEnd).toHaveBeenCalledWith("silence", expect.any(Number));
306
+ expect(detector.isActive).toBe(false);
307
+
308
+ detector.dispose();
309
+ });
310
+
311
+ test("no-speech continuous noise/silence does not start a turn", () => {
312
+ const onTurnStart = jest.fn();
313
+ const onTurnEnd = jest.fn();
314
+
315
+ const detector = new MediaTurnDetector(
316
+ { silenceThresholdMs: 500 },
317
+ { onTurnStart, onTurnEnd },
318
+ );
319
+
320
+ // Send many chunks with no speech — turn should never start
321
+ for (let i = 0; i < 20; i++) {
322
+ detector.onMediaChunk(false);
323
+ advance(50);
324
+ }
325
+
326
+ expect(onTurnStart).not.toHaveBeenCalled();
327
+ expect(onTurnEnd).not.toHaveBeenCalled();
328
+ expect(detector.isActive).toBe(false);
329
+
330
+ detector.dispose();
331
+ });
332
+
333
+ test("max-duration fallback still fires with continuous speech", () => {
334
+ const onTurnEnd = jest.fn();
335
+
336
+ const detector = new MediaTurnDetector(
337
+ { silenceThresholdMs: 500, maxTurnDurationMs: 2000 },
338
+ { onTurnEnd },
339
+ );
340
+
341
+ // Continuous speech chunks — silence timer keeps resetting
342
+ detector.onMediaChunk(true);
343
+ for (let i = 0; i < 10; i++) {
344
+ advance(180);
345
+ detector.onMediaChunk(true);
346
+ }
347
+
348
+ // At ~1800ms. Advance to 2000ms to hit max-duration.
349
+ advance(200);
350
+
351
+ expect(onTurnEnd).toHaveBeenCalledTimes(1);
352
+ expect(onTurnEnd).toHaveBeenCalledWith(
353
+ "max-duration",
354
+ expect.any(Number),
355
+ );
356
+ expect(detector.isActive).toBe(false);
357
+
358
+ detector.dispose();
359
+ });
360
+
361
+ test("silent chunks during active turn do not reset the silence timer", () => {
362
+ const onTurnEnd = jest.fn();
363
+
364
+ const detector = new MediaTurnDetector(
365
+ { silenceThresholdMs: 500 },
366
+ { onTurnEnd },
367
+ );
368
+
369
+ // Start with speech
370
+ detector.onMediaChunk(true);
371
+
372
+ // Advance 200ms, then send silent chunks — they should NOT
373
+ // extend the turn by resetting the timer.
374
+ advance(200);
375
+ detector.onMediaChunk(false);
376
+ advance(100);
377
+ detector.onMediaChunk(false);
378
+ advance(100);
379
+ detector.onMediaChunk(false);
380
+
381
+ // Silence timer started from the last speech chunk. At 500ms
382
+ // from that point, the turn should end.
383
+ advance(200); // 200+100+100+200 = 600ms since last speech
384
+
385
+ expect(onTurnEnd).toHaveBeenCalledTimes(1);
386
+ expect(onTurnEnd).toHaveBeenCalledWith("silence", expect.any(Number));
387
+
388
+ detector.dispose();
389
+ });
390
+
391
+ test("speech resuming during silence countdown resets the timer", () => {
392
+ const onTurnEnd = jest.fn();
393
+
394
+ const detector = new MediaTurnDetector(
395
+ { silenceThresholdMs: 500 },
396
+ { onTurnEnd },
397
+ );
398
+
399
+ // Speech starts
400
+ detector.onMediaChunk(true);
401
+
402
+ // 300ms of silence
403
+ advance(300);
404
+ detector.onMediaChunk(false);
405
+
406
+ // Speech resumes — resets silence timer
407
+ advance(100);
408
+ detector.onMediaChunk(true);
409
+
410
+ // Timer reset: another 500ms must pass
411
+ advance(400);
412
+ expect(onTurnEnd).not.toHaveBeenCalled();
413
+
414
+ advance(200);
415
+ expect(onTurnEnd).toHaveBeenCalledTimes(1);
416
+
417
+ detector.dispose();
418
+ });
419
+
420
+ test("backwards compatibility: onMediaChunk without hasSpeech argument defaults to true", () => {
421
+ const onTurnStart = jest.fn();
422
+ const onTurnEnd = jest.fn();
423
+
424
+ const detector = new MediaTurnDetector(
425
+ { silenceThresholdMs: 500 },
426
+ { onTurnStart, onTurnEnd },
427
+ );
428
+
429
+ // Call without argument — defaults to hasSpeech=true
430
+ detector.onMediaChunk();
431
+ expect(onTurnStart).toHaveBeenCalledTimes(1);
432
+ expect(detector.isActive).toBe(true);
433
+
434
+ advance(600);
435
+ expect(onTurnEnd).toHaveBeenCalledTimes(1);
436
+
437
+ detector.dispose();
438
+ });
439
+ });
440
+ });
@@ -0,0 +1,125 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ DEFAULT_MAX_QUEUE_BYTES,
5
+ MessageQueue,
6
+ type QueuedMessage,
7
+ } from "../daemon/conversation-queue-manager.js";
8
+
9
+ function makeItem(content: string, requestId = "r"): QueuedMessage {
10
+ return {
11
+ content,
12
+ attachments: [],
13
+ requestId,
14
+ onEvent: () => {},
15
+ sentAt: Date.now(),
16
+ };
17
+ }
18
+
19
+ describe("MessageQueue.peek", () => {
20
+ test("peek(0) on empty queue returns undefined", () => {
21
+ const q = new MessageQueue();
22
+ expect(q.peek(0)).toBeUndefined();
23
+ });
24
+
25
+ test("peek(0) on non-empty queue returns head without mutating length or totalBytes", () => {
26
+ const q = new MessageQueue();
27
+ q.push(makeItem("first", "r1"));
28
+ q.push(makeItem("second", "r2"));
29
+
30
+ const lengthBefore = q.length;
31
+ const bytesBefore = q.totalBytes;
32
+
33
+ const head = q.peek(0);
34
+ expect(head).toBeDefined();
35
+ expect(head?.requestId).toBe("r1");
36
+ expect(head?.content).toBe("first");
37
+
38
+ expect(q.length).toBe(lengthBefore);
39
+ expect(q.totalBytes).toBe(bytesBefore);
40
+ });
41
+
42
+ test("peek(2) returns the third item; peek(99) returns undefined", () => {
43
+ const q = new MessageQueue();
44
+ q.push(makeItem("a", "r1"));
45
+ q.push(makeItem("b", "r2"));
46
+ q.push(makeItem("c", "r3"));
47
+
48
+ expect(q.peek(2)?.requestId).toBe("r3");
49
+ expect(q.peek(99)).toBeUndefined();
50
+ });
51
+ });
52
+
53
+ describe("MessageQueue.shiftN", () => {
54
+ test("shiftN(0) returns [] and does not mutate", () => {
55
+ const q = new MessageQueue();
56
+ q.push(makeItem("a", "r1"));
57
+ q.push(makeItem("b", "r2"));
58
+
59
+ const lengthBefore = q.length;
60
+ const bytesBefore = q.totalBytes;
61
+
62
+ const popped = q.shiftN(0);
63
+ expect(popped).toEqual([]);
64
+ expect(q.length).toBe(lengthBefore);
65
+ expect(q.totalBytes).toBe(bytesBefore);
66
+ });
67
+
68
+ test("shiftN(2) on a 3-item queue returns the first two in FIFO order; length becomes 1; totalBytes matches remaining", () => {
69
+ const q = new MessageQueue();
70
+ q.push(makeItem("a", "r1"));
71
+ q.push(makeItem("b", "r2"));
72
+ q.push(makeItem("c", "r3"));
73
+
74
+ // Capture the remaining-item bytes by draining a fresh queue with just "c".
75
+ const reference = new MessageQueue();
76
+ reference.push(makeItem("c", "r3"));
77
+ const expectedRemainingBytes = reference.totalBytes;
78
+
79
+ const popped = q.shiftN(2);
80
+ expect(popped.map((m) => m.requestId)).toEqual(["r1", "r2"]);
81
+ expect(q.length).toBe(1);
82
+ expect(q.totalBytes).toBe(expectedRemainingBytes);
83
+ expect(q.peek(0)?.requestId).toBe("r3");
84
+ });
85
+
86
+ test("shiftN(99) on a 3-item queue returns all three; length becomes 0; totalBytes becomes 0", () => {
87
+ const q = new MessageQueue();
88
+ q.push(makeItem("a", "r1"));
89
+ q.push(makeItem("b", "r2"));
90
+ q.push(makeItem("c", "r3"));
91
+
92
+ const popped = q.shiftN(99);
93
+ expect(popped.map((m) => m.requestId)).toEqual(["r1", "r2", "r3"]);
94
+ expect(q.length).toBe(0);
95
+ expect(q.totalBytes).toBe(0);
96
+ });
97
+
98
+ test("after shiftN, a subsequent push up to the budget still succeeds (byte accounting not drifted)", () => {
99
+ // Tight budget so we can verify the freed bytes are exactly correct.
100
+ // Each 500-char item ≈ 500*2 + 512 = 1512 bytes.
101
+ // Budget of 3000 fits two items (3024 > 3000 — actually only one, since 1512+1512=3024 exceeds).
102
+ // Use 4000: fits two (3024) but not three (4536).
103
+ const q = new MessageQueue(4_000);
104
+ expect(q.push(makeItem("a".repeat(500), "r1"))).toBe(true);
105
+ expect(q.push(makeItem("b".repeat(500), "r2"))).toBe(true);
106
+ expect(q.push(makeItem("c".repeat(500), "r3"))).toBe(false);
107
+
108
+ // Drain both — budget should be fully reclaimed.
109
+ const popped = q.shiftN(2);
110
+ expect(popped).toHaveLength(2);
111
+ expect(q.totalBytes).toBe(0);
112
+
113
+ // Now we should be able to push two fresh items again.
114
+ expect(q.push(makeItem("d".repeat(500), "r4"))).toBe(true);
115
+ expect(q.push(makeItem("e".repeat(500), "r5"))).toBe(true);
116
+ expect(q.length).toBe(2);
117
+ });
118
+ });
119
+
120
+ describe("MessageQueue exports", () => {
121
+ test("DEFAULT_MAX_QUEUE_BYTES is importable and positive", () => {
122
+ expect(typeof DEFAULT_MAX_QUEUE_BYTES).toBe("number");
123
+ expect(DEFAULT_MAX_QUEUE_BYTES).toBeGreaterThan(0);
124
+ });
125
+ });
@@ -508,7 +508,7 @@ describe("route policy registration", () => {
508
508
  const policy = getPolicy("migrations/validate");
509
509
 
510
510
  expect(policy).toBeDefined();
511
- expect(policy?.requiredScopes).toContain("settings.write");
511
+ expect(policy?.requiredScopes).toContain("settings.read");
512
512
  });
513
513
  });
514
514
 
@@ -531,15 +531,15 @@ describe("auth policy shape", () => {
531
531
  expect(policy!.allowedPrincipalTypes).toHaveLength(4);
532
532
  });
533
533
 
534
- test("export policy matches validate policy shape", async () => {
534
+ test("export policy differs from validate policy on scopes (validate is read-only)", async () => {
535
535
  const { getPolicy } = await import("../runtime/auth/route-policy.js");
536
536
  const exportPolicy = getPolicy("migrations/export");
537
537
  const validatePolicy = getPolicy("migrations/validate");
538
538
 
539
- // Both migration endpoints should have the same auth requirements
540
- expect(exportPolicy!.requiredScopes).toEqual(
541
- validatePolicy!.requiredScopes,
542
- );
539
+ // validate is read-only so requires settings.read; export requires settings.write
540
+ expect(exportPolicy!.requiredScopes).toEqual(["settings.write"]);
541
+ expect(validatePolicy!.requiredScopes).toEqual(["settings.read"]);
542
+ // Both share the same principal types
543
543
  expect(exportPolicy!.allowedPrincipalTypes).toEqual(
544
544
  validatePolicy!.allowedPrincipalTypes,
545
545
  );
@@ -988,23 +988,25 @@ describe("route policy registration", () => {
988
988
  expect(policy?.allowedPrincipalTypes).toContain("local");
989
989
  });
990
990
 
991
- test("import policy matches other migration endpoint policies", async () => {
991
+ test("import policy matches export/preflight but differs from validate on scopes", async () => {
992
992
  const { getPolicy } = await import("../runtime/auth/route-policy.js");
993
993
  const importPolicy = getPolicy("migrations/import");
994
994
  const validatePolicy = getPolicy("migrations/validate");
995
995
  const exportPolicy = getPolicy("migrations/export");
996
996
  const preflightPolicy = getPolicy("migrations/import-preflight");
997
997
 
998
+ // import, export, and preflight all require settings.write
999
+ expect(importPolicy!.requiredScopes).toEqual(exportPolicy!.requiredScopes);
998
1000
  expect(importPolicy!.requiredScopes).toEqual(
999
- validatePolicy!.requiredScopes,
1001
+ preflightPolicy!.requiredScopes,
1000
1002
  );
1003
+ expect(importPolicy!.requiredScopes).toEqual(["settings.write"]);
1004
+ // validate is read-only so requires settings.read
1005
+ expect(validatePolicy!.requiredScopes).toEqual(["settings.read"]);
1006
+ // all share the same principal types
1001
1007
  expect(importPolicy!.allowedPrincipalTypes).toEqual(
1002
1008
  validatePolicy!.allowedPrincipalTypes,
1003
1009
  );
1004
- expect(importPolicy!.requiredScopes).toEqual(exportPolicy!.requiredScopes);
1005
- expect(importPolicy!.requiredScopes).toEqual(
1006
- preflightPolicy!.requiredScopes,
1007
- );
1008
1010
  });
1009
1011
  });
1010
1012
 
@@ -720,21 +720,22 @@ describe("route policy registration", () => {
720
720
  expect(policy?.allowedPrincipalTypes).toContain("local");
721
721
  });
722
722
 
723
- test("import-preflight policy matches validate/export policy shape", async () => {
723
+ test("import-preflight policy matches export but differs from validate on scopes", async () => {
724
724
  const { getPolicy } = await import("../runtime/auth/route-policy.js");
725
725
  const preflightPolicy = getPolicy("migrations/import-preflight");
726
726
  const validatePolicy = getPolicy("migrations/validate");
727
727
  const exportPolicy = getPolicy("migrations/export");
728
728
 
729
+ // preflight and export both require settings.write
729
730
  expect(preflightPolicy!.requiredScopes).toEqual(
730
- validatePolicy!.requiredScopes,
731
+ exportPolicy!.requiredScopes,
731
732
  );
733
+ // validate is read-only so requires settings.read
734
+ expect(validatePolicy!.requiredScopes).toEqual(["settings.read"]);
735
+ // all share the same principal types
732
736
  expect(preflightPolicy!.allowedPrincipalTypes).toEqual(
733
737
  validatePolicy!.allowedPrincipalTypes,
734
738
  );
735
- expect(preflightPolicy!.requiredScopes).toEqual(
736
- exportPolicy!.requiredScopes,
737
- );
738
739
  });
739
740
  });
740
741
 
@@ -5,7 +5,7 @@
5
5
  * - Success: valid .vbundle archive returns is_valid: true with manifest
6
6
  * - Validation failures: invalid gzip, missing entries, bad manifest, checksum mismatches
7
7
  * - Request errors: empty body, invalid multipart
8
- * - Auth: route policy enforcement (settings.write scope required)
8
+ * - Auth: route policy enforcement (settings.read scope required)
9
9
  * - Integration: existing routes are unaffected by the new endpoint
10
10
  */
11
11
  import { createHash } from "node:crypto";
@@ -638,13 +638,13 @@ describe("handleMigrationValidate", () => {
638
638
  // ---------------------------------------------------------------------------
639
639
 
640
640
  describe("route policy registration", () => {
641
- test("migrations/validate policy requires settings.write scope", async () => {
641
+ test("migrations/validate policy requires settings.read scope", async () => {
642
642
  // Import route-policy to verify the registration exists
643
643
  const { getPolicy } = await import("../runtime/auth/route-policy.js");
644
644
  const policy = getPolicy("migrations/validate");
645
645
 
646
646
  expect(policy).toBeDefined();
647
- expect(policy?.requiredScopes).toContain("settings.write");
647
+ expect(policy?.requiredScopes).toContain("settings.read");
648
648
  expect(policy?.allowedPrincipalTypes).toContain("actor");
649
649
  expect(policy?.allowedPrincipalTypes).toContain("svc_gateway");
650
650
  expect(policy?.allowedPrincipalTypes).toContain("local");