@vellumai/assistant 0.6.2 → 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 (895) hide show
  1. package/ARCHITECTURE.md +273 -10
  2. package/Dockerfile +2 -3
  3. package/bun.lock +41 -49
  4. package/bunfig.toml +3 -0
  5. package/docs/architecture/memory.md +1 -1
  6. package/docs/backup-troubleshooting.md +52 -0
  7. package/docs/browser-use-architecture-phase2.md +174 -0
  8. package/docs/stt-provider-onboarding.md +120 -0
  9. package/knip.json +12 -2
  10. package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
  11. package/node_modules/@vellumai/ces-contracts/package.json +3 -3
  12. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
  13. package/openapi.yaml +1111 -86
  14. package/package.json +40 -42
  15. package/scripts/generate-openapi.ts +0 -2
  16. package/scripts/test.sh +73 -18
  17. package/src/__tests__/acp-session.test.ts +43 -0
  18. package/src/__tests__/agent-image-optimize.test.ts +28 -0
  19. package/src/__tests__/agent-loop.test.ts +123 -0
  20. package/src/__tests__/anthropic-provider.test.ts +263 -10
  21. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
  22. package/src/__tests__/app-executors.test.ts +1 -0
  23. package/src/__tests__/app-source-watcher.test.ts +37 -11
  24. package/src/__tests__/approval-routes-http.test.ts +178 -1
  25. package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
  26. package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
  27. package/src/__tests__/browser-fill-credential.test.ts +240 -94
  28. package/src/__tests__/browser-manager.test.ts +40 -27
  29. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  30. package/src/__tests__/browser-skill-endstate.test.ts +31 -7
  31. package/src/__tests__/btw-routes.test.ts +7 -0
  32. package/src/__tests__/call-controller.test.ts +581 -20
  33. package/src/__tests__/catalog-files.test.ts +1000 -0
  34. package/src/__tests__/channel-approvals.test.ts +53 -0
  35. package/src/__tests__/channel-invite-transport.test.ts +2 -2
  36. package/src/__tests__/channel-readiness-routes.test.ts +16 -20
  37. package/src/__tests__/channel-readiness-service.test.ts +12 -7
  38. package/src/__tests__/checker.test.ts +157 -10
  39. package/src/__tests__/clawhub-files.test.ts +347 -0
  40. package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
  41. package/src/__tests__/config-analysis.test.ts +100 -0
  42. package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
  43. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  44. package/src/__tests__/config-schema.test.ts +1248 -224
  45. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
  46. package/src/__tests__/config-watcher.test.ts +43 -8
  47. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
  48. package/src/__tests__/contact-store-user-file.test.ts +512 -0
  49. package/src/__tests__/contacts-write.test.ts +197 -0
  50. package/src/__tests__/context-overflow-approval.test.ts +16 -1
  51. package/src/__tests__/context-window-manager.test.ts +88 -0
  52. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  53. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -1
  54. package/src/__tests__/conversation-agent-loop.test.ts +99 -3
  55. package/src/__tests__/conversation-analysis-routes.test.ts +2 -2
  56. package/src/__tests__/conversation-attachments.test.ts +80 -4
  57. package/src/__tests__/conversation-confirmation-signals.test.ts +290 -0
  58. package/src/__tests__/conversation-error.test.ts +70 -0
  59. package/src/__tests__/conversation-fork-crud.test.ts +17 -0
  60. package/src/__tests__/conversation-history-web-search.test.ts +12 -4
  61. package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
  62. package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
  63. package/src/__tests__/conversation-inject-context.test.ts +103 -0
  64. package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
  65. package/src/__tests__/conversation-list-source.test.ts +145 -0
  66. package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
  67. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  68. package/src/__tests__/conversation-queue.test.ts +946 -62
  69. package/src/__tests__/conversation-routes-disk-view.test.ts +275 -0
  70. package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
  71. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  72. package/src/__tests__/conversation-runtime-assembly.test.ts +324 -46
  73. package/src/__tests__/conversation-skill-tools.test.ts +7 -4
  74. package/src/__tests__/conversation-slash-commands.test.ts +33 -0
  75. package/src/__tests__/conversation-slash-queue.test.ts +89 -18
  76. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  77. package/src/__tests__/conversation-starter-routes.test.ts +126 -0
  78. package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
  79. package/src/__tests__/conversation-store.test.ts +195 -0
  80. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
  81. package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
  82. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  83. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  84. package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -1
  85. package/src/__tests__/credential-health-service.test.ts +352 -0
  86. package/src/__tests__/credential-security-invariants.test.ts +6 -3
  87. package/src/__tests__/credential-vault-unit.test.ts +383 -7
  88. package/src/__tests__/credential-vault.test.ts +152 -13
  89. package/src/__tests__/credentials-cli.test.ts +42 -18
  90. package/src/__tests__/cross-provider-web-search.test.ts +146 -35
  91. package/src/__tests__/date-context.test.ts +4 -4
  92. package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
  93. package/src/__tests__/device-id.test.ts +112 -0
  94. package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
  95. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
  96. package/src/__tests__/email-html-renderer.test.ts +71 -0
  97. package/src/__tests__/email-invite-adapter.test.ts +36 -32
  98. package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
  99. package/src/__tests__/emit-event-signal.test.ts +71 -0
  100. package/src/__tests__/extension-id-sync-guard.test.ts +222 -0
  101. package/src/__tests__/fixtures/mock-chrome-extension.ts +386 -0
  102. package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
  103. package/src/__tests__/gateway-only-guard.test.ts +2 -0
  104. package/src/__tests__/gemini-provider.test.ts +66 -2
  105. package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
  106. package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
  107. package/src/__tests__/gmail-archive-gate.test.ts +246 -0
  108. package/src/__tests__/gmail-preferences.test.ts +117 -0
  109. package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
  110. package/src/__tests__/headless-browser-interactions.test.ts +738 -359
  111. package/src/__tests__/headless-browser-mode.test.ts +614 -0
  112. package/src/__tests__/headless-browser-navigate.test.ts +528 -49
  113. package/src/__tests__/headless-browser-read-tools.test.ts +274 -100
  114. package/src/__tests__/headless-browser-snapshot.test.ts +250 -77
  115. package/src/__tests__/heartbeat-service.test.ts +70 -17
  116. package/src/__tests__/home-state-routes.test.ts +162 -0
  117. package/src/__tests__/host-bash-proxy.test.ts +145 -1
  118. package/src/__tests__/host-browser-e2e-cloud.test.ts +596 -0
  119. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
  120. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
  121. package/src/__tests__/host-browser-event-routes.test.ts +350 -0
  122. package/src/__tests__/host-browser-proxy.test.ts +444 -0
  123. package/src/__tests__/host-browser-routes.test.ts +198 -0
  124. package/src/__tests__/host-browser-ws-events-e2e.test.ts +423 -0
  125. package/src/__tests__/host-cu-proxy.test.ts +166 -1
  126. package/src/__tests__/host-file-proxy.test.ts +185 -1
  127. package/src/__tests__/host-file-read-tool.test.ts +52 -0
  128. package/src/__tests__/host-proxy-interface.test.ts +165 -0
  129. package/src/__tests__/host-shell-tool.test.ts +1 -11
  130. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  131. package/src/__tests__/identity-intro-cache.test.ts +40 -10
  132. package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
  133. package/src/__tests__/integration-status.test.ts +6 -7
  134. package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
  135. package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
  136. package/src/__tests__/llm-context-normalization.test.ts +488 -0
  137. package/src/__tests__/llm-context-route-provider.test.ts +86 -5
  138. package/src/__tests__/llm-usage-store.test.ts +363 -0
  139. package/src/__tests__/mcp-client-auth.test.ts +40 -4
  140. package/src/__tests__/mcp-health-check.test.ts +10 -3
  141. package/src/__tests__/media-stream-output.test.ts +555 -0
  142. package/src/__tests__/media-stream-parser.test.ts +374 -0
  143. package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
  144. package/src/__tests__/media-stream-stt-session.test.ts +588 -0
  145. package/src/__tests__/media-turn-detector.test.ts +440 -0
  146. package/src/__tests__/message-queue.test.ts +125 -0
  147. package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
  148. package/src/__tests__/migration-export-http.test.ts +67 -8
  149. package/src/__tests__/migration-export-streaming.test.ts +66 -0
  150. package/src/__tests__/migration-import-commit-http.test.ts +109 -7
  151. package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
  152. package/src/__tests__/migration-validate-http.test.ts +3 -3
  153. package/src/__tests__/mock-gateway-ipc.ts +151 -0
  154. package/src/__tests__/model-intents.test.ts +2 -2
  155. package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
  156. package/src/__tests__/oauth-apps-routes.test.ts +18 -12
  157. package/src/__tests__/oauth-cli.test.ts +709 -60
  158. package/src/__tests__/oauth-connect-orchestrator.test.ts +118 -24
  159. package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
  160. package/src/__tests__/oauth-provider-serializer.test.ts +147 -10
  161. package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
  162. package/src/__tests__/oauth-providers-routes.test.ts +52 -14
  163. package/src/__tests__/oauth-store.test.ts +1465 -176
  164. package/src/__tests__/oauth2-gateway-transport.test.ts +460 -26
  165. package/src/__tests__/onboarding-template-contract.test.ts +81 -70
  166. package/src/__tests__/openai-provider.test.ts +178 -2
  167. package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
  168. package/src/__tests__/openai-responses-provider.test.ts +1105 -0
  169. package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
  170. package/src/__tests__/outlook-categories.test.ts +1 -1
  171. package/src/__tests__/outlook-client-automation.test.ts +1 -1
  172. package/src/__tests__/outlook-compose-tools.test.ts +1 -1
  173. package/src/__tests__/outlook-email-watcher.test.ts +1 -1
  174. package/src/__tests__/outlook-follow-up.test.ts +1 -1
  175. package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
  176. package/src/__tests__/outlook-trash.test.ts +1 -1
  177. package/src/__tests__/outlook-unsubscribe.test.ts +32 -3
  178. package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
  179. package/src/__tests__/permission-mode.test.ts +28 -56
  180. package/src/__tests__/persona-resolver.test.ts +251 -0
  181. package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
  182. package/src/__tests__/platform-callback-registration.test.ts +19 -0
  183. package/src/__tests__/platform.test.ts +92 -1
  184. package/src/__tests__/post-turn-tool-result-truncation.test.ts +343 -0
  185. package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
  186. package/src/__tests__/pricing.test.ts +174 -0
  187. package/src/__tests__/proxy-approval-callback.test.ts +18 -0
  188. package/src/__tests__/qdrant-manager.test.ts +29 -8
  189. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
  190. package/src/__tests__/relationship-state-contract.test.ts +175 -0
  191. package/src/__tests__/relay-server.test.ts +423 -5
  192. package/src/__tests__/require-fresh-approval.test.ts +40 -1
  193. package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
  194. package/src/__tests__/schedule-routes.test.ts +162 -0
  195. package/src/__tests__/search-skills-unified.test.ts +118 -0
  196. package/src/__tests__/secret-detection-handler.test.ts +84 -0
  197. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  198. package/src/__tests__/secret-scanner-executor.test.ts +4 -0
  199. package/src/__tests__/secure-keys.test.ts +107 -0
  200. package/src/__tests__/send-endpoint-busy.test.ts +8 -1
  201. package/src/__tests__/sequence-store.test.ts +1 -1
  202. package/src/__tests__/server-history-render.test.ts +49 -0
  203. package/src/__tests__/set-permission-mode.test.ts +13 -250
  204. package/src/__tests__/settings-routes.test.ts +201 -0
  205. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  206. package/src/__tests__/skills-file-content-endpoint.test.ts +801 -0
  207. package/src/__tests__/skills-files-catalog-fallback.test.ts +738 -0
  208. package/src/__tests__/skills.test.ts +5 -2
  209. package/src/__tests__/skillssh-files.test.ts +446 -0
  210. package/src/__tests__/slack-block-formatting.test.ts +110 -0
  211. package/src/__tests__/slack-channel-config.test.ts +576 -16
  212. package/src/__tests__/stt-catalog-parity.test.ts +282 -0
  213. package/src/__tests__/stt-stream-session.test.ts +535 -0
  214. package/src/__tests__/subagent-detail.test.ts +44 -2
  215. package/src/__tests__/subagent-disposal.test.ts +1 -0
  216. package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
  217. package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
  218. package/src/__tests__/subagent-manager-notify.test.ts +1 -0
  219. package/src/__tests__/subagent-notify-parent.test.ts +1 -0
  220. package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
  221. package/src/__tests__/subagent-tools.test.ts +1 -0
  222. package/src/__tests__/subagent-types.test.ts +1 -0
  223. package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
  224. package/src/__tests__/system-prompt.test.ts +184 -27
  225. package/src/__tests__/task-scheduler.test.ts +32 -6
  226. package/src/__tests__/telegram-config.test.ts +10 -13
  227. package/src/__tests__/telephony-stt-routing.test.ts +329 -0
  228. package/src/__tests__/terminal-tools.test.ts +25 -5
  229. package/src/__tests__/test-preload.ts +18 -0
  230. package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
  231. package/src/__tests__/tool-approval-handler.test.ts +73 -0
  232. package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
  233. package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
  234. package/src/__tests__/tool-executor.test.ts +33 -24
  235. package/src/__tests__/tool-result-truncation.test.ts +36 -0
  236. package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
  237. package/src/__tests__/top-level-renderer.test.ts +73 -1
  238. package/src/__tests__/transport-hints-queue.test.ts +14 -29
  239. package/src/__tests__/trust-store.test.ts +7 -1
  240. package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
  241. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
  242. package/src/__tests__/tts-catalog-parity.test.ts +345 -0
  243. package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
  244. package/src/__tests__/twilio-routes.test.ts +376 -0
  245. package/src/__tests__/unicode.test.ts +293 -0
  246. package/src/__tests__/update-bulletin-format.test.ts +59 -0
  247. package/src/__tests__/update-bulletin.test.ts +206 -5
  248. package/src/__tests__/usage-routes.test.ts +25 -4
  249. package/src/__tests__/user-reference.test.ts +46 -61
  250. package/src/__tests__/v2-consent-policy.test.ts +103 -0
  251. package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
  252. package/src/__tests__/voice-config-update.test.ts +403 -0
  253. package/src/__tests__/voice-quality.test.ts +434 -19
  254. package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
  255. package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
  256. package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
  257. package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
  258. package/src/__tests__/workspace-migration-meets.test.ts +244 -0
  259. package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
  260. package/src/__tests__/workspace-policy.test.ts +2 -0
  261. package/src/acp/client-handler.ts +30 -4
  262. package/src/agent/image-optimize.ts +24 -12
  263. package/src/agent/loop.ts +55 -9
  264. package/src/approvals/guardian-request-resolvers.ts +21 -15
  265. package/src/backup/__tests__/backup-key.test.ts +152 -0
  266. package/src/backup/__tests__/backup-worker.test.ts +767 -0
  267. package/src/backup/__tests__/list-snapshots.test.ts +87 -0
  268. package/src/backup/__tests__/local-writer.test.ts +218 -0
  269. package/src/backup/__tests__/offsite-writer.test.ts +641 -0
  270. package/src/backup/__tests__/paths.test.ts +300 -0
  271. package/src/backup/__tests__/restore.test.ts +498 -0
  272. package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
  273. package/src/backup/__tests__/stream-crypt.test.ts +228 -0
  274. package/src/backup/backup-key.ts +137 -0
  275. package/src/backup/backup-worker.ts +459 -0
  276. package/src/backup/list-snapshots.ts +147 -0
  277. package/src/backup/local-writer.ts +133 -0
  278. package/src/backup/offsite-writer.ts +222 -0
  279. package/src/backup/paths.ts +226 -0
  280. package/src/backup/restore.ts +322 -0
  281. package/src/backup/snapshot-lock.ts +431 -0
  282. package/src/backup/stream-crypt.ts +263 -0
  283. package/src/browser-session/__tests__/manager.test.ts +297 -0
  284. package/src/browser-session/backends/cdp-inspect.ts +30 -0
  285. package/src/browser-session/backends/extension.ts +26 -0
  286. package/src/browser-session/backends/local.ts +24 -0
  287. package/src/browser-session/events.ts +164 -0
  288. package/src/browser-session/index.ts +27 -0
  289. package/src/browser-session/manager.ts +159 -0
  290. package/src/browser-session/types.ts +28 -0
  291. package/src/bundler/package-resolver.ts +4 -0
  292. package/src/calls/audio-store.ts +11 -5
  293. package/src/calls/call-controller.ts +226 -71
  294. package/src/calls/call-domain.ts +9 -0
  295. package/src/calls/call-speech-output.ts +190 -0
  296. package/src/calls/call-transport.ts +77 -0
  297. package/src/calls/media-stream-audio-transcode.ts +173 -0
  298. package/src/calls/media-stream-output.ts +660 -0
  299. package/src/calls/media-stream-parser.ts +300 -0
  300. package/src/calls/media-stream-protocol.ts +166 -0
  301. package/src/calls/media-stream-server.ts +592 -0
  302. package/src/calls/media-stream-stt-session.ts +460 -0
  303. package/src/calls/media-turn-detector.ts +230 -0
  304. package/src/calls/relay-server.ts +90 -75
  305. package/src/calls/resolve-call-tts-provider.ts +136 -0
  306. package/src/calls/telephony-stt-routing.ts +145 -0
  307. package/src/calls/tts-call-strategy.ts +161 -0
  308. package/src/calls/tts-text-sanitizer.ts +32 -16
  309. package/src/calls/twilio-routes.ts +281 -17
  310. package/src/calls/voice-quality.ts +78 -35
  311. package/src/calls/voice-session-bridge.ts +8 -1
  312. package/src/channels/__tests__/types.test.ts +134 -0
  313. package/src/channels/types.ts +69 -3
  314. package/src/cli/__tests__/run-assistant-command.ts +11 -1
  315. package/src/cli/commands/__tests__/backup.test.ts +1165 -0
  316. package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
  317. package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
  318. package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
  319. package/src/cli/commands/__tests__/email-download.test.ts +16 -1
  320. package/src/cli/commands/__tests__/email-list.test.ts +22 -4
  321. package/src/cli/commands/__tests__/email-register.test.ts +4 -4
  322. package/src/cli/commands/__tests__/email-send.test.ts +37 -4
  323. package/src/cli/commands/__tests__/email-status.test.ts +5 -1
  324. package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
  325. package/src/cli/commands/backup.ts +993 -0
  326. package/src/cli/commands/conversations.ts +77 -0
  327. package/src/cli/commands/credentials.ts +3 -4
  328. package/src/cli/commands/domain.ts +210 -0
  329. package/src/cli/commands/email.ts +273 -16
  330. package/src/cli/commands/mcp.ts +16 -4
  331. package/src/cli/commands/oauth/__tests__/connect.test.ts +56 -44
  332. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
  333. package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
  334. package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
  335. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +32 -33
  336. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +330 -0
  337. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +117 -12
  338. package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
  339. package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
  340. package/src/cli/commands/oauth/apps.ts +7 -4
  341. package/src/cli/commands/oauth/connect.ts +6 -3
  342. package/src/cli/commands/oauth/disconnect.ts +1 -1
  343. package/src/cli/commands/oauth/mode.ts +12 -3
  344. package/src/cli/commands/oauth/providers.ts +215 -36
  345. package/src/cli/commands/oauth/shared.ts +7 -6
  346. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +254 -0
  347. package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
  348. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  349. package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
  350. package/src/cli/commands/platform/index.ts +107 -10
  351. package/src/cli/commands/usage.ts +10 -9
  352. package/src/cli/lib/daemon-credential-client.ts +4 -0
  353. package/src/cli/program.ts +30 -4
  354. package/src/config/__tests__/backup-schema.test.ts +134 -0
  355. package/src/config/assistant-feature-flags.ts +61 -62
  356. package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
  357. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +141 -0
  358. package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
  359. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
  360. package/src/config/bundled-skills/browser/SKILL.md +30 -5
  361. package/src/config/bundled-skills/browser/TOOLS.json +123 -0
  362. package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
  363. package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
  364. package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
  365. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
  366. package/src/config/bundled-skills/contacts/SKILL.md +5 -2
  367. package/src/config/bundled-skills/document/SKILL.md +4 -0
  368. package/src/config/bundled-skills/gmail/SKILL.md +54 -8
  369. package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
  370. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
  371. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
  372. package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
  373. package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
  374. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
  375. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
  376. package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
  377. package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
  378. package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
  379. package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
  380. package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
  381. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
  382. package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
  383. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
  384. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  385. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
  386. package/src/config/bundled-skills/outlook/SKILL.md +9 -2
  387. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
  388. package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
  389. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
  390. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
  391. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  392. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
  393. package/src/config/bundled-skills/slack/SKILL.md +1 -0
  394. package/src/config/bundled-skills/subagent/SKILL.md +21 -0
  395. package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
  396. package/src/config/bundled-skills/tasks/SKILL.md +5 -0
  397. package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
  398. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
  399. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
  400. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
  401. package/src/config/bundled-tool-registry.ts +8 -0
  402. package/src/config/env-registry.ts +38 -0
  403. package/src/config/env.ts +49 -4
  404. package/src/config/feature-flag-registry.json +85 -14
  405. package/src/config/loader.ts +82 -13
  406. package/src/config/sanitize-for-transfer.ts +47 -0
  407. package/src/config/schema.ts +81 -15
  408. package/src/config/schemas/__tests__/stt.test.ts +43 -0
  409. package/src/config/schemas/analysis.ts +51 -0
  410. package/src/config/schemas/backup.ts +72 -0
  411. package/src/config/schemas/calls.ts +1 -26
  412. package/src/config/schemas/elevenlabs.ts +0 -59
  413. package/src/config/schemas/filing.ts +47 -7
  414. package/src/config/schemas/heartbeat.ts +27 -5
  415. package/src/config/schemas/host-browser.ts +112 -0
  416. package/src/config/schemas/inference.ts +1 -1
  417. package/src/config/schemas/memory-lifecycle.ts +14 -2
  418. package/src/config/schemas/memory-retrieval.ts +103 -0
  419. package/src/config/schemas/security.ts +0 -6
  420. package/src/config/schemas/services.ts +52 -0
  421. package/src/config/schemas/stt.ts +59 -0
  422. package/src/config/schemas/tts.ts +230 -0
  423. package/src/config/schemas/updates.ts +14 -0
  424. package/src/config/skills.ts +4 -0
  425. package/src/config/types.ts +4 -1
  426. package/src/contacts/contact-store.ts +56 -11
  427. package/src/contacts/contacts-write.ts +38 -1
  428. package/src/context/post-turn-tool-result-truncation.ts +177 -0
  429. package/src/context/tool-result-truncation.ts +2 -1
  430. package/src/context/window-manager.ts +61 -10
  431. package/src/credential-execution/approval-bridge.ts +49 -15
  432. package/src/credential-execution/executable-discovery.ts +12 -2
  433. package/src/credential-execution/process-manager.ts +33 -2
  434. package/src/credential-health/credential-health-service.ts +366 -0
  435. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
  436. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
  437. package/src/daemon/__tests__/conversation-tool-setup.test.ts +195 -0
  438. package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
  439. package/src/daemon/app-source-watcher.ts +35 -0
  440. package/src/daemon/config-watcher.ts +99 -5
  441. package/src/daemon/context-overflow-approval.ts +5 -0
  442. package/src/daemon/conversation-agent-loop-handlers.ts +23 -2
  443. package/src/daemon/conversation-agent-loop.ts +153 -42
  444. package/src/daemon/conversation-attachments.ts +40 -0
  445. package/src/daemon/conversation-error.ts +11 -0
  446. package/src/daemon/conversation-history.ts +40 -6
  447. package/src/daemon/conversation-launch.ts +220 -0
  448. package/src/daemon/conversation-lifecycle.ts +59 -9
  449. package/src/daemon/conversation-messaging.ts +37 -3
  450. package/src/daemon/conversation-notifiers.ts +5 -0
  451. package/src/daemon/conversation-process.ts +622 -13
  452. package/src/daemon/conversation-queue-manager.ts +24 -0
  453. package/src/daemon/conversation-runtime-assembly.ts +128 -36
  454. package/src/daemon/conversation-slash.ts +36 -0
  455. package/src/daemon/conversation-surfaces.ts +131 -40
  456. package/src/daemon/conversation-tool-setup.ts +99 -8
  457. package/src/daemon/conversation-usage.ts +7 -4
  458. package/src/daemon/conversation-workspace.ts +12 -0
  459. package/src/daemon/conversation.ts +292 -16
  460. package/src/daemon/date-context.ts +10 -10
  461. package/src/daemon/first-greeting.ts +3 -2
  462. package/src/daemon/handlers/config-slack-channel.ts +269 -94
  463. package/src/daemon/handlers/conversations.ts +13 -141
  464. package/src/daemon/handlers/shared.ts +80 -0
  465. package/src/daemon/handlers/skills.ts +483 -44
  466. package/src/daemon/host-bash-proxy.ts +48 -13
  467. package/src/daemon/host-browser-proxy.ts +192 -0
  468. package/src/daemon/host-cu-proxy.ts +36 -11
  469. package/src/daemon/host-file-proxy.ts +57 -9
  470. package/src/daemon/lifecycle.ts +179 -28
  471. package/src/daemon/message-protocol.ts +13 -0
  472. package/src/daemon/message-types/conversations.ts +89 -14
  473. package/src/daemon/message-types/home.ts +40 -0
  474. package/src/daemon/message-types/host-browser.ts +100 -0
  475. package/src/daemon/message-types/meet.ts +143 -0
  476. package/src/daemon/message-types/messages.ts +19 -5
  477. package/src/daemon/message-types/schedules.ts +34 -2
  478. package/src/daemon/message-types/skills.ts +26 -0
  479. package/src/daemon/message-types/subagents.ts +2 -0
  480. package/src/daemon/message-types/surfaces.ts +2 -0
  481. package/src/daemon/server.ts +439 -14
  482. package/src/daemon/shutdown-handlers.ts +32 -4
  483. package/src/daemon/shutdown-registry.ts +40 -0
  484. package/src/daemon/tool-side-effects.ts +15 -0
  485. package/src/daemon/transport-hints.ts +5 -24
  486. package/src/email/html-renderer.ts +76 -0
  487. package/src/heartbeat/heartbeat-service.ts +93 -7
  488. package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
  489. package/src/home/__tests__/emit-feed-event.test.ts +169 -0
  490. package/src/home/__tests__/feed-scheduler.test.ts +194 -0
  491. package/src/home/__tests__/feed-types.test.ts +275 -0
  492. package/src/home/__tests__/feed-writer.test.ts +688 -0
  493. package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
  494. package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
  495. package/src/home/__tests__/progress-formula.test.ts +213 -0
  496. package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
  497. package/src/home/__tests__/rollup-producer.test.ts +398 -0
  498. package/src/home/assistant-feed-authoring.ts +124 -0
  499. package/src/home/emit-feed-event.ts +158 -0
  500. package/src/home/feed-scheduler.ts +247 -0
  501. package/src/home/feed-types.ts +181 -0
  502. package/src/home/feed-writer.ts +469 -0
  503. package/src/home/platform-gmail-digest.ts +163 -0
  504. package/src/home/progress-formula.ts +86 -0
  505. package/src/home/relationship-state-writer.ts +824 -0
  506. package/src/home/relationship-state.ts +143 -0
  507. package/src/home/rollup-producer.ts +384 -0
  508. package/src/hooks/runner.ts +7 -0
  509. package/src/inbound/platform-callback-registration.ts +30 -20
  510. package/src/inbound/public-ingress-urls.ts +12 -0
  511. package/src/instrument.ts +1 -1
  512. package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
  513. package/src/ipc/cli-client.ts +151 -0
  514. package/src/ipc/cli-server.ts +234 -0
  515. package/src/ipc/gateway-client.ts +180 -0
  516. package/src/ipc/routes/index.ts +5 -0
  517. package/src/ipc/routes/wake-conversation.ts +19 -0
  518. package/src/mcp/client.ts +59 -24
  519. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
  520. package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
  521. package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
  522. package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
  523. package/src/memory/app-store.ts +31 -1
  524. package/src/memory/attachments-store.ts +70 -0
  525. package/src/memory/auto-analysis-enqueue.ts +127 -0
  526. package/src/memory/auto-analysis-guard.ts +27 -0
  527. package/src/memory/cleanup-schedule-state.ts +37 -0
  528. package/src/memory/conversation-analyze-job.ts +73 -0
  529. package/src/memory/conversation-crud.ts +122 -0
  530. package/src/memory/conversation-disk-view.ts +7 -0
  531. package/src/memory/conversation-group-migration.ts +34 -2
  532. package/src/memory/conversation-queries.ts +6 -5
  533. package/src/memory/conversation-starters-cadence.ts +76 -0
  534. package/src/memory/conversation-title-service.ts +5 -2
  535. package/src/memory/db-init.ts +18 -0
  536. package/src/memory/db-maintenance.ts +108 -0
  537. package/src/memory/db.ts +1 -0
  538. package/src/memory/embedding-backend.test.ts +75 -0
  539. package/src/memory/embedding-backend.ts +131 -5
  540. package/src/memory/embedding-gemini.test.ts +54 -0
  541. package/src/memory/embedding-gemini.ts +20 -9
  542. package/src/memory/embedding-local.ts +176 -17
  543. package/src/memory/graph/consolidation.ts +10 -23
  544. package/src/memory/graph/conversation-graph-memory.ts +15 -0
  545. package/src/memory/graph/extraction-job.ts +15 -0
  546. package/src/memory/graph/extraction.test.ts +23 -0
  547. package/src/memory/graph/extraction.ts +8 -0
  548. package/src/memory/graph/retriever.ts +67 -40
  549. package/src/memory/graph/scoring.test.ts +186 -0
  550. package/src/memory/graph/scoring.ts +31 -1
  551. package/src/memory/graph/store.test.ts +7 -3
  552. package/src/memory/graph/store.ts +47 -12
  553. package/src/memory/graph/tools.ts +1 -1
  554. package/src/memory/group-crud.ts +6 -1
  555. package/src/memory/indexer.ts +95 -16
  556. package/src/memory/job-handlers/cleanup.ts +11 -8
  557. package/src/memory/job-handlers/conversation-starters.ts +16 -10
  558. package/src/memory/jobs-store.ts +64 -4
  559. package/src/memory/jobs-worker.ts +22 -9
  560. package/src/memory/llm-usage-store.ts +137 -60
  561. package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
  562. package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
  563. package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
  564. package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
  565. package/src/memory/migrations/217-conversation-host-access.ts +40 -0
  566. package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
  567. package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
  568. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
  569. package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
  570. package/src/memory/migrations/index.ts +12 -0
  571. package/src/memory/migrations/registry.ts +16 -0
  572. package/src/memory/qdrant-manager.ts +43 -16
  573. package/src/memory/schema/conversations.ts +3 -0
  574. package/src/memory/schema/oauth.ts +21 -13
  575. package/src/memory/usage-buckets.ts +396 -0
  576. package/src/messaging/providers/gmail/client.ts +57 -6
  577. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
  578. package/src/messaging/providers/slack/adapter.ts +143 -38
  579. package/src/messaging/providers/slack/client.ts +16 -0
  580. package/src/messaging/providers/slack/types.ts +4 -0
  581. package/src/notifications/decision-engine.ts +3 -3
  582. package/src/notifications/signal.ts +5 -0
  583. package/src/oauth/AGENTS.md +76 -0
  584. package/src/oauth/__tests__/identity-verifier.test.ts +25 -19
  585. package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
  586. package/src/oauth/byo-connection.test.ts +26 -9
  587. package/src/oauth/byo-connection.ts +10 -8
  588. package/src/oauth/connect-orchestrator.ts +25 -21
  589. package/src/oauth/connect-types.ts +3 -3
  590. package/src/oauth/connection-resolver.test.ts +17 -4
  591. package/src/oauth/connection-resolver.ts +22 -18
  592. package/src/oauth/connection.ts +3 -1
  593. package/src/oauth/manual-token-connection.ts +13 -13
  594. package/src/oauth/oauth-store.ts +223 -100
  595. package/src/oauth/platform-connection.test.ts +101 -3
  596. package/src/oauth/platform-connection.ts +56 -35
  597. package/src/oauth/provider-serializer.ts +31 -5
  598. package/src/oauth/revoke.ts +76 -0
  599. package/src/oauth/seed-providers.ts +133 -87
  600. package/src/oauth/token-persistence.ts +1 -1
  601. package/src/permissions/checker.ts +16 -6
  602. package/src/permissions/defaults.ts +49 -1
  603. package/src/permissions/permission-mode.ts +4 -11
  604. package/src/permissions/prompter.ts +13 -1
  605. package/src/permissions/trust-store.ts +3 -3
  606. package/src/permissions/v2-consent-policy.ts +87 -0
  607. package/src/permissions/workspace-policy.ts +3 -0
  608. package/src/platform/client.test.ts +10 -0
  609. package/src/platform/sync-identity.ts +129 -0
  610. package/src/prompts/persona-resolver.ts +126 -2
  611. package/src/prompts/system-prompt.ts +76 -38
  612. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
  613. package/src/prompts/templates/BOOTSTRAP.md +59 -105
  614. package/src/prompts/templates/SOUL.md +3 -1
  615. package/src/prompts/templates/UPDATES.md +12 -0
  616. package/src/prompts/templates/channels/slack.md +20 -0
  617. package/src/prompts/update-bulletin-format.ts +26 -9
  618. package/src/prompts/update-bulletin.ts +34 -23
  619. package/src/prompts/user-reference.ts +20 -17
  620. package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
  621. package/src/providers/anthropic/client.ts +157 -60
  622. package/src/providers/fireworks/client.ts +2 -2
  623. package/src/providers/gemini/client.ts +9 -1
  624. package/src/providers/model-catalog.ts +6 -0
  625. package/src/providers/model-intents.ts +4 -4
  626. package/src/providers/ollama/client.ts +2 -2
  627. package/src/providers/openai/chat-completions-provider.ts +474 -0
  628. package/src/providers/openai/client.ts +25 -440
  629. package/src/providers/openai/responses-provider.ts +502 -0
  630. package/src/providers/openrouter/client.ts +101 -4
  631. package/src/providers/provider-secret-catalog.ts +139 -0
  632. package/src/providers/registry.ts +2 -2
  633. package/src/providers/retry.ts +14 -3
  634. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
  635. package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
  636. package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
  637. package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
  638. package/src/providers/speech-to-text/deepgram.test.ts +332 -0
  639. package/src/providers/speech-to-text/deepgram.ts +115 -0
  640. package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
  641. package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
  642. package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
  643. package/src/providers/speech-to-text/google-gemini.ts +101 -0
  644. package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
  645. package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
  646. package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
  647. package/src/providers/speech-to-text/openai-whisper.ts +63 -33
  648. package/src/providers/speech-to-text/provider-catalog.ts +306 -0
  649. package/src/providers/speech-to-text/resolve.ts +386 -6
  650. package/src/providers/types.ts +10 -1
  651. package/src/runtime/AGENTS.md +65 -0
  652. package/src/runtime/__tests__/agent-wake.test.ts +831 -0
  653. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
  654. package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
  655. package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
  656. package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
  657. package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
  658. package/src/runtime/agent-wake.ts +512 -0
  659. package/src/runtime/assistant-event-hub.ts +2 -2
  660. package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
  661. package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
  662. package/src/runtime/auth/__tests__/route-policy.test.ts +48 -0
  663. package/src/runtime/auth/middleware.ts +98 -0
  664. package/src/runtime/auth/route-policy.ts +33 -9
  665. package/src/runtime/auth/token-service.ts +56 -1
  666. package/src/runtime/btw-sidechain.ts +2 -0
  667. package/src/runtime/capability-tokens.ts +414 -0
  668. package/src/runtime/channel-approvals.ts +18 -5
  669. package/src/runtime/channel-invite-transport.ts +1 -1
  670. package/src/runtime/channel-invite-transports/email.ts +14 -6
  671. package/src/runtime/channel-readiness-service.ts +12 -22
  672. package/src/runtime/chrome-extension-registry.ts +368 -0
  673. package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
  674. package/src/runtime/guardian-decision-types.ts +7 -0
  675. package/src/runtime/http-server.ts +815 -75
  676. package/src/runtime/http-types.ts +6 -2
  677. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
  678. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
  679. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +198 -0
  680. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
  681. package/src/runtime/migrations/migration-transport.ts +7 -0
  682. package/src/runtime/migrations/migration-wizard.ts +23 -2
  683. package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
  684. package/src/runtime/migrations/vbundle-builder.ts +145 -38
  685. package/src/runtime/migrations/vbundle-import-analyzer.ts +96 -1
  686. package/src/runtime/migrations/vbundle-importer.ts +89 -5
  687. package/src/runtime/pending-interactions.ts +18 -13
  688. package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
  689. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
  690. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
  691. package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
  692. package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
  693. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
  694. package/src/runtime/routes/app-management-routes.ts +12 -18
  695. package/src/runtime/routes/approval-routes.ts +90 -16
  696. package/src/runtime/routes/attachment-routes.test.ts +9 -3
  697. package/src/runtime/routes/attachment-routes.ts +216 -17
  698. package/src/runtime/routes/backup-routes.ts +519 -0
  699. package/src/runtime/routes/browser-extension-pair-routes.ts +556 -0
  700. package/src/runtime/routes/btw-routes.ts +8 -6
  701. package/src/runtime/routes/contact-routes.test.ts +298 -0
  702. package/src/runtime/routes/contact-routes.ts +132 -5
  703. package/src/runtime/routes/conversation-analysis-routes.ts +22 -141
  704. package/src/runtime/routes/conversation-management-routes.ts +223 -0
  705. package/src/runtime/routes/conversation-routes.ts +598 -103
  706. package/src/runtime/routes/conversation-starter-routes.ts +78 -16
  707. package/src/runtime/routes/filing-routes.ts +93 -0
  708. package/src/runtime/routes/guardian-action-routes.ts +24 -13
  709. package/src/runtime/routes/home-feed-routes.ts +334 -0
  710. package/src/runtime/routes/home-state-routes.ts +138 -0
  711. package/src/runtime/routes/host-browser-routes.ts +268 -0
  712. package/src/runtime/routes/host-file-routes.ts +9 -1
  713. package/src/runtime/routes/identity-intro-cache.ts +7 -3
  714. package/src/runtime/routes/identity-routes.ts +262 -33
  715. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
  716. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
  717. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
  718. package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
  719. package/src/runtime/routes/integrations/slack/channel.ts +11 -3
  720. package/src/runtime/routes/integrations/slack/share.ts +45 -7
  721. package/src/runtime/routes/llm-context-normalization.ts +303 -0
  722. package/src/runtime/routes/log-export-routes.ts +42 -22
  723. package/src/runtime/routes/memory-item-routes.test.ts +3 -2
  724. package/src/runtime/routes/memory-item-routes.ts +1 -7
  725. package/src/runtime/routes/migration-routes.ts +122 -2
  726. package/src/runtime/routes/oauth-apps.ts +15 -17
  727. package/src/runtime/routes/oauth-providers.ts +4 -0
  728. package/src/runtime/routes/schedule-routes.ts +24 -11
  729. package/src/runtime/routes/settings-routes.ts +31 -102
  730. package/src/runtime/routes/skills-routes.ts +128 -9
  731. package/src/runtime/routes/stt-routes.ts +233 -0
  732. package/src/runtime/routes/subagents-routes.ts +14 -10
  733. package/src/runtime/routes/surface-action-routes.ts +41 -2
  734. package/src/runtime/routes/tts-routes.ts +108 -24
  735. package/src/runtime/routes/usage-routes.ts +38 -9
  736. package/src/runtime/routes/user-route-dispatcher.ts +50 -5
  737. package/src/runtime/routes/user-routes.ts +13 -1
  738. package/src/runtime/routes/work-items-routes.ts +8 -1
  739. package/src/runtime/routes/workspace-routes.test.ts +22 -0
  740. package/src/runtime/routes/workspace-routes.ts +8 -1
  741. package/src/runtime/routes/workspace-utils.ts +2 -0
  742. package/src/runtime/runtime-mode.ts +33 -0
  743. package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
  744. package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
  745. package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
  746. package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
  747. package/src/runtime/services/analyze-conversation.ts +344 -0
  748. package/src/runtime/services/analyze-deps-singleton.ts +32 -0
  749. package/src/runtime/services/auto-analysis-prompt.ts +55 -0
  750. package/src/runtime/skill-route-registry.ts +49 -0
  751. package/src/runtime/slack-block-formatting.ts +437 -10
  752. package/src/schedule/scheduler.ts +57 -5
  753. package/src/security/ces-credential-client.ts +20 -0
  754. package/src/security/ces-rpc-credential-backend.ts +17 -0
  755. package/src/security/credential-backend.ts +5 -0
  756. package/src/security/oauth2.ts +68 -29
  757. package/src/security/secure-keys.ts +143 -27
  758. package/src/security/token-manager.ts +31 -10
  759. package/src/sequence/engine.ts +23 -0
  760. package/src/sequence/types.ts +1 -1
  761. package/src/skills/catalog-files.ts +554 -0
  762. package/src/skills/category-inference.ts +122 -0
  763. package/src/skills/clawhub-files.ts +213 -0
  764. package/src/skills/clawhub.ts +84 -23
  765. package/src/skills/skill-file-provider.ts +40 -0
  766. package/src/skills/skillssh-files.ts +395 -0
  767. package/src/skills/skillssh-registry.ts +4 -4
  768. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
  769. package/src/stt/__tests__/types.test.ts +89 -0
  770. package/src/stt/daemon-batch-transcriber.ts +195 -0
  771. package/src/stt/stt-stream-session.ts +499 -0
  772. package/src/stt/types.ts +330 -0
  773. package/src/stt/wav-encoder.test.ts +373 -0
  774. package/src/stt/wav-encoder.ts +175 -0
  775. package/src/subagent/manager.ts +169 -40
  776. package/src/subagent/types.ts +19 -0
  777. package/src/tools/apps/executors.ts +11 -2
  778. package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
  779. package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
  780. package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
  781. package/src/tools/browser/auth-detector.ts +43 -12
  782. package/src/tools/browser/browser-execution.ts +1787 -342
  783. package/src/tools/browser/browser-manager.ts +81 -12
  784. package/src/tools/browser/browser-mode-constants.ts +12 -0
  785. package/src/tools/browser/browser-mode.ts +92 -0
  786. package/src/tools/browser/browser-status-constants.ts +33 -0
  787. package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
  788. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
  789. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +1263 -0
  790. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +359 -0
  791. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1993 -0
  792. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
  793. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
  794. package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
  795. package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
  796. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
  797. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
  798. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +1007 -0
  799. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
  800. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +744 -0
  801. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
  802. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +868 -0
  803. package/src/tools/browser/cdp-client/errors.ts +49 -0
  804. package/src/tools/browser/cdp-client/extension-cdp-client.ts +148 -0
  805. package/src/tools/browser/cdp-client/factory.ts +914 -0
  806. package/src/tools/browser/cdp-client/index.ts +28 -0
  807. package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
  808. package/src/tools/browser/cdp-client/types.ts +120 -0
  809. package/src/tools/credentials/vault.ts +35 -6
  810. package/src/tools/filesystem/edit.ts +1 -1
  811. package/src/tools/filesystem/list.ts +1 -1
  812. package/src/tools/filesystem/read.ts +1 -1
  813. package/src/tools/filesystem/write.ts +2 -1
  814. package/src/tools/host-filesystem/edit.ts +1 -1
  815. package/src/tools/host-filesystem/read.ts +12 -15
  816. package/src/tools/host-filesystem/write.ts +1 -1
  817. package/src/tools/host-terminal/host-shell.ts +21 -16
  818. package/src/tools/network/web-fetch.ts +5 -2
  819. package/src/tools/network/web-search.ts +5 -2
  820. package/src/tools/permission-checker.ts +77 -82
  821. package/src/tools/registry.ts +0 -2
  822. package/src/tools/secret-detection-handler.ts +34 -0
  823. package/src/tools/shared/filesystem/image-read.ts +61 -40
  824. package/src/tools/shared/shell-output.ts +3 -1
  825. package/src/tools/side-effects.ts +2 -0
  826. package/src/tools/skills/sandbox-runner.ts +3 -2
  827. package/src/tools/subagent/spawn.ts +47 -3
  828. package/src/tools/subagent/status.ts +2 -0
  829. package/src/tools/system/register.ts +2 -16
  830. package/src/tools/terminal/safe-env.ts +15 -0
  831. package/src/tools/terminal/shell.ts +36 -20
  832. package/src/tools/tool-approval-handler.ts +48 -2
  833. package/src/tools/tool-manifest.ts +21 -0
  834. package/src/tools/types.ts +19 -0
  835. package/src/tools/ui-surface/definitions.ts +6 -1
  836. package/src/tts/__tests__/provider-adapters.test.ts +834 -0
  837. package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
  838. package/src/tts/__tests__/provider-catalog.test.ts +183 -0
  839. package/src/tts/__tests__/provider-registry.test.ts +90 -0
  840. package/src/tts/provider-catalog.ts +201 -0
  841. package/src/tts/provider-registry.ts +73 -0
  842. package/src/tts/providers/deepgram-provider.ts +219 -0
  843. package/src/tts/providers/elevenlabs-provider.ts +211 -0
  844. package/src/tts/providers/fish-audio-provider.ts +183 -0
  845. package/src/tts/providers/index.ts +42 -0
  846. package/src/tts/providers/register-builtins.ts +130 -0
  847. package/src/tts/synthesize-text.ts +110 -0
  848. package/src/tts/tts-config-resolver.ts +78 -0
  849. package/src/tts/types.ts +153 -0
  850. package/src/types/onboarding-context.ts +7 -0
  851. package/src/util/abort-reasons.ts +58 -0
  852. package/src/util/device-id.ts +32 -16
  853. package/src/util/errors.ts +9 -1
  854. package/src/util/platform.ts +63 -24
  855. package/src/util/pricing.ts +66 -3
  856. package/src/util/spawn.ts +1 -1
  857. package/src/util/truncate.ts +4 -2
  858. package/src/util/unicode.ts +201 -0
  859. package/src/version.ts +19 -24
  860. package/src/watcher/engine.ts +23 -0
  861. package/src/watcher/watcher-store.ts +31 -0
  862. package/src/workspace/migrations/003-seed-device-id.ts +9 -3
  863. package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
  864. package/src/workspace/migrations/029-seed-pkb.ts +1 -1
  865. package/src/workspace/migrations/031-drop-user-md.ts +317 -0
  866. package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
  867. package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
  868. package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
  869. package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
  870. package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
  871. package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
  872. package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
  873. package/src/workspace/migrations/registry.ts +16 -0
  874. package/src/workspace/top-level-renderer.ts +31 -1
  875. package/src/workspace/turn-commit.ts +31 -0
  876. package/src/__tests__/chrome-cdp.test.ts +0 -419
  877. package/src/__tests__/email-cli.test.ts +0 -297
  878. package/src/__tests__/email-service-config-fallback.test.ts +0 -102
  879. package/src/__tests__/permission-mode-sse.test.ts +0 -418
  880. package/src/__tests__/permission-mode-store.test.ts +0 -277
  881. package/src/browser-extension-relay/protocol.ts +0 -63
  882. package/src/browser-extension-relay/server.ts +0 -203
  883. package/src/cli/commands/browser-relay.ts +0 -536
  884. package/src/config/schemas/sandbox.ts +0 -14
  885. package/src/email/guardrails.ts +0 -221
  886. package/src/email/provider.ts +0 -117
  887. package/src/email/providers/agentmail.ts +0 -361
  888. package/src/email/providers/index.ts +0 -65
  889. package/src/email/service.ts +0 -384
  890. package/src/email/types.ts +0 -126
  891. package/src/permissions/permission-mode-store.ts +0 -180
  892. package/src/prompts/templates/USER.md +0 -13
  893. package/src/providers/speech-to-text/types.ts +0 -17
  894. package/src/tools/browser/chrome-cdp.ts +0 -239
  895. package/src/tools/system/set-permission-mode.ts +0 -103
