@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,914 @@
1
+ import {
2
+ type BrowserBackend,
3
+ BrowserSessionManager,
4
+ type CdpCommand,
5
+ type CdpResult,
6
+ createCdpInspectBackend,
7
+ createExtensionBackend,
8
+ createLocalBackend,
9
+ } from "../../../browser-session/index.js";
10
+ import { getConfig } from "../../../config/loader.js";
11
+ import { getLogger } from "../../../util/logger.js";
12
+ import type { ToolContext } from "../../types.js";
13
+ import { createCdpInspectClient } from "./cdp-inspect-client.js";
14
+ import { CdpError } from "./errors.js";
15
+ import { createExtensionCdpClient } from "./extension-cdp-client.js";
16
+ import { createLocalCdpClient } from "./local-cdp-client.js";
17
+ import type {
18
+ AttemptDiagnostic,
19
+ BackendCandidate,
20
+ BrowserMode,
21
+ CdpClient,
22
+ CdpClientKind,
23
+ ScopedCdpClient,
24
+ } from "./types.js";
25
+
26
+ const log = getLogger("cdp-factory");
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Desktop-auto cdp-inspect cooldown tracker
30
+ // ---------------------------------------------------------------------------
31
+
32
+ /**
33
+ * Module-level timestamp (epoch ms) of the last transport-level failure for
34
+ * a desktop-auto cdp-inspect attempt. While `Date.now() - _desktopAutoCooldownSince`
35
+ * is less than the configured `desktopAuto.cooldownMs`, the factory skips the
36
+ * automatic cdp-inspect candidate and goes straight to the local backend.
37
+ *
38
+ * **Process-global scope**: this is a module-level singleton that affects ALL
39
+ * conversations in the process. A cdp-inspect failure on any conversation
40
+ * suppresses desktop-auto probes for every conversation in this daemon until
41
+ * the cooldown expires. This is intentional -- the local loopback CDP
42
+ * endpoint is per-machine, not per-conversation, so a failure on one
43
+ * conversation implies all others would fail the same way.
44
+ *
45
+ * Reset to 0 when the cooldown expires or when manually cleared via
46
+ * {@link _resetDesktopAutoCooldown} (for testing).
47
+ */
48
+ let _desktopAutoCooldownSince = 0;
49
+
50
+ /**
51
+ * Record a cooldown after a desktop-auto cdp-inspect transport failure.
52
+ * Called by {@link maybeRecordDesktopAutoCooldown} in production; also
53
+ * exported directly for use in tests.
54
+ */
55
+ export function recordDesktopAutoCooldown(): void {
56
+ _desktopAutoCooldownSince = Date.now();
57
+ }
58
+
59
+ /**
60
+ * Whether the desktop-auto cdp-inspect cooldown is currently active.
61
+ * Returns `true` if a failure was recorded and the configured cooldown
62
+ * window has not yet elapsed.
63
+ */
64
+ export function isDesktopAutoCooldownActive(cooldownMs: number): boolean {
65
+ if (_desktopAutoCooldownSince === 0 || cooldownMs <= 0) return false;
66
+ return Date.now() - _desktopAutoCooldownSince < cooldownMs;
67
+ }
68
+
69
+ /**
70
+ * Reset the desktop-auto cooldown state. Exported for testing only.
71
+ */
72
+ export function _resetDesktopAutoCooldown(): void {
73
+ _desktopAutoCooldownSince = 0;
74
+ }
75
+
76
+ /**
77
+ * Get the raw cooldown-since timestamp. Exported for testing only.
78
+ */
79
+ export function _getDesktopAutoCooldownSince(): number {
80
+ return _desktopAutoCooldownSince;
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // Public API
85
+ // ---------------------------------------------------------------------------
86
+
87
+ /**
88
+ * Options for {@link getCdpClient}. All fields are optional — omitting
89
+ * them preserves the existing auto-mode behavior.
90
+ */
91
+ export interface GetCdpClientOptions {
92
+ /**
93
+ * Backend mode preference. When omitted or `"auto"`, the factory
94
+ * uses the existing priority-ordered fallback chain. When set to a
95
+ * specific backend kind, the factory pins to that single backend
96
+ * and disables failover.
97
+ */
98
+ mode?: BrowserMode;
99
+ }
100
+
101
+ /**
102
+ * Select the appropriate CdpClient implementation for a tool
103
+ * invocation based on the ToolContext and config. Three backends are
104
+ * considered in priority order:
105
+ *
106
+ * 1. **Extension** -- When `context.hostBrowserProxy` is set AND
107
+ * `hostBrowserProxy.isAvailable()` returns `true` (i.e. the
108
+ * proxy exists and the client is actually connected). This
109
+ * prevents selecting the extension transport when the proxy
110
+ * object exists but the underlying WebSocket is disconnected.
111
+ * 2. **cdp-inspect** -- When `hostBrowser.cdpInspect.enabled` is
112
+ * `true` in config, construct a `CdpInspectClient` that attaches
113
+ * to an already-running Chrome via the DevTools JSON protocol.
114
+ * On macOS, cdp-inspect is also included automatically when
115
+ * `desktopAuto.enabled` is true (the default), even when the
116
+ * top-level `enabled` flag is false.
117
+ * 3. **Local** -- Default. Drives Playwright's CDPSession against
118
+ * the sacrificial-profile browser managed by browserManager.
119
+ *
120
+ * When `options.mode` is set to a specific backend kind, the factory
121
+ * builds exactly one candidate and disables failover. If the pinned
122
+ * backend is unavailable (e.g. pinned `extension` without an
123
+ * available host browser proxy), the factory throws a typed
124
+ * `CdpError` with `transport_error` code and a diagnostic indicating
125
+ * the precondition that was not met.
126
+ *
127
+ * The factory builds an ordered candidate list and returns a
128
+ * {@link ScopedCdpClient} with per-invocation failover semantics:
129
+ *
130
+ * - On the first `send()`, the top-ranked candidate is selected and
131
+ * its backend is materialised.
132
+ * - If the first command fails with a **transport-level** error
133
+ * (`transport_error`), the factory tears down the failed backend
134
+ * and retries the same command against the next candidate.
135
+ * - **CDP protocol errors** (`cdp_error`) do NOT trigger failover --
136
+ * they indicate the browser understood the command and rejected it,
137
+ * so hopping transports would not help.
138
+ * - After the first successful CDP command, the backend becomes
139
+ * **sticky** for the remainder of the invocation. Subsequent
140
+ * commands always route through the same backend so multi-command
141
+ * tool flows do not hop transports mid-step.
142
+ *
143
+ * IMPORTANT: the returned client is per-invocation. Tools MUST call
144
+ * `dispose()` in a finally block. Dispose tears down the manager's
145
+ * session and the underlying CDP client. Disposing an extension-backed
146
+ * client does NOT dispose the underlying HostBrowserProxy -- that is
147
+ * owned by the conversation.
148
+ */
149
+ export function getCdpClient(
150
+ context: ToolContext,
151
+ options?: GetCdpClientOptions,
152
+ ): ScopedCdpClient {
153
+ const mode: BrowserMode = options?.mode ?? "auto";
154
+ const candidates =
155
+ mode === "auto"
156
+ ? buildCandidateList(context)
157
+ : buildPinnedCandidateList(context, mode);
158
+
159
+ log.debug(
160
+ {
161
+ conversationId: context.conversationId,
162
+ mode,
163
+ candidates: candidates.map((c) => ({ kind: c.kind, reason: c.reason })),
164
+ },
165
+ "CDP factory: built candidate list",
166
+ );
167
+
168
+ return buildChainedClient(context.conversationId, candidates, mode);
169
+ }
170
+
171
+ // ---------------------------------------------------------------------------
172
+ // Pinned candidate list construction
173
+ // ---------------------------------------------------------------------------
174
+
175
+ /**
176
+ * Build a single-element candidate list for a pinned backend mode.
177
+ * Throws a typed `CdpError` with structured diagnostics when the
178
+ * requested backend's preconditions are not met.
179
+ *
180
+ * Exported for testing.
181
+ */
182
+ export function buildPinnedCandidateList(
183
+ context: ToolContext,
184
+ mode: Exclude<BrowserMode, "auto">,
185
+ ): BackendCandidate[] {
186
+ const { conversationId, hostBrowserProxy } = context;
187
+
188
+ switch (mode) {
189
+ case "extension": {
190
+ if (!hostBrowserProxy || !hostBrowserProxy.isAvailable()) {
191
+ const reason = !hostBrowserProxy
192
+ ? "no host browser proxy provisioned for this conversation"
193
+ : "host browser proxy exists but is not connected";
194
+ throw new CdpError(
195
+ "transport_error",
196
+ `Pinned mode "extension" unavailable: ${reason}`,
197
+ {
198
+ attemptDiagnostics: [
199
+ {
200
+ candidateKind: "extension",
201
+ inclusionReason: `pinned mode: extension`,
202
+ stage: "candidate_selection",
203
+ errorCode: "transport_error",
204
+ errorMessage: reason,
205
+ },
206
+ ],
207
+ },
208
+ );
209
+ }
210
+ return [
211
+ {
212
+ kind: "extension",
213
+ reason: "pinned mode: extension",
214
+ create() {
215
+ const client = createExtensionCdpClient(
216
+ hostBrowserProxy,
217
+ conversationId,
218
+ );
219
+ const backend = createExtensionBackend({
220
+ isAvailable: () => true,
221
+ sendCdp: (command, signal) =>
222
+ dispatchThroughClient(client, command, signal),
223
+ dispose: () => client.dispose(),
224
+ });
225
+ return { client, backend };
226
+ },
227
+ },
228
+ ];
229
+ }
230
+ case "cdp-inspect": {
231
+ const cdpInspectConfig = getConfig().hostBrowser.cdpInspect;
232
+ return [
233
+ {
234
+ kind: "cdp-inspect",
235
+ reason: "pinned mode: cdp-inspect",
236
+ create() {
237
+ const client = createCdpInspectClient(conversationId, {
238
+ host: cdpInspectConfig.host,
239
+ port: cdpInspectConfig.port,
240
+ discoveryTimeoutMs: cdpInspectConfig.probeTimeoutMs,
241
+ });
242
+ const backend = createCdpInspectBackend({
243
+ isAvailable: () => true,
244
+ sendCdp: (command, signal) =>
245
+ dispatchThroughClient(client, command, signal),
246
+ dispose: () => client.dispose(),
247
+ });
248
+ return { client, backend };
249
+ },
250
+ },
251
+ ];
252
+ }
253
+ case "local": {
254
+ return [
255
+ {
256
+ kind: "local",
257
+ reason: "pinned mode: local",
258
+ create() {
259
+ const client = createLocalCdpClient(conversationId);
260
+ const backend = createLocalBackend({
261
+ isAvailable: () => true,
262
+ sendCdp: (command, signal) =>
263
+ dispatchThroughClient(client, command, signal),
264
+ dispose: () => client.dispose(),
265
+ });
266
+ return { client, backend };
267
+ },
268
+ },
269
+ ];
270
+ }
271
+ default: {
272
+ // Exhaustive check — if new modes are added, TypeScript will
273
+ // flag this as an error.
274
+ const _exhaustive: never = mode;
275
+ throw new Error(`Unknown pinned mode: ${_exhaustive}`);
276
+ }
277
+ }
278
+ }
279
+
280
+ // ---------------------------------------------------------------------------
281
+ // Candidate list construction (auto mode)
282
+ // ---------------------------------------------------------------------------
283
+
284
+ /**
285
+ * Build an ordered list of backend candidates from the tool context
286
+ * and config. Candidates are evaluated lazily -- `create()` is only
287
+ * called when the candidate is actually selected.
288
+ *
289
+ * Exported for testing.
290
+ */
291
+ export function buildCandidateList(context: ToolContext): BackendCandidate[] {
292
+ const { conversationId, hostBrowserProxy } = context;
293
+ const candidates: BackendCandidate[] = [];
294
+
295
+ // 1. Extension -- preferred when a chrome-extension is bound AND
296
+ // the proxy reports it is connected. Checking isAvailable()
297
+ // prevents selecting the extension transport when the proxy
298
+ // object exists (e.g. it was provisioned at conversation start)
299
+ // but the client has since disconnected.
300
+ if (hostBrowserProxy && hostBrowserProxy.isAvailable()) {
301
+ candidates.push({
302
+ kind: "extension",
303
+ reason: "hostBrowserProxy present and available",
304
+ create() {
305
+ const client = createExtensionCdpClient(
306
+ hostBrowserProxy,
307
+ conversationId,
308
+ );
309
+ const backend = createExtensionBackend({
310
+ isAvailable: () => true,
311
+ sendCdp: (command, signal) =>
312
+ dispatchThroughClient(client, command, signal),
313
+ dispose: () => client.dispose(),
314
+ });
315
+ return { client, backend };
316
+ },
317
+ });
318
+ } else if (hostBrowserProxy) {
319
+ log.debug(
320
+ { conversationId },
321
+ "CDP factory: hostBrowserProxy present but not available, skipping extension candidate",
322
+ );
323
+ }
324
+
325
+ // 2. cdp-inspect -- opt-in via config OR desktop-auto for macOS turns.
326
+ const cdpInspectConfig = getConfig().hostBrowser.cdpInspect;
327
+ if (cdpInspectConfig.enabled) {
328
+ // Explicitly enabled in config -- always include regardless of platform.
329
+ candidates.push({
330
+ kind: "cdp-inspect",
331
+ reason: "cdpInspect enabled in config",
332
+ create() {
333
+ const client = createCdpInspectClient(conversationId, {
334
+ host: cdpInspectConfig.host,
335
+ port: cdpInspectConfig.port,
336
+ discoveryTimeoutMs: cdpInspectConfig.probeTimeoutMs,
337
+ });
338
+ const backend = createCdpInspectBackend({
339
+ isAvailable: () => true,
340
+ sendCdp: (command, signal) =>
341
+ dispatchThroughClient(client, command, signal),
342
+ dispose: () => client.dispose(),
343
+ });
344
+ return { client, backend };
345
+ },
346
+ });
347
+ } else if (
348
+ context.transportInterface === "macos" &&
349
+ cdpInspectConfig.desktopAuto.enabled
350
+ ) {
351
+ // macOS desktop-auto: include cdp-inspect as a candidate unless:
352
+ // (a) the hostBrowserProxy exists but is temporarily unavailable
353
+ // (extension transport expected but transiently disconnected --
354
+ // inserting cdp-inspect here would cause a silent takeover), or
355
+ // (b) the cooldown from a recent failure is still active.
356
+ //
357
+ // When no hostBrowserProxy is present at all (extension not
358
+ // provisioned for this conversation), cdp-inspect remains available
359
+ // as a fallback per the desktop-auto contract.
360
+ if (hostBrowserProxy && !hostBrowserProxy.isAvailable()) {
361
+ log.debug(
362
+ { conversationId },
363
+ "CDP factory: desktop-auto cdp-inspect skipped (extension transport expected but temporarily unavailable)",
364
+ );
365
+ } else {
366
+ const { cooldownMs } = cdpInspectConfig.desktopAuto;
367
+ if (isDesktopAutoCooldownActive(cooldownMs)) {
368
+ log.debug(
369
+ {
370
+ conversationId,
371
+ cooldownMs,
372
+ cooldownSince: _desktopAutoCooldownSince,
373
+ },
374
+ "CDP factory: desktop-auto cdp-inspect skipped (cooldown active)",
375
+ );
376
+ } else {
377
+ candidates.push({
378
+ kind: "cdp-inspect",
379
+ reason: "desktopAuto: macOS turn, cdp-inspect auto-attempted",
380
+ create() {
381
+ const client = createCdpInspectClient(conversationId, {
382
+ host: cdpInspectConfig.host,
383
+ port: cdpInspectConfig.port,
384
+ discoveryTimeoutMs: cdpInspectConfig.probeTimeoutMs,
385
+ wsConnectTimeoutMs: cdpInspectConfig.probeTimeoutMs,
386
+ });
387
+ const backend = createCdpInspectBackend({
388
+ isAvailable: () => true,
389
+ sendCdp: (command, signal) =>
390
+ dispatchThroughClient(client, command, signal),
391
+ dispose: () => client.dispose(),
392
+ });
393
+ return { client, backend };
394
+ },
395
+ });
396
+ }
397
+ }
398
+ }
399
+
400
+ // 3. Local -- always present as the final fallback.
401
+ candidates.push({
402
+ kind: "local",
403
+ reason: "default Playwright fallback",
404
+ create() {
405
+ const client = createLocalCdpClient(conversationId);
406
+ const backend = createLocalBackend({
407
+ isAvailable: () => true,
408
+ sendCdp: (command, signal) =>
409
+ dispatchThroughClient(client, command, signal),
410
+ dispose: () => client.dispose(),
411
+ });
412
+ return { client, backend };
413
+ },
414
+ });
415
+
416
+ return candidates;
417
+ }
418
+
419
+ // ---------------------------------------------------------------------------
420
+ // Chained client with per-invocation failover
421
+ // ---------------------------------------------------------------------------
422
+
423
+ /**
424
+ * Build a {@link ScopedCdpClient} that walks the candidate list on
425
+ * the first command, failing over on transport-level errors, and
426
+ * becomes sticky after the first successful CDP command.
427
+ *
428
+ * Exported for testing.
429
+ */
430
+ export function buildChainedClient(
431
+ conversationId: string,
432
+ candidates: BackendCandidate[],
433
+ mode: BrowserMode = "auto",
434
+ ): ScopedCdpClient {
435
+ if (candidates.length === 0) {
436
+ throw new Error("CDP factory: no backend candidates available");
437
+ }
438
+
439
+ /** Active backend state -- populated after first successful command. */
440
+ let active: {
441
+ kind: CdpClientKind;
442
+ manager: BrowserSessionManager;
443
+ sessionId: string;
444
+ } | null = null;
445
+
446
+ /** Set to true after the first successful CDP command. */
447
+ let sticky = false;
448
+
449
+ let disposed = false;
450
+
451
+ /**
452
+ * Track all materialised backends so dispose() can tear them all
453
+ * down, even ones that were tried and failed before the sticky
454
+ * backend was established.
455
+ */
456
+ const materialisedManagers: BrowserSessionManager[] = [];
457
+
458
+ /**
459
+ * The kind of the currently active (or last attempted) backend.
460
+ * Before the first send this reflects the first candidate; after
461
+ * the sticky backend is established it reflects the chosen kind.
462
+ */
463
+ let currentKind: CdpClientKind = candidates[0].kind;
464
+
465
+ const scopedClient: ScopedCdpClient = {
466
+ get kind(): CdpClientKind {
467
+ return active?.kind ?? currentKind;
468
+ },
469
+ conversationId,
470
+
471
+ async send<T = unknown>(
472
+ method: string,
473
+ params?: Record<string, unknown>,
474
+ signal?: AbortSignal,
475
+ ): Promise<T> {
476
+ if (disposed) {
477
+ throw new CdpError("disposed", "CdpClient already disposed", {
478
+ cdpMethod: method,
479
+ cdpParams: params,
480
+ });
481
+ }
482
+
483
+ // Fast path: backend is already sticky -- route directly.
484
+ if (sticky && active) {
485
+ const command: CdpCommand = { method, params };
486
+ const envelope = await active.manager.send(
487
+ active.sessionId,
488
+ command,
489
+ signal,
490
+ );
491
+ return unwrapResult<T>(envelope, method, params);
492
+ }
493
+
494
+ // Slow path: walk the candidate list with failover.
495
+ return sendWithFailover<T>(
496
+ candidates,
497
+ materialisedManagers,
498
+ method,
499
+ params,
500
+ signal,
501
+ (established) => {
502
+ active = established;
503
+ sticky = true;
504
+ currentKind = established.kind;
505
+ },
506
+ () => disposed,
507
+ conversationId,
508
+ mode,
509
+ );
510
+ },
511
+
512
+ dispose(): void {
513
+ if (disposed) return;
514
+ disposed = true;
515
+ for (const m of materialisedManagers) {
516
+ m.disposeAll();
517
+ }
518
+ materialisedManagers.length = 0;
519
+ active = null;
520
+ },
521
+ };
522
+
523
+ return scopedClient;
524
+ }
525
+
526
+ /**
527
+ * Walk the candidate list attempting to execute a single CDP command.
528
+ * Transport-level failures trigger failover to the next candidate;
529
+ * CDP protocol errors propagate immediately.
530
+ *
531
+ * When a desktop-auto cdp-inspect candidate fails with a transport
532
+ * error, the factory records a cooldown so subsequent calls skip the
533
+ * probe until the window expires.
534
+ *
535
+ * In auto mode, each attempted candidate is recorded as an
536
+ * {@link AttemptDiagnostic}. When fallback occurs, a production-visible
537
+ * log is emitted with the full candidate sequence and per-candidate
538
+ * failure reasons. If all candidates are exhausted, the diagnostics
539
+ * are attached to the thrown {@link CdpError}.
540
+ */
541
+ async function sendWithFailover<T>(
542
+ candidates: BackendCandidate[],
543
+ materialisedManagers: BrowserSessionManager[],
544
+ method: string,
545
+ params: Record<string, unknown> | undefined,
546
+ signal: AbortSignal | undefined,
547
+ onEstablished: (active: {
548
+ kind: CdpClientKind;
549
+ manager: BrowserSessionManager;
550
+ sessionId: string;
551
+ }) => void,
552
+ isDisposed: () => boolean,
553
+ conversationId: string,
554
+ mode: BrowserMode,
555
+ ): Promise<T> {
556
+ let lastError: CdpError | undefined;
557
+ const diagnostics: AttemptDiagnostic[] = [];
558
+
559
+ for (let i = 0; i < candidates.length; i++) {
560
+ const candidate = candidates[i];
561
+ if (isDisposed()) {
562
+ throw new CdpError("disposed", "CdpClient already disposed", {
563
+ cdpMethod: method,
564
+ cdpParams: params,
565
+ });
566
+ }
567
+
568
+ log.debug(
569
+ {
570
+ conversationId,
571
+ candidateKind: candidate.kind,
572
+ candidateIndex: i,
573
+ method,
574
+ },
575
+ "CDP factory: attempting candidate",
576
+ );
577
+
578
+ let backend: BrowserBackend;
579
+ try {
580
+ const created = candidate.create();
581
+ backend = created.backend;
582
+ } catch (err) {
583
+ // Backend construction failed -- treat as transport error and
584
+ // try the next candidate.
585
+ const errorMessage = `Backend ${candidate.kind} construction failed: ${err instanceof Error ? err.message : String(err)}`;
586
+ log.debug(
587
+ { conversationId, candidateKind: candidate.kind, err },
588
+ "CDP factory: candidate construction failed, trying next",
589
+ );
590
+ lastError = new CdpError("transport_error", errorMessage, {
591
+ cdpMethod: method,
592
+ cdpParams: params,
593
+ underlying: err,
594
+ });
595
+ diagnostics.push({
596
+ candidateKind: candidate.kind,
597
+ inclusionReason: candidate.reason,
598
+ stage: "construction",
599
+ errorCode: "transport_error",
600
+ errorMessage,
601
+ });
602
+ maybeRecordDesktopAutoCooldown(candidate);
603
+
604
+ // Emit production-visible fallback log in auto mode
605
+ if (mode === "auto" && i < candidates.length - 1) {
606
+ log.warn(
607
+ {
608
+ conversationId,
609
+ failedCandidate: candidate.kind,
610
+ nextCandidate: candidates[i + 1].kind,
611
+ attemptedSoFar: diagnostics.map((d) => ({
612
+ kind: d.candidateKind,
613
+ stage: d.stage,
614
+ errorCode: d.errorCode,
615
+ errorMessage: d.errorMessage,
616
+ })),
617
+ },
618
+ "CDP factory: auto-mode fallback triggered",
619
+ );
620
+ }
621
+ continue;
622
+ }
623
+
624
+ const manager = new BrowserSessionManager({ backends: [backend] });
625
+ materialisedManagers.push(manager);
626
+ const session = manager.createSession();
627
+
628
+ const command: CdpCommand = { method, params };
629
+ let envelope: CdpResult;
630
+ try {
631
+ envelope = await manager.send(session.id, command, signal);
632
+ } catch (err) {
633
+ // Manager-level errors (unknown session, no available backend)
634
+ // are transport-level problems -- try the next candidate.
635
+ const errorMessage = `Backend ${candidate.kind} send threw: ${err instanceof Error ? err.message : String(err)}`;
636
+ log.debug(
637
+ { conversationId, candidateKind: candidate.kind, err },
638
+ "CDP factory: candidate send threw, trying next",
639
+ );
640
+ manager.disposeAll();
641
+ lastError = new CdpError("transport_error", errorMessage, {
642
+ cdpMethod: method,
643
+ cdpParams: params,
644
+ underlying: err,
645
+ });
646
+ diagnostics.push({
647
+ candidateKind: candidate.kind,
648
+ inclusionReason: candidate.reason,
649
+ stage: "send",
650
+ errorCode: "transport_error",
651
+ errorMessage,
652
+ discoveryCode: extractDiscoveryCode(err),
653
+ });
654
+ maybeRecordDesktopAutoCooldown(candidate);
655
+
656
+ // Emit production-visible fallback log in auto mode
657
+ if (mode === "auto" && i < candidates.length - 1) {
658
+ log.warn(
659
+ {
660
+ conversationId,
661
+ failedCandidate: candidate.kind,
662
+ nextCandidate: candidates[i + 1].kind,
663
+ attemptedSoFar: diagnostics.map((d) => ({
664
+ kind: d.candidateKind,
665
+ stage: d.stage,
666
+ errorCode: d.errorCode,
667
+ errorMessage: d.errorMessage,
668
+ })),
669
+ },
670
+ "CDP factory: auto-mode fallback triggered",
671
+ );
672
+ }
673
+ continue;
674
+ }
675
+
676
+ // Inspect the envelope for errors. Transport-level errors trigger
677
+ // failover; CDP protocol errors propagate immediately.
678
+ if (envelope.error) {
679
+ const cdpError = extractCdpError(envelope, method, params);
680
+
681
+ if (isTransportFailover(cdpError) && i < candidates.length - 1) {
682
+ log.debug(
683
+ {
684
+ conversationId,
685
+ candidateKind: candidate.kind,
686
+ errorCode: cdpError.code,
687
+ errorMessage: cdpError.message,
688
+ },
689
+ "CDP factory: transport-level failure, failing over to next candidate",
690
+ );
691
+ manager.disposeAll();
692
+ lastError = cdpError;
693
+ diagnostics.push({
694
+ candidateKind: candidate.kind,
695
+ inclusionReason: candidate.reason,
696
+ stage: "send",
697
+ errorCode: cdpError.code,
698
+ errorMessage: cdpError.message,
699
+ discoveryCode: extractDiscoveryCode(cdpError.underlying),
700
+ });
701
+ maybeRecordDesktopAutoCooldown(candidate);
702
+
703
+ // Emit production-visible fallback log in auto mode
704
+ if (mode === "auto") {
705
+ log.warn(
706
+ {
707
+ conversationId,
708
+ failedCandidate: candidate.kind,
709
+ nextCandidate: candidates[i + 1].kind,
710
+ attemptedSoFar: diagnostics.map((d) => ({
711
+ kind: d.candidateKind,
712
+ stage: d.stage,
713
+ errorCode: d.errorCode,
714
+ errorMessage: d.errorMessage,
715
+ })),
716
+ },
717
+ "CDP factory: auto-mode fallback triggered",
718
+ );
719
+ }
720
+ continue;
721
+ }
722
+
723
+ // Either a CDP protocol error or we've exhausted candidates --
724
+ // propagate the error as-is, attaching diagnostics.
725
+ diagnostics.push({
726
+ candidateKind: candidate.kind,
727
+ inclusionReason: candidate.reason,
728
+ stage: "send",
729
+ errorCode: cdpError.code,
730
+ errorMessage: cdpError.message,
731
+ discoveryCode: extractDiscoveryCode(cdpError.underlying),
732
+ });
733
+ throw new CdpError(cdpError.code, cdpError.message, {
734
+ cdpMethod: cdpError.cdpMethod,
735
+ cdpParams: cdpError.cdpParams,
736
+ underlying: cdpError.underlying,
737
+ attemptDiagnostics: diagnostics.length > 0 ? diagnostics : undefined,
738
+ });
739
+ }
740
+
741
+ // Success! Establish this backend as the sticky choice.
742
+ diagnostics.push({
743
+ candidateKind: candidate.kind,
744
+ inclusionReason: candidate.reason,
745
+ stage: "success",
746
+ });
747
+
748
+ // If there were prior failed candidates in auto mode, log the
749
+ // full sequence for observability.
750
+ if (mode === "auto" && diagnostics.length > 1) {
751
+ log.warn(
752
+ {
753
+ conversationId,
754
+ stickyCandidate: candidate.kind,
755
+ attemptSequence: diagnostics.map((d) => ({
756
+ kind: d.candidateKind,
757
+ stage: d.stage,
758
+ errorCode: d.errorCode,
759
+ errorMessage: d.errorMessage,
760
+ })),
761
+ },
762
+ "CDP factory: auto-mode fallback completed, backend established after retries",
763
+ );
764
+ }
765
+
766
+ log.debug(
767
+ { conversationId, candidateKind: candidate.kind, method },
768
+ "CDP factory: candidate succeeded, backend is now sticky",
769
+ );
770
+ onEstablished({ kind: candidate.kind, manager, sessionId: session.id });
771
+ return envelope.result as T;
772
+ }
773
+
774
+ // All candidates exhausted -- throw the last transport error with
775
+ // full attempt diagnostics attached.
776
+ throw lastError
777
+ ? new CdpError(lastError.code, lastError.message, {
778
+ cdpMethod: lastError.cdpMethod,
779
+ cdpParams: lastError.cdpParams,
780
+ underlying: lastError.underlying,
781
+ attemptDiagnostics: diagnostics.length > 0 ? diagnostics : undefined,
782
+ })
783
+ : new CdpError("transport_error", "All backend candidates exhausted", {
784
+ cdpMethod: method,
785
+ cdpParams: params,
786
+ attemptDiagnostics: diagnostics.length > 0 ? diagnostics : undefined,
787
+ });
788
+ }
789
+
790
+ /**
791
+ * If the failed candidate is a desktop-auto cdp-inspect attempt,
792
+ * record the cooldown so subsequent calls skip the probe.
793
+ */
794
+ function maybeRecordDesktopAutoCooldown(candidate: BackendCandidate): void {
795
+ if (
796
+ candidate.kind === "cdp-inspect" &&
797
+ candidate.reason.startsWith("desktopAuto:")
798
+ ) {
799
+ log.debug(
800
+ "CDP factory: recording desktop-auto cdp-inspect cooldown after transport failure",
801
+ );
802
+ recordDesktopAutoCooldown();
803
+ }
804
+ }
805
+
806
+ /**
807
+ * Determine whether a CdpError should trigger failover to the next
808
+ * candidate. Only transport-level failures are eligible -- CDP
809
+ * protocol errors indicate the browser understood the command and
810
+ * rejected it, so retrying on a different transport would not help.
811
+ */
812
+ function isTransportFailover(err: CdpError): boolean {
813
+ return err.code === "transport_error";
814
+ }
815
+
816
+ // ---------------------------------------------------------------------------
817
+ // Helpers (shared with the old implementation)
818
+ // ---------------------------------------------------------------------------
819
+
820
+ /**
821
+ * Extract a CdpError from a CdpResult envelope that carries an error.
822
+ */
823
+ function extractCdpError(
824
+ envelope: CdpResult,
825
+ method: string,
826
+ params?: Record<string, unknown>,
827
+ ): CdpError {
828
+ if (envelope.error?.data instanceof CdpError) {
829
+ return envelope.error.data;
830
+ }
831
+ return new CdpError(
832
+ "cdp_error",
833
+ envelope.error?.message ?? "Unknown CDP error",
834
+ {
835
+ cdpMethod: method,
836
+ cdpParams: params,
837
+ underlying: envelope.error,
838
+ },
839
+ );
840
+ }
841
+
842
+ /**
843
+ * Adapter that makes an existing `CdpClient` look like a
844
+ * `BrowserBackend.send`. Converts thrown CdpErrors back into a
845
+ * `CdpResult` envelope with an `error` payload so the manager does
846
+ * not need to know about our thrown-error convention, then the
847
+ * envelope is unwrapped again on the way out of the managed client.
848
+ *
849
+ * The per-command `command.sessionId` (populated by the manager from
850
+ * a session's opaque `targetId`) is intentionally not forwarded to
851
+ * the underlying CdpClient today -- both LocalCdpClient and
852
+ * ExtensionCdpClient take their CDP sessionId at construction time
853
+ * and tools run one client per invocation. The seam is preserved so
854
+ * a future multi-target backend can read it off the CdpCommand.
855
+ */
856
+ async function dispatchThroughClient(
857
+ client: CdpClient,
858
+ command: CdpCommand,
859
+ signal: AbortSignal | undefined,
860
+ ): Promise<CdpResult> {
861
+ try {
862
+ const result = await client.send(command.method, command.params, signal);
863
+ return { result };
864
+ } catch (err) {
865
+ if (err instanceof CdpError) {
866
+ // Preserve the original CdpError so extractCdpError can
867
+ // re-throw it verbatim. CdpResult's error channel is opaque
868
+ // to the manager, so stashing the instance under `data` is safe.
869
+ return {
870
+ error: {
871
+ code: -1,
872
+ message: err.message,
873
+ data: err,
874
+ },
875
+ };
876
+ }
877
+ throw err;
878
+ }
879
+ }
880
+
881
+ /**
882
+ * Unwrap a CdpResult envelope into the raw CDP result `T` or throw
883
+ * the underlying CdpError. If the envelope carries an error but the
884
+ * `data` is not a CdpError (e.g. a future backend surfaces a JSON-RPC
885
+ * error envelope directly), synthesize a transport_error CdpError so
886
+ * call sites keep their uniform error handling.
887
+ */
888
+ function unwrapResult<T>(
889
+ envelope: CdpResult,
890
+ method: string,
891
+ params?: Record<string, unknown>,
892
+ ): T {
893
+ if (envelope.error) {
894
+ throw extractCdpError(envelope, method, params);
895
+ }
896
+ return envelope.result as T;
897
+ }
898
+
899
+ /**
900
+ * Attempt to extract a discovery-level error code from an underlying
901
+ * error. Some CdpInspectClient errors embed a discovery code (e.g.
902
+ * "ECONNREFUSED", "DISCOVERY_TIMEOUT") that is useful for diagnostics.
903
+ */
904
+ function extractDiscoveryCode(underlying: unknown): string | undefined {
905
+ if (underlying == null) return undefined;
906
+ if (typeof underlying === "object" && "code" in underlying) {
907
+ const code = (underlying as Record<string, unknown>).code;
908
+ if (typeof code === "string") return code;
909
+ }
910
+ if (underlying instanceof Error && "cause" in underlying) {
911
+ return extractDiscoveryCode(underlying.cause);
912
+ }
913
+ return undefined;
914
+ }