@@ -0,0 +1,715 @@
1
+ /**
2
+ * Tests for the /v1/browser-extension-pair capability-token pair endpoint.
3
+ *
4
+ * Covers:
5
+ * - Method/host/origin enforcement (405, 403, 400, 401)
6
+ * - Native-host marker header requirement (403 when missing)
7
+ * - Browser-origin rejection (non-allowlisted Origin header -> 403)
8
+ * - Strict per-peer rate limiting (10/min, then 429)
9
+ * - Audit logging field shape for denied attempts
10
+ * - Successful mint on allowed origin (200) for both the preferred
11
+ * `extensionOrigin` body field and the legacy `origin` alias
12
+ * - `expiresAt` response field is an ISO 8601 string matching what the
13
+ * native messaging helper validates
14
+ * - IPv6 loopback `Host` header variants (bracketed and bare) are
15
+ * accepted
16
+ * - Issued token round-trips through `verifyHostBrowserCapability`
17
+ * - Tampered tokens fail verification
18
+ */
19
+
20
+ import { randomBytes } from "node:crypto";
21
+ import { beforeEach, describe, expect, test } from "bun:test";
22
+
23
+ import {
24
+ resetCapabilityTokenSecretForTests,
25
+ setCapabilityTokenSecretForTests,
26
+ verifyHostBrowserCapability,
27
+ } from "../capability-tokens.js";
28
+ import {
29
+ ALLOWED_EXTENSION_ORIGINS,
30
+ handleBrowserExtensionPair,
31
+ NATIVE_HOST_MARKER_HEADER,
32
+ NATIVE_HOST_MARKER_VALUE,
33
+ parseHostHeader,
34
+ resetPairRateLimiterForTests,
35
+ } from "../routes/browser-extension-pair-routes.js";
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Test helpers
39
+ // ---------------------------------------------------------------------------
40
+
41
+ type ServerWithRequestIP = {
42
+ requestIP(
43
+ req: Request,
44
+ ): { address: string; family: string; port: number } | null;
45
+ };
46
+
47
+ function mockServer(address: string): ServerWithRequestIP {
48
+ return {
49
+ requestIP: () => ({ address, family: "IPv4", port: 0 }),
50
+ };
51
+ }
52
+
53
+ const loopbackServer = mockServer("127.0.0.1");
54
+ const lanPeerServer = mockServer("192.168.1.10");
55
+ const publicPeerServer = mockServer("203.0.113.50");
56
+
57
+ const ALLOWED_ORIGIN = (() => {
58
+ const first = Array.from(ALLOWED_EXTENSION_ORIGINS)[0];
59
+ if (!first) {
60
+ throw new Error(
61
+ "ALLOWED_EXTENSION_ORIGINS must contain at least one extension origin for tests",
62
+ );
63
+ }
64
+ return first;
65
+ })();
66
+ const ALLOWED_ORIGIN_BARE = ALLOWED_ORIGIN.endsWith("/")
67
+ ? ALLOWED_ORIGIN.slice(0, -1)
68
+ : ALLOWED_ORIGIN;
69
+
70
+ /**
71
+ * Build a pair request. By default includes the native-host marker
72
+ * header so existing tests exercising other invariants continue to
73
+ * pass. Tests that want to exercise the marker-header gate pass
74
+ * `nativeHost: false` (or an explicit override value).
75
+ */
76
+ function buildRequest(
77
+ options: {
78
+ method?: string;
79
+ body?: unknown;
80
+ host?: string | null;
81
+ origin?: string;
82
+ forwardedFor?: string;
83
+ rawBody?: string;
84
+ /**
85
+ * When `false`, omits the native-host marker header entirely.
86
+ * When a string, sets the header to that value (for testing
87
+ * unexpected values). Defaults to including the expected value.
88
+ */
89
+ nativeHost?: boolean | string;
90
+ } = {},
91
+ ): Request {
92
+ const headers = new Headers();
93
+ if (options.host !== null) {
94
+ headers.set("host", options.host ?? "127.0.0.1:8765");
95
+ }
96
+ if (options.forwardedFor) {
97
+ headers.set("x-forwarded-for", options.forwardedFor);
98
+ }
99
+ if (options.origin !== undefined) {
100
+ headers.set("origin", options.origin);
101
+ }
102
+ if (options.nativeHost === undefined || options.nativeHost === true) {
103
+ headers.set(NATIVE_HOST_MARKER_HEADER, NATIVE_HOST_MARKER_VALUE);
104
+ } else if (typeof options.nativeHost === "string") {
105
+ headers.set(NATIVE_HOST_MARKER_HEADER, options.nativeHost);
106
+ }
107
+ // else: nativeHost === false — omit the header entirely.
108
+ let bodyStr: string | undefined;
109
+ if (options.rawBody !== undefined) {
110
+ bodyStr = options.rawBody;
111
+ headers.set("content-type", "application/json");
112
+ } else if (options.body !== undefined) {
113
+ bodyStr = JSON.stringify(options.body);
114
+ headers.set("content-type", "application/json");
115
+ }
116
+ return new Request("http://127.0.0.1:8765/v1/browser-extension-pair", {
117
+ method: options.method ?? "POST",
118
+ headers,
119
+ body: bodyStr,
120
+ });
121
+ }
122
+
123
+ // ---------------------------------------------------------------------------
124
+ // Tests
125
+ // ---------------------------------------------------------------------------
126
+
127
+ describe("handleBrowserExtensionPair", () => {
128
+ beforeEach(() => {
129
+ resetCapabilityTokenSecretForTests();
130
+ setCapabilityTokenSecretForTests(randomBytes(32));
131
+ // Reset the per-peer rate limiter so one test's burst of requests
132
+ // cannot leak budget into the next test.
133
+ resetPairRateLimiterForTests();
134
+ });
135
+
136
+ test("rejects non-POST methods with 405", async () => {
137
+ const req = buildRequest({
138
+ method: "GET",
139
+ body: {
140
+ extensionOrigin: ALLOWED_ORIGIN,
141
+ },
142
+ });
143
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
144
+ expect(res.status).toBe(405);
145
+ });
146
+
147
+ test("rejects non-loopback peer with 403", async () => {
148
+ const req = buildRequest({
149
+ body: {
150
+ extensionOrigin: ALLOWED_ORIGIN,
151
+ },
152
+ });
153
+ const res = await handleBrowserExtensionPair(req, publicPeerServer);
154
+ expect(res.status).toBe(403);
155
+ });
156
+
157
+ test("rejects LAN peer (not loopback) with 403", async () => {
158
+ const req = buildRequest({
159
+ body: {
160
+ extensionOrigin: ALLOWED_ORIGIN,
161
+ },
162
+ });
163
+ const res = await handleBrowserExtensionPair(req, lanPeerServer);
164
+ expect(res.status).toBe(403);
165
+ });
166
+
167
+ test("rejects request with non-loopback Host header", async () => {
168
+ const req = buildRequest({
169
+ body: {
170
+ extensionOrigin: ALLOWED_ORIGIN,
171
+ },
172
+ host: "vellum.example.com",
173
+ });
174
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
175
+ expect(res.status).toBe(403);
176
+ });
177
+
178
+ test("rejects request with x-forwarded-for header", async () => {
179
+ const req = buildRequest({
180
+ body: {
181
+ extensionOrigin: ALLOWED_ORIGIN,
182
+ },
183
+ forwardedFor: "1.2.3.4",
184
+ });
185
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
186
+ expect(res.status).toBe(403);
187
+ });
188
+
189
+ // ─────────────────────────────────────────────────────────────────────
190
+ // Native-host marker header enforcement
191
+ // ─────────────────────────────────────────────────────────────────────
192
+
193
+ test("rejects request missing native-host marker header with 403", async () => {
194
+ const req = buildRequest({
195
+ body: { extensionOrigin: ALLOWED_ORIGIN },
196
+ nativeHost: false,
197
+ });
198
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
199
+ expect(res.status).toBe(403);
200
+ const payload = (await res.json()) as {
201
+ error?: { code?: string; message?: string };
202
+ };
203
+ expect(payload.error?.code).toBe("FORBIDDEN");
204
+ });
205
+
206
+ test("rejects request with wrong native-host marker header value", async () => {
207
+ const req = buildRequest({
208
+ body: { extensionOrigin: ALLOWED_ORIGIN },
209
+ nativeHost: "bogus",
210
+ });
211
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
212
+ expect(res.status).toBe(403);
213
+ });
214
+
215
+ test("rejects request with empty native-host marker header value", async () => {
216
+ const req = buildRequest({
217
+ body: { extensionOrigin: ALLOWED_ORIGIN },
218
+ nativeHost: "",
219
+ });
220
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
221
+ expect(res.status).toBe(403);
222
+ });
223
+
224
+ // ─────────────────────────────────────────────────────────────────────
225
+ // Browser-origin rejection
226
+ // ─────────────────────────────────────────────────────────────────────
227
+
228
+ test("rejects request with a non-allowlisted Origin header", async () => {
229
+ const req = buildRequest({
230
+ body: { extensionOrigin: ALLOWED_ORIGIN },
231
+ origin: "https://evil.example.com",
232
+ });
233
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
234
+ expect(res.status).toBe(403);
235
+ const payload = (await res.json()) as {
236
+ error?: { code?: string };
237
+ };
238
+ expect(payload.error?.code).toBe("FORBIDDEN");
239
+ });
240
+
241
+ test("rejects request with http://localhost Origin header (browser-originated)", async () => {
242
+ // A web page served from http://localhost:8080 that POSTs to the
243
+ // pair endpoint would attach this Origin header. The endpoint must
244
+ // refuse it even though the host itself is loopback — a local web
245
+ // page in another browser tab is NOT the native messaging helper.
246
+ const req = buildRequest({
247
+ body: { extensionOrigin: ALLOWED_ORIGIN },
248
+ origin: "http://localhost:8080",
249
+ });
250
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
251
+ expect(res.status).toBe(403);
252
+ });
253
+
254
+ test("accepts request with no Origin header (native-host default)", async () => {
255
+ // Node fetch does not set an Origin header unless explicitly told
256
+ // to — the native messaging helper's `fetch(...)` call therefore
257
+ // ships without one. This is the common-case allowed path.
258
+ const req = buildRequest({
259
+ body: { extensionOrigin: ALLOWED_ORIGIN },
260
+ // origin intentionally omitted
261
+ });
262
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
263
+ expect(res.status).toBe(200);
264
+ });
265
+
266
+ test("accepts request when Origin header equals an allowlisted extension origin", async () => {
267
+ // The allowlist stores entries with a trailing slash. Browsers'
268
+ // Origin headers never carry a path segment, so both the exact
269
+ // match and the bare form should succeed.
270
+ const reqExact = buildRequest({
271
+ body: { extensionOrigin: ALLOWED_ORIGIN },
272
+ origin: ALLOWED_ORIGIN,
273
+ });
274
+ expect(
275
+ (await handleBrowserExtensionPair(reqExact, loopbackServer)).status,
276
+ ).toBe(200);
277
+
278
+ const reqBare = buildRequest({
279
+ body: { extensionOrigin: ALLOWED_ORIGIN },
280
+ origin: ALLOWED_ORIGIN_BARE,
281
+ });
282
+ expect(
283
+ (await handleBrowserExtensionPair(reqBare, loopbackServer)).status,
284
+ ).toBe(200);
285
+ });
286
+
287
+ // ─────────────────────────────────────────────────────────────────────
288
+ // Rate limiting
289
+ // ─────────────────────────────────────────────────────────────────────
290
+
291
+ test("rate limits successive pair requests (429 after burst)", async () => {
292
+ // Fire a tight burst of requests and assert that once the per-peer
293
+ // budget is exhausted, the endpoint returns 429 with the standard
294
+ // error envelope and a Retry-After hint. The rate limiter budget
295
+ // is 10/min per peer IP; we send 12 to ensure we hit it.
296
+ const results: number[] = [];
297
+ for (let i = 0; i < 12; i++) {
298
+ const req = buildRequest({
299
+ body: { extensionOrigin: ALLOWED_ORIGIN },
300
+ });
301
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
302
+ results.push(res.status);
303
+ // Drain the body so any async internal state settles (and so
304
+ // we don't leak unconsumed Response bodies).
305
+ await res.text();
306
+ }
307
+
308
+ // The first 10 requests should succeed (200). The 11th and 12th
309
+ // should be rate limited (429).
310
+ const successes = results.filter((s) => s === 200).length;
311
+ const rateLimited = results.filter((s) => s === 429).length;
312
+ expect(successes).toBe(10);
313
+ expect(rateLimited).toBe(2);
314
+ });
315
+
316
+ test("rate-limited response carries Retry-After and RATE_LIMITED error code", async () => {
317
+ // Exhaust the budget.
318
+ for (let i = 0; i < 10; i++) {
319
+ const req = buildRequest({
320
+ body: { extensionOrigin: ALLOWED_ORIGIN },
321
+ });
322
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
323
+ await res.text();
324
+ }
325
+ // The next request should be rate limited.
326
+ const req = buildRequest({
327
+ body: { extensionOrigin: ALLOWED_ORIGIN },
328
+ });
329
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
330
+ expect(res.status).toBe(429);
331
+ const retryAfter = res.headers.get("Retry-After");
332
+ expect(retryAfter).not.toBeNull();
333
+ // Retry-After should be a positive integer of seconds.
334
+ expect(Number(retryAfter)).toBeGreaterThan(0);
335
+ expect(res.headers.get("X-RateLimit-Limit")).toBe("10");
336
+ expect(res.headers.get("X-RateLimit-Remaining")).toBe("0");
337
+ const payload = (await res.json()) as {
338
+ error?: { code?: string };
339
+ };
340
+ expect(payload.error?.code).toBe("RATE_LIMITED");
341
+ });
342
+
343
+ test("native-host marker check runs BEFORE the rate limiter (unmarked requests don't burn budget)", async () => {
344
+ // Security invariant: an unmarked drive-by POST from a malicious
345
+ // webpage must NOT consume the legitimate 10/min quota. If the
346
+ // rate limiter ran first, a cross-origin page could issue 10
347
+ // unmarked requests per minute and starve the native messaging
348
+ // helper's real pair attempts with 429s until the window reset.
349
+ //
350
+ // We send a large burst of UNMARKED requests (well beyond the
351
+ // 10/min budget). Every single one must return 403 (missing
352
+ // marker), NOT 429 — because the marker check runs first and
353
+ // unmarked requests are supposed to short-circuit the handler
354
+ // without touching the limiter at all.
355
+ const unmarkedResults: number[] = [];
356
+ for (let i = 0; i < 30; i++) {
357
+ const req = buildRequest({
358
+ body: { extensionOrigin: ALLOWED_ORIGIN },
359
+ nativeHost: false, // no marker header
360
+ });
361
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
362
+ unmarkedResults.push(res.status);
363
+ await res.text();
364
+ }
365
+ // Every unmarked request should be 403. If any 429 appeared, the
366
+ // limiter was burning budget on unauthenticated probes.
367
+ expect(unmarkedResults.every((s) => s === 403)).toBe(true);
368
+ expect(unmarkedResults.some((s) => s === 429)).toBe(false);
369
+
370
+ // After 30 unmarked requests, the legitimate native-messaging
371
+ // helper must still have its full 10/min budget intact. Issue 10
372
+ // MARKED requests: all 10 should succeed. The 11th should be the
373
+ // first 429 — proving the limiter only counts marker-carrying
374
+ // requests.
375
+ const markedResults: number[] = [];
376
+ for (let i = 0; i < 11; i++) {
377
+ const req = buildRequest({
378
+ body: { extensionOrigin: ALLOWED_ORIGIN },
379
+ });
380
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
381
+ markedResults.push(res.status);
382
+ await res.text();
383
+ }
384
+ expect(markedResults.slice(0, 10).every((s) => s === 200)).toBe(true);
385
+ expect(markedResults[10]).toBe(429);
386
+ });
387
+
388
+ // ─────────────────────────────────────────────────────────────────────
389
+ // Body validation
390
+ // ─────────────────────────────────────────────────────────────────────
391
+
392
+ test("returns 400 when body is missing", async () => {
393
+ const req = buildRequest({});
394
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
395
+ expect(res.status).toBe(400);
396
+ });
397
+
398
+ test("returns 400 when body is malformed JSON", async () => {
399
+ const req = buildRequest({ rawBody: "{not json" });
400
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
401
+ expect(res.status).toBe(400);
402
+ });
403
+
404
+ test("returns 400 when extensionOrigin is missing", async () => {
405
+ const req = buildRequest({ body: {} });
406
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
407
+ expect(res.status).toBe(400);
408
+ });
409
+
410
+ test("returns 400 when extensionOrigin is not a string", async () => {
411
+ const req = buildRequest({ body: { extensionOrigin: 42 } });
412
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
413
+ expect(res.status).toBe(400);
414
+ });
415
+
416
+ test("returns 400 when legacy origin field is not a string", async () => {
417
+ const req = buildRequest({ body: { origin: 42 } });
418
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
419
+ expect(res.status).toBe(400);
420
+ });
421
+
422
+ test("returns 401 when extensionOrigin is not on the allowlist", async () => {
423
+ const req = buildRequest({
424
+ body: { extensionOrigin: "chrome-extension://not-allowed/" },
425
+ });
426
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
427
+ expect(res.status).toBe(401);
428
+ });
429
+
430
+ test("returns 200 with a valid token for the preferred extensionOrigin field", async () => {
431
+ const req = buildRequest({
432
+ body: {
433
+ extensionOrigin: ALLOWED_ORIGIN,
434
+ },
435
+ });
436
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
437
+ expect(res.status).toBe(200);
438
+
439
+ const payload = (await res.json()) as {
440
+ token: string;
441
+ expiresAt: string;
442
+ guardianId: string;
443
+ };
444
+
445
+ expect(typeof payload.token).toBe("string");
446
+ expect(payload.token.length).toBeGreaterThan(0);
447
+
448
+ // expiresAt must be an ISO 8601 string (matching what the
449
+ // chrome-extension/native-host helper validates) and must be in
450
+ // the future.
451
+ expect(typeof payload.expiresAt).toBe("string");
452
+ expect(payload.expiresAt).toMatch(
453
+ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/,
454
+ );
455
+ const expiresAtMs = Date.parse(payload.expiresAt);
456
+ expect(Number.isNaN(expiresAtMs)).toBe(false);
457
+ expect(expiresAtMs).toBeGreaterThan(Date.now());
458
+
459
+ expect(typeof payload.guardianId).toBe("string");
460
+ expect(payload.guardianId.length).toBeGreaterThan(0);
461
+
462
+ // Token should round-trip through verifyHostBrowserCapability.
463
+ const claims = verifyHostBrowserCapability(payload.token);
464
+ expect(claims).not.toBeNull();
465
+ expect(claims?.capability).toBe("host_browser_command");
466
+ expect(claims?.guardianId).toBe(payload.guardianId);
467
+ // The numeric claim expiry should match the ISO response field.
468
+ expect(claims?.expiresAt).toBe(expiresAtMs);
469
+ });
470
+
471
+ test("returns 200 using the legacy `origin` field for backwards compat", async () => {
472
+ const req = buildRequest({
473
+ body: { origin: ALLOWED_ORIGIN },
474
+ });
475
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
476
+ expect(res.status).toBe(200);
477
+ const payload = (await res.json()) as {
478
+ token: string;
479
+ expiresAt: string;
480
+ };
481
+ expect(typeof payload.token).toBe("string");
482
+ expect(typeof payload.expiresAt).toBe("string");
483
+ });
484
+
485
+ test("prefers extensionOrigin over legacy origin when both are provided", async () => {
486
+ // extensionOrigin is on the allowlist, `origin` is not — so the
487
+ // request must succeed because we honor `extensionOrigin` first.
488
+ const req = buildRequest({
489
+ body: {
490
+ extensionOrigin: ALLOWED_ORIGIN,
491
+ origin: "chrome-extension://not-allowed/",
492
+ },
493
+ });
494
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
495
+ expect(res.status).toBe(200);
496
+ });
497
+
498
+ test("accepts loopback Host header variants", async () => {
499
+ const variants = [
500
+ "localhost:8765",
501
+ "127.0.0.1:8765",
502
+ "127.0.0.1",
503
+ "localhost",
504
+ "127.1.2.3:8765",
505
+ "[::1]:8765",
506
+ "[::1]",
507
+ "::1",
508
+ ];
509
+ for (const host of variants) {
510
+ // Reset the limiter between iterations so the last few variants
511
+ // don't fall over the 10/min budget.
512
+ resetPairRateLimiterForTests();
513
+ const req = buildRequest({
514
+ body: {
515
+ extensionOrigin: ALLOWED_ORIGIN,
516
+ },
517
+ host,
518
+ });
519
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
520
+ expect(res.status).toBe(200);
521
+ }
522
+ });
523
+
524
+ test("rejects malformed bracketed Host header", async () => {
525
+ const req = buildRequest({
526
+ body: {
527
+ extensionOrigin: ALLOWED_ORIGIN,
528
+ },
529
+ host: "[::1", // missing closing bracket
530
+ });
531
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
532
+ expect(res.status).toBe(403);
533
+ });
534
+
535
+ test("rejects bracketed Host header with junk after closing bracket", async () => {
536
+ // Defensive against `[::1]attacker.com`-style injection: the parser
537
+ // used to silently truncate at the first `]` and treat the rest as
538
+ // the hostname, which would let an attacker spoof a non-loopback
539
+ // host while still passing the loopback Host header check.
540
+ const req = buildRequest({
541
+ body: {
542
+ extensionOrigin: ALLOWED_ORIGIN,
543
+ },
544
+ host: "[::1]attacker.com",
545
+ });
546
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
547
+ expect(res.status).toBe(403);
548
+ });
549
+
550
+ test("rejects non-loopback IPv6 Host header", async () => {
551
+ const req = buildRequest({
552
+ body: {
553
+ extensionOrigin: ALLOWED_ORIGIN,
554
+ },
555
+ host: "[2001:db8::1]:8765",
556
+ });
557
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
558
+ expect(res.status).toBe(403);
559
+ });
560
+
561
+ test("parseHostHeader handles IPv4, IPv6, and bracketed forms", () => {
562
+ expect(parseHostHeader("localhost:8765")).toBe("localhost");
563
+ expect(parseHostHeader("127.0.0.1:8765")).toBe("127.0.0.1");
564
+ expect(parseHostHeader("127.0.0.1")).toBe("127.0.0.1");
565
+ expect(parseHostHeader("[::1]:8765")).toBe("::1");
566
+ expect(parseHostHeader("[::1]")).toBe("::1");
567
+ expect(parseHostHeader("::1")).toBe("::1");
568
+ expect(parseHostHeader("[2001:db8::1]:443")).toBe("2001:db8::1");
569
+ expect(parseHostHeader("[::1")).toBeNull();
570
+ expect(parseHostHeader("")).toBeNull();
571
+ // Anything after the closing bracket that isn't an optional ":port"
572
+ // must be rejected — otherwise `[::1]attacker.com` would slip past
573
+ // the loopback check by parsing as `::1`.
574
+ expect(parseHostHeader("[::1]attacker.com")).toBeNull();
575
+ expect(parseHostHeader("[::1]extra")).toBeNull();
576
+ });
577
+
578
+ test("tampered tokens fail verification", async () => {
579
+ const req = buildRequest({
580
+ body: {
581
+ extensionOrigin: ALLOWED_ORIGIN,
582
+ },
583
+ });
584
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
585
+ expect(res.status).toBe(200);
586
+
587
+ const payload = (await res.json()) as { token: string };
588
+ const originalToken = payload.token;
589
+
590
+ // Modify the signature: flip the last character.
591
+ const [head, sig] = originalToken.split(".");
592
+ const lastChar = sig.slice(-1);
593
+ const replacement = lastChar === "A" ? "B" : "A";
594
+ const tamperedToken = `${head}.${sig.slice(0, -1)}${replacement}`;
595
+
596
+ expect(verifyHostBrowserCapability(tamperedToken)).toBeNull();
597
+ // The original token should still verify.
598
+ expect(verifyHostBrowserCapability(originalToken)).not.toBeNull();
599
+ });
600
+
601
+ test("tokens minted with a different secret fail verification", async () => {
602
+ // Mint a token, then swap the secret — verification should fail.
603
+ const req = buildRequest({
604
+ body: {
605
+ extensionOrigin: ALLOWED_ORIGIN,
606
+ },
607
+ });
608
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
609
+ expect(res.status).toBe(200);
610
+ const payload = (await res.json()) as { token: string };
611
+
612
+ // Swap secret and re-verify.
613
+ setCapabilityTokenSecretForTests(randomBytes(32));
614
+ expect(verifyHostBrowserCapability(payload.token)).toBeNull();
615
+ });
616
+
617
+ test("rejects tampered payload even with matching signature length", async () => {
618
+ const req = buildRequest({
619
+ body: {
620
+ extensionOrigin: ALLOWED_ORIGIN,
621
+ },
622
+ });
623
+ const res = await handleBrowserExtensionPair(req, loopbackServer);
624
+ expect(res.status).toBe(200);
625
+ const payload = (await res.json()) as { token: string };
626
+ const [head, sig] = payload.token.split(".");
627
+
628
+ // Swap the payload for a different base64url value of equivalent shape.
629
+ const bogusPayload = Buffer.from(
630
+ JSON.stringify({
631
+ capability: "host_browser_command",
632
+ guardianId: "attacker",
633
+ nonce: "00".repeat(16),
634
+ expiresAt: Date.now() + 60_000,
635
+ }),
636
+ "utf8",
637
+ )
638
+ .toString("base64")
639
+ .replace(/\+/g, "-")
640
+ .replace(/\//g, "_")
641
+ .replace(/=+$/, "");
642
+
643
+ const tampered = `${bogusPayload}.${sig}`;
644
+ // Keep `head` referenced so the test reads naturally even though we
645
+ // do not use it after tampering.
646
+ expect(head).toBeTruthy();
647
+ expect(verifyHostBrowserCapability(tampered)).toBeNull();
648
+ });
649
+ });
650
+
651
+ // ---------------------------------------------------------------------------
652
+ // parseHostHeader — table-driven unit tests for IPv6 / port / malformed edge
653
+ // cases. The handler-level tests above cover end-to-end accept/reject of the
654
+ // pair endpoint; these tests pin down the parser contract itself so that a
655
+ // future refactor of `parseHostHeader` cannot silently change its semantics.
656
+ //
657
+ // Important: `parseHostHeader` does NOT lowercase the hostname — case
658
+ // normalization happens later in `isLoopbackHostHeader`. The tests below
659
+ // reflect that.
660
+ // ---------------------------------------------------------------------------
661
+ describe("parseHostHeader", () => {
662
+ // [input, expected-parseHostHeader-output]. `null` means "malformed".
663
+ const cases: Array<[string, string | null]> = [
664
+ // IPv4 with port
665
+ ["127.0.0.1:7821", "127.0.0.1"],
666
+ // Bare IPv4
667
+ ["127.0.0.1", "127.0.0.1"],
668
+ // IPv4 in 127.0.0.0/8 range
669
+ ["127.1.2.3:7821", "127.1.2.3"],
670
+ // IPv6 with brackets and port
671
+ ["[::1]:7821", "::1"],
672
+ // IPv6 with brackets, no port
673
+ ["[::1]", "::1"],
674
+ // Bare IPv6 (no brackets, no port) — two or more colons, no brackets,
675
+ // so treated as a whole IPv6 literal rather than split at the first colon.
676
+ ["::1", "::1"],
677
+ // Non-loopback IPv6 with brackets
678
+ ["[2001:db8::1]:443", "2001:db8::1"],
679
+ // Non-loopback IPv6, bare (multi-colon → treated as IPv6 literal)
680
+ ["2001:db8::1", "2001:db8::1"],
681
+ // Hostname with port
682
+ ["localhost:7821", "localhost"],
683
+ // Bare hostname
684
+ ["localhost", "localhost"],
685
+ // Mixed-case hostname — the parser preserves case; downstream
686
+ // `isLoopbackHostHeader` is responsible for case folding.
687
+ ["LocalHost:7821", "LocalHost"],
688
+ // Empty string
689
+ ["", null],
690
+ // Malformed: content after the closing bracket that isn't `:port`.
691
+ // Critical security case: `[::1]attacker.com` would slip a non-loopback
692
+ // hostname past a naive parser that truncates at `]`.
693
+ ["[::1]attacker.com", null],
694
+ ["[::1]extra", null],
695
+ // Malformed: unbalanced brackets (missing closing `]`)
696
+ ["[::1", null],
697
+ // Malformed: unbalanced brackets (missing opening `[`) — the leading
698
+ // character is not `[`, so the bare-host path runs; `"]":"port"` has
699
+ // two colons so it's treated as an IPv6 literal (garbage in, garbage
700
+ // out for this edge case — documented for visibility).
701
+ ["::1]:7821", "::1]:7821"],
702
+ // Empty brackets: after `[` we see `]` at index 1, after `]` is `:7821`
703
+ // which is a valid port, so the parser returns the substring between
704
+ // the brackets — the empty string. `isLoopbackHostHeader` then rejects
705
+ // it because `""` is not a loopback address.
706
+ ["[]:7821", ""],
707
+ // Empty brackets, no port
708
+ ["[]", ""],
709
+ ];
710
+ for (const [input, expected] of cases) {
711
+ test(`parseHostHeader(${JSON.stringify(input)}) returns ${JSON.stringify(expected)}`, () => {
712
+ expect(parseHostHeader(input)).toBe(expected);
713
+ });
714
+ }
715
+ });