@vellumai/assistant 0.7.1 → 0.7.2

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 (535) hide show
  1. package/ARCHITECTURE.md +32 -49
  2. package/Dockerfile +1 -0
  3. package/README.md +1 -2
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +9 -3
  5. package/bun.lock +26 -26
  6. package/docs/architecture/security.md +20 -0
  7. package/docs/plugins.md +7 -9
  8. package/knip.json +1 -0
  9. package/node_modules/@vellumai/gateway-client/src/index.ts +1 -0
  10. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +39 -1
  11. package/node_modules/@vellumai/gateway-client/src/types.ts +11 -0
  12. package/node_modules/@vellumai/service-contracts/package.json +2 -0
  13. package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +4 -0
  14. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +107 -0
  15. package/node_modules/@vellumai/service-contracts/src/index.ts +5 -1
  16. package/node_modules/@vellumai/service-contracts/src/ingress.ts +24 -0
  17. package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +84 -0
  18. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +9 -0
  19. package/node_modules/@vellumai/twilio-client/bun.lock +24 -0
  20. package/node_modules/@vellumai/twilio-client/package.json +18 -0
  21. package/node_modules/@vellumai/twilio-client/src/__tests__/twilio-client.test.ts +128 -0
  22. package/node_modules/@vellumai/twilio-client/src/index.ts +179 -0
  23. package/node_modules/@vellumai/twilio-client/tsconfig.json +20 -0
  24. package/openapi.yaml +565 -12
  25. package/package.json +6 -3
  26. package/src/__tests__/app-builder-tool-scripts.test.ts +3 -3
  27. package/src/__tests__/app-bundler.test.ts +170 -1
  28. package/src/__tests__/app-control-flow.test.ts +374 -0
  29. package/src/__tests__/app-control-no-global-cgevent.test.ts +98 -0
  30. package/src/__tests__/app-control-tool-schemas.test.ts +621 -0
  31. package/src/__tests__/app-executors.test.ts +30 -43
  32. package/src/__tests__/approval-routes-http.test.ts +23 -6
  33. package/src/__tests__/assistant-event-hub-machine-name.test.ts +146 -0
  34. package/src/__tests__/assistant-event-hub-targeted.test.ts +257 -0
  35. package/src/__tests__/assistant-event-hub.test.ts +109 -2
  36. package/src/__tests__/assistant-event.test.ts +10 -0
  37. package/src/__tests__/assistant-events-sse-hardening.test.ts +7 -2
  38. package/src/__tests__/assistant-feature-flags-integration.test.ts +11 -7
  39. package/src/__tests__/background-shell-host-bash.test.ts +14 -15
  40. package/src/__tests__/bootstrap-turn-cleanup.test.ts +44 -0
  41. package/src/__tests__/btw-routes.test.ts +13 -4
  42. package/src/__tests__/call-controller.test.ts +49 -1
  43. package/src/__tests__/call-domain.test.ts +0 -2
  44. package/src/__tests__/call-routes-http.test.ts +0 -2
  45. package/src/__tests__/channel-readiness-service.test.ts +59 -1
  46. package/src/__tests__/checker.test.ts +3 -4
  47. package/src/__tests__/config-loader-backfill.test.ts +90 -155
  48. package/src/__tests__/config-loader-platform-defaults.test.ts +196 -0
  49. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  50. package/src/__tests__/config-set-platform-guard.test.ts +48 -4
  51. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +2 -2
  52. package/src/__tests__/config-watcher.test.ts +2 -2
  53. package/src/__tests__/conversation-app-control-instantiation.test.ts +392 -0
  54. package/src/__tests__/conversation-app-control-lifecycle.test.ts +237 -0
  55. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  56. package/src/__tests__/conversation-lifecycle.test.ts +36 -0
  57. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +283 -0
  58. package/src/__tests__/conversation-routes-disk-view.test.ts +6 -0
  59. package/src/__tests__/conversation-routes-guardian-reply.test.ts +120 -72
  60. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  61. package/src/__tests__/conversation-slash-commands.test.ts +0 -4
  62. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +202 -0
  63. package/src/__tests__/conversation-surfaces-app-control.test.ts +317 -0
  64. package/src/__tests__/credential-execution-feature-gates.test.ts +5 -12
  65. package/src/__tests__/credential-execution-managed-contract.test.ts +3 -131
  66. package/src/__tests__/credentials-cli.test.ts +5 -12
  67. package/src/__tests__/cu-unified-flow.test.ts +185 -23
  68. package/src/__tests__/daemon-credential-client.test.ts +101 -19
  69. package/src/__tests__/db-schedule-syntax-migration.test.ts +2 -0
  70. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  71. package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
  72. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -2
  73. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -2
  74. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -1
  75. package/src/__tests__/heartbeat-service.test.ts +718 -1
  76. package/src/__tests__/helpers/call-route-handler.ts +7 -1
  77. package/src/__tests__/host-app-control-proxy.test.ts +602 -0
  78. package/src/__tests__/host-app-control-routes.test.ts +263 -0
  79. package/src/__tests__/host-bash-proxy.test.ts +246 -47
  80. package/src/__tests__/host-bash-routes.test.ts +294 -0
  81. package/src/__tests__/host-browser-proxy.test.ts +24 -22
  82. package/src/__tests__/host-browser-routes.test.ts +39 -13
  83. package/src/__tests__/host-cu-proxy.test.ts +41 -52
  84. package/src/__tests__/host-cu-routes-targeted.test.ts +300 -0
  85. package/src/__tests__/host-file-edit-tool.test.ts +47 -1
  86. package/src/__tests__/host-file-proxy-targeted.test.ts +339 -0
  87. package/src/__tests__/host-file-proxy.test.ts +37 -43
  88. package/src/__tests__/host-file-read-tool.test.ts +17 -0
  89. package/src/__tests__/host-file-routes-targeted.test.ts +262 -0
  90. package/src/__tests__/host-file-write-tool.test.ts +42 -1
  91. package/src/__tests__/host-proxy-base.test.ts +312 -0
  92. package/src/__tests__/host-shell-tool.test.ts +22 -4
  93. package/src/__tests__/host-transfer-proxy-targeted.test.ts +583 -0
  94. package/src/__tests__/host-transfer-proxy.test.ts +121 -22
  95. package/src/__tests__/host-transfer-routes-targeted.test.ts +447 -0
  96. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  97. package/src/__tests__/identity-intro-cache.test.ts +29 -0
  98. package/src/__tests__/identity-routes.test.ts +103 -1
  99. package/src/__tests__/init-feature-flag-overrides.test.ts +26 -3
  100. package/src/__tests__/inline-command-runner.test.ts +0 -1
  101. package/src/__tests__/inline-skill-load-permissions.test.ts +5 -11
  102. package/src/__tests__/integration-status.test.ts +85 -5
  103. package/src/__tests__/intent-routing.test.ts +0 -1
  104. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +95 -5
  105. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +17 -0
  106. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
  107. package/src/__tests__/mcp-auth-routes.test.ts +197 -0
  108. package/src/__tests__/mcp-cli.test.ts +338 -2
  109. package/src/__tests__/memory-jobs-worker-lanes.test.ts +188 -0
  110. package/src/__tests__/migration-import-commit-http.test.ts +108 -2
  111. package/src/__tests__/mock-gateway-ipc.ts +1 -0
  112. package/src/__tests__/oauth-cli.test.ts +0 -2
  113. package/src/__tests__/oauth2-gateway-transport.test.ts +0 -1
  114. package/src/__tests__/persistence-secret-redaction.test.ts +299 -0
  115. package/src/__tests__/platform-bash-auto-approve.test.ts +5 -9
  116. package/src/__tests__/prechat-onboarding-contract.test.ts +3 -1
  117. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  118. package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
  119. package/src/__tests__/public-ingress-urls.test.ts +97 -0
  120. package/src/__tests__/require-fresh-approval.test.ts +0 -1
  121. package/src/__tests__/retry-backoff.test.ts +87 -0
  122. package/src/__tests__/runtime-events-sse.test.ts +10 -6
  123. package/src/__tests__/sanitize-config-for-transfer.test.ts +24 -2
  124. package/src/__tests__/schedule-retry.test.ts +715 -0
  125. package/src/__tests__/script-proxy-mitm-handler.test.ts +1 -1
  126. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  127. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  128. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
  129. package/src/__tests__/skill-feature-flags.test.ts +43 -41
  130. package/src/__tests__/skill-load-feature-flag.test.ts +13 -14
  131. package/src/__tests__/skill-load-inline-command.test.ts +0 -51
  132. package/src/__tests__/skill-load-inline-includes.test.ts +0 -43
  133. package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
  134. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
  135. package/src/__tests__/slack-channel-config.test.ts +9 -14
  136. package/src/__tests__/system-prompt-ask-mode.test.ts +0 -1
  137. package/src/__tests__/system-prompt.test.ts +0 -1
  138. package/src/__tests__/telegram-config.test.ts +0 -1
  139. package/src/__tests__/test-preload.ts +8 -0
  140. package/src/__tests__/tool-approval-handler.test.ts +3 -4
  141. package/src/__tests__/tool-audit-listener.test.ts +48 -0
  142. package/src/__tests__/tool-execute-pipeline.test.ts +0 -1
  143. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
  144. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  145. package/src/__tests__/tool-executor.test.ts +0 -1
  146. package/src/__tests__/twilio-config.test.ts +3 -16
  147. package/src/__tests__/twilio-routes.test.ts +3 -5
  148. package/src/__tests__/twilio-validation.test.ts +93 -0
  149. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -4
  150. package/src/__tests__/verification-control-plane-policy.test.ts +2 -4
  151. package/src/__tests__/voice-ingress-preflight.test.ts +19 -0
  152. package/src/__tests__/workspace-migration-006-services-config.test.ts +3 -2
  153. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +1 -5
  154. package/src/__tests__/workspace-migration-down-functions.test.ts +8 -8
  155. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +10 -6
  156. package/src/backup/__tests__/paths.test.ts +0 -22
  157. package/src/backup/__tests__/restore.test.ts +51 -151
  158. package/src/backup/paths.ts +2 -18
  159. package/src/backup/restore.ts +107 -231
  160. package/src/bundler/app-bundler.ts +51 -3
  161. package/src/calls/relay-server.ts +4 -44
  162. package/src/calls/twilio-config.ts +2 -17
  163. package/src/calls/twilio-rest.ts +33 -105
  164. package/src/calls/twilio-routes.ts +11 -12
  165. package/src/channels/types.ts +8 -7
  166. package/src/cli/commands/__tests__/backup.test.ts +6 -277
  167. package/src/cli/commands/__tests__/gateway.test.ts +288 -0
  168. package/src/cli/commands/__tests__/memory-v2.test.ts +4 -0
  169. package/src/cli/commands/__tests__/webhooks.test.ts +0 -1
  170. package/src/cli/commands/backup.ts +6 -331
  171. package/src/cli/commands/clients.ts +36 -37
  172. package/src/cli/commands/contacts.ts +73 -0
  173. package/src/cli/commands/conversations.ts +2 -5
  174. package/src/cli/commands/credentials.ts +15 -7
  175. package/src/cli/commands/domain.ts +66 -15
  176. package/src/cli/commands/gateway.ts +183 -0
  177. package/src/cli/commands/keys.ts +9 -6
  178. package/src/cli/commands/mcp.ts +116 -156
  179. package/src/cli/commands/memory-v2.ts +296 -1
  180. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
  181. package/src/cli/commands/platform/__tests__/connect.test.ts +0 -2
  182. package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -2
  183. package/src/cli/commands/platform/__tests__/status.test.ts +13 -15
  184. package/src/cli/commands/platform/disconnect.ts +5 -4
  185. package/src/cli/commands/platform/index.ts +0 -18
  186. package/src/cli/lib/daemon-credential-client.ts +110 -28
  187. package/src/cli/program.ts +2 -0
  188. package/src/config/assistant-feature-flags.ts +67 -10
  189. package/src/config/bundled-skills/acp/SKILL.md +6 -0
  190. package/src/config/bundled-skills/acp/TOOLS.json +1 -22
  191. package/src/config/bundled-skills/app-builder/SKILL.md +14 -109
  192. package/src/config/bundled-skills/app-builder/TOOLS.json +1 -28
  193. package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -10
  194. package/src/config/bundled-skills/app-control/SKILL.md +75 -0
  195. package/src/config/bundled-skills/app-control/TOOLS.json +299 -0
  196. package/src/config/bundled-skills/app-control/tools/app-control-click.ts +12 -0
  197. package/src/config/bundled-skills/app-control/tools/app-control-combo.ts +12 -0
  198. package/src/config/bundled-skills/app-control/tools/app-control-drag.ts +12 -0
  199. package/src/config/bundled-skills/app-control/tools/app-control-observe.ts +12 -0
  200. package/src/config/bundled-skills/app-control/tools/app-control-press.ts +12 -0
  201. package/src/config/bundled-skills/app-control/tools/app-control-sequence.ts +12 -0
  202. package/src/config/bundled-skills/app-control/tools/app-control-start.ts +12 -0
  203. package/src/config/bundled-skills/app-control/tools/app-control-stop.ts +12 -0
  204. package/src/config/bundled-skills/app-control/tools/app-control-type.ts +12 -0
  205. package/src/config/bundled-skills/computer-use/SKILL.md +6 -0
  206. package/src/config/bundled-skills/computer-use/TOOLS.json +67 -43
  207. package/src/config/bundled-skills/contacts/TOOLS.json +0 -16
  208. package/src/config/bundled-skills/document/TOOLS.json +0 -8
  209. package/src/config/bundled-skills/followups/TOOLS.json +0 -12
  210. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  211. package/src/config/bundled-skills/image-studio/TOOLS.json +0 -4
  212. package/src/config/bundled-skills/media-processing/TOOLS.json +0 -24
  213. package/src/config/bundled-skills/messaging/TOOLS.json +0 -40
  214. package/src/config/bundled-skills/phone-calls/TOOLS.json +0 -12
  215. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +19 -4
  216. package/src/config/bundled-skills/playbooks/TOOLS.json +0 -16
  217. package/src/config/bundled-skills/schedule/TOOLS.json +14 -14
  218. package/src/config/bundled-skills/sequences/TOOLS.json +0 -36
  219. package/src/config/bundled-skills/settings/SKILL.md +4 -0
  220. package/src/config/bundled-skills/settings/TOOLS.json +0 -12
  221. package/src/config/bundled-skills/skill-management/SKILL.md +6 -0
  222. package/src/config/bundled-skills/skill-management/TOOLS.json +0 -8
  223. package/src/config/bundled-skills/subagent/SKILL.md +6 -2
  224. package/src/config/bundled-skills/subagent/TOOLS.json +0 -20
  225. package/src/config/bundled-skills/transcribe/SKILL.md +4 -0
  226. package/src/config/bundled-skills/transcribe/TOOLS.json +0 -4
  227. package/src/config/bundled-tool-registry.ts +21 -0
  228. package/src/config/env-registry.ts +0 -2
  229. package/src/config/env.ts +19 -12
  230. package/src/config/feature-flag-registry.json +21 -133
  231. package/src/config/loader.ts +73 -99
  232. package/src/config/sanitize-for-transfer.ts +2 -0
  233. package/src/config/schemas/__tests__/memory-lifecycle.test.ts +80 -0
  234. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -4
  235. package/src/config/schemas/calls.ts +0 -9
  236. package/src/config/schemas/heartbeat.ts +63 -0
  237. package/src/config/schemas/ingress.ts +10 -6
  238. package/src/config/schemas/llm.ts +5 -10
  239. package/src/config/schemas/memory-lifecycle.ts +77 -24
  240. package/src/config/schemas/memory-v2.ts +48 -4
  241. package/src/config/schemas/platform.ts +6 -0
  242. package/src/config/schemas/services.ts +1 -15
  243. package/src/config/schemas/skills.ts +0 -6
  244. package/src/config/seed-inference-profiles.ts +1 -1
  245. package/src/contacts/contact-store.ts +0 -30
  246. package/src/contacts/contacts-write.ts +0 -27
  247. package/src/context/window-manager.ts +1 -2
  248. package/src/credential-execution/feature-gates.ts +10 -10
  249. package/src/credential-execution/process-manager.ts +12 -41
  250. package/src/daemon/__tests__/conversation-tool-setup.test.ts +126 -5
  251. package/src/daemon/bootstrap-turn-cleanup.ts +45 -0
  252. package/src/daemon/config-watcher.ts +4 -3
  253. package/src/daemon/conversation-agent-loop-handlers.ts +21 -3
  254. package/src/daemon/conversation-agent-loop.ts +32 -28
  255. package/src/daemon/conversation-lifecycle.ts +8 -1
  256. package/src/daemon/conversation-process.ts +16 -11
  257. package/src/daemon/conversation-runtime-assembly.ts +2 -2
  258. package/src/daemon/conversation-surfaces.ts +125 -4
  259. package/src/daemon/conversation-tool-setup.ts +16 -55
  260. package/src/daemon/conversation.ts +21 -2
  261. package/src/daemon/doordash-steps.ts +1 -1
  262. package/src/daemon/handlers/shared.ts +4 -1
  263. package/src/daemon/host-app-control-proxy.ts +293 -0
  264. package/src/daemon/host-bash-proxy.ts +84 -74
  265. package/src/daemon/host-browser-proxy.ts +67 -82
  266. package/src/daemon/host-cu-proxy.ts +81 -86
  267. package/src/daemon/host-file-proxy.ts +93 -69
  268. package/src/daemon/host-proxy-base.ts +294 -0
  269. package/src/daemon/host-proxy-preactivation.ts +82 -0
  270. package/src/daemon/host-transfer-proxy.ts +247 -129
  271. package/src/daemon/lifecycle.ts +115 -117
  272. package/src/daemon/message-protocol.ts +3 -8
  273. package/src/daemon/message-types/contacts.ts +23 -1
  274. package/src/daemon/message-types/conversations.ts +11 -8
  275. package/src/daemon/message-types/host-app-control.ts +150 -0
  276. package/src/daemon/message-types/host-bash.ts +4 -0
  277. package/src/daemon/message-types/host-cu.ts +2 -0
  278. package/src/daemon/message-types/host-file.ts +4 -0
  279. package/src/daemon/message-types/host-transfer.ts +3 -0
  280. package/src/daemon/message-types/schedules.ts +8 -3
  281. package/src/daemon/message-types/skills.ts +2 -2
  282. package/src/daemon/process-message.ts +18 -1
  283. package/src/daemon/shutdown-handlers.ts +0 -3
  284. package/src/daemon/tool-setup-types.ts +51 -0
  285. package/src/daemon/tool-side-effects.ts +1 -1
  286. package/src/events/tool-audit-listener.ts +2 -1
  287. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +15 -7
  288. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +216 -0
  289. package/src/heartbeat/heartbeat-run-store.ts +236 -0
  290. package/src/heartbeat/heartbeat-service.ts +280 -49
  291. package/src/home/__tests__/post-connect-feed.test.ts +99 -0
  292. package/src/home/__tests__/relationship-state-writer.test.ts +11 -9
  293. package/src/home/__tests__/suggested-prompts.test.ts +89 -0
  294. package/src/home/post-connect-feed.ts +68 -0
  295. package/src/home/relationship-state-writer.ts +17 -92
  296. package/src/home/suggested-prompts.ts +46 -10
  297. package/src/inbound/public-ingress-urls.ts +32 -34
  298. package/src/ipc/__tests__/route-error-envelope.test.ts +80 -0
  299. package/src/ipc/assistant-server.ts +14 -1
  300. package/src/ipc/cli-client.ts +32 -1
  301. package/src/live-voice/live-voice-metrics.ts +10 -10
  302. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +304 -0
  303. package/src/mcp/mcp-auth-orchestrator.ts +213 -0
  304. package/src/mcp/mcp-auth-state.ts +133 -0
  305. package/src/mcp/mcp-oauth-provider.ts +19 -0
  306. package/src/memory/__tests__/jobs-store-job-classes.test.ts +24 -0
  307. package/src/memory/__tests__/qdrant-client-sentinel.test.ts +49 -0
  308. package/src/memory/__tests__/sparse-tokenize.test.ts +66 -0
  309. package/src/memory/anisotropy.test.ts +247 -0
  310. package/src/memory/anisotropy.ts +443 -0
  311. package/src/memory/auto-analysis-constants.ts +17 -0
  312. package/src/memory/auto-analysis-guard.ts +5 -15
  313. package/src/memory/canonical-guardian-store.ts +7 -7
  314. package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +122 -0
  315. package/src/memory/context-search/agent-protocol.ts +6 -6
  316. package/src/memory/context-search/agent-runner.ts +32 -7
  317. package/src/memory/context-search/sources/memory-v2.ts +17 -5
  318. package/src/memory/conversation-crud.ts +1 -1
  319. package/src/memory/conversation-key-store.ts +2 -15
  320. package/src/memory/db-init.ts +4 -0
  321. package/src/memory/embedding-backend.ts +9 -21
  322. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +49 -4
  323. package/src/memory/graph/conversation-graph-memory.ts +1 -24
  324. package/src/memory/graph/graph-search.ts +8 -0
  325. package/src/memory/graph/retriever.ts +28 -0
  326. package/src/memory/graph/tools.ts +1 -1
  327. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +8 -2
  328. package/src/memory/jobs/embed-concept-page.ts +28 -2
  329. package/src/memory/jobs/embed-pkb-file.test.ts +2 -2
  330. package/src/memory/jobs-store.ts +66 -22
  331. package/src/memory/jobs-worker.ts +112 -63
  332. package/src/memory/memory-v2-activation-log-store.ts +1 -1
  333. package/src/memory/migrations/237-heartbeat-runs.ts +45 -0
  334. package/src/memory/migrations/238-schedule-retry-policy.ts +20 -0
  335. package/src/memory/migrations/index.ts +5 -0
  336. package/src/memory/migrations/registry.ts +8 -0
  337. package/src/memory/pkb/pkb-search.ts +7 -0
  338. package/src/memory/qdrant-client.ts +50 -20
  339. package/src/memory/schema/infrastructure.ts +15 -0
  340. package/src/memory/search/semantic.ts +7 -0
  341. package/src/memory/sparse-tokenize.ts +49 -0
  342. package/src/memory/v2/__tests__/activation.test.ts +77 -95
  343. package/src/memory/v2/__tests__/injection.test.ts +43 -21
  344. package/src/memory/v2/__tests__/sim.test.ts +166 -6
  345. package/src/memory/v2/__tests__/sparse-bm25.test.ts +292 -0
  346. package/src/memory/v2/__tests__/static-context.test.ts +0 -1
  347. package/src/memory/v2/activation.ts +69 -88
  348. package/src/memory/v2/consolidation-job.ts +3 -5
  349. package/src/memory/v2/constants.ts +7 -0
  350. package/src/memory/v2/injection.ts +86 -53
  351. package/src/memory/v2/prompts/consolidation.ts +312 -91
  352. package/src/memory/v2/qdrant.ts +99 -1
  353. package/src/memory/v2/sim.ts +126 -16
  354. package/src/memory/v2/skill-qdrant.ts +12 -3
  355. package/src/memory/v2/skill-store.ts +16 -1
  356. package/src/memory/v2/sparse-bm25.ts +245 -0
  357. package/src/memory/v2/static-context.ts +6 -5
  358. package/src/messaging/providers/gmail/types.ts +0 -49
  359. package/src/messaging/providers/slack/adapter.ts +1 -31
  360. package/src/messaging/providers/slack/types.ts +0 -32
  361. package/src/notifications/README.md +10 -10
  362. package/src/notifications/broadcaster.ts +1 -1
  363. package/src/notifications/guardian-question-mode.ts +5 -5
  364. package/src/oauth/connect-orchestrator.ts +4 -0
  365. package/src/oauth/credential-token-resolver.ts +1 -3
  366. package/src/oauth/manual-token-connection.ts +0 -4
  367. package/src/outbound-proxy/index.ts +1 -37
  368. package/src/outbound-proxy/logging.ts +1 -1
  369. package/src/outbound-proxy/policy.ts +6 -5
  370. package/src/outbound-proxy/router.ts +2 -1
  371. package/src/permissions/approval-policy.test.ts +6 -275
  372. package/src/permissions/approval-policy.ts +0 -51
  373. package/src/permissions/checker.test.ts +0 -1
  374. package/src/permissions/checker.ts +3 -17
  375. package/src/permissions/gateway-threshold-reader.ts +2 -0
  376. package/src/permissions/prompter.ts +34 -1
  377. package/src/permissions/secret-prompter.ts +6 -2
  378. package/src/prompts/bootstrap-cleanup.ts +27 -0
  379. package/src/prompts/system-prompt.ts +3 -18
  380. package/src/prompts/templates/SOUL.md +13 -1
  381. package/src/providers/speech-to-text/provider-catalog.ts +7 -8
  382. package/src/runtime/assistant-event-hub.ts +118 -96
  383. package/src/runtime/assistant-event.ts +1 -0
  384. package/src/runtime/auth/__tests__/middleware.test.ts +11 -56
  385. package/src/runtime/auth/middleware.ts +0 -96
  386. package/src/runtime/auth/route-policy.ts +19 -0
  387. package/src/runtime/btw-sidechain.ts +2 -3
  388. package/src/runtime/channel-invite-transport.ts +2 -48
  389. package/src/runtime/channel-invite-transports/email.ts +1 -1
  390. package/src/runtime/channel-invite-transports/slack.ts +1 -1
  391. package/src/runtime/channel-invite-transports/telegram.ts +1 -1
  392. package/src/runtime/channel-invite-transports/voice.ts +1 -1
  393. package/src/runtime/channel-invite-transports/whatsapp.ts +1 -1
  394. package/src/runtime/channel-invite-types.ts +54 -0
  395. package/src/runtime/channel-readiness-service.ts +32 -13
  396. package/src/runtime/http-server.ts +3 -329
  397. package/src/runtime/http-types.ts +0 -5
  398. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +413 -0
  399. package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +260 -0
  400. package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +189 -0
  401. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +153 -1
  402. package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +451 -0
  403. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  404. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +515 -0
  405. package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +437 -0
  406. package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +319 -0
  407. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +51 -1
  408. package/src/runtime/migrations/migration-transport.ts +7 -7
  409. package/src/runtime/migrations/vbundle-builder.ts +327 -60
  410. package/src/runtime/migrations/vbundle-import-analyzer.ts +4 -4
  411. package/src/runtime/migrations/vbundle-import-policy.ts +172 -0
  412. package/src/runtime/migrations/vbundle-importer.ts +245 -68
  413. package/src/runtime/migrations/vbundle-streaming-importer.ts +326 -35
  414. package/src/runtime/migrations/vbundle-streaming-validator.ts +157 -4
  415. package/src/runtime/migrations/vbundle-tar-stream.ts +15 -6
  416. package/src/runtime/migrations/vbundle-validator.ts +114 -0
  417. package/src/runtime/pending-interactions.ts +35 -9
  418. package/src/runtime/routes/__tests__/backup-routes.test.ts +22 -150
  419. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
  420. package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +242 -0
  421. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +112 -0
  422. package/src/runtime/routes/approval-interception-types.ts +13 -0
  423. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +1 -1
  424. package/src/runtime/routes/backup-routes.ts +15 -38
  425. package/src/runtime/routes/btw-routes.ts +14 -37
  426. package/src/runtime/routes/client-routes.ts +1 -0
  427. package/src/runtime/routes/contact-prompt-routes.ts +183 -0
  428. package/src/runtime/routes/conversation-query-routes.ts +36 -1
  429. package/src/runtime/routes/conversation-routes.ts +30 -13
  430. package/src/runtime/routes/document-pdf-renderer.ts +165 -0
  431. package/src/runtime/routes/documents-routes.ts +30 -0
  432. package/src/runtime/routes/errors.ts +19 -4
  433. package/src/runtime/routes/events-routes.ts +12 -6
  434. package/src/runtime/routes/gateway-log-routes.ts +79 -0
  435. package/src/runtime/routes/guardian-approval-interception.ts +2 -8
  436. package/src/runtime/routes/heartbeat-routes.ts +103 -38
  437. package/src/runtime/routes/host-app-control-routes.ts +134 -0
  438. package/src/runtime/routes/host-bash-routes.ts +36 -6
  439. package/src/runtime/routes/host-browser-routes.ts +108 -13
  440. package/src/runtime/routes/host-cu-routes.ts +44 -14
  441. package/src/runtime/routes/host-file-routes.ts +33 -10
  442. package/src/runtime/routes/host-transfer-routes.ts +64 -24
  443. package/src/runtime/routes/http-adapter.ts +1 -0
  444. package/src/runtime/routes/identity-intro-cache.ts +30 -0
  445. package/src/runtime/routes/identity-routes.ts +15 -43
  446. package/src/runtime/routes/inbound-message-handler.ts +1 -9
  447. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -7
  448. package/src/runtime/routes/inbound-stages/edit-intercept.ts +0 -8
  449. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -20
  450. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +5 -13
  451. package/src/runtime/routes/index.ts +8 -0
  452. package/src/runtime/routes/mcp-auth-routes.ts +132 -0
  453. package/src/runtime/routes/memory-item-routes.ts +10 -12
  454. package/src/runtime/routes/memory-v2-routes.ts +441 -1
  455. package/src/runtime/routes/migration-routes.ts +96 -0
  456. package/src/runtime/routes/schedule-routes.ts +7 -0
  457. package/src/runtime/verification-templates.ts +4 -7
  458. package/src/schedule/integration-status.ts +66 -2
  459. package/src/schedule/recurrence-engine.ts +4 -1
  460. package/src/schedule/retry-backoff.ts +18 -0
  461. package/src/schedule/retry-policy.ts +82 -0
  462. package/src/schedule/schedule-recovery.ts +64 -0
  463. package/src/schedule/schedule-store.ts +106 -2
  464. package/src/schedule/scheduler-types.ts +25 -0
  465. package/src/schedule/scheduler.ts +63 -38
  466. package/src/security/oauth-callback-registry.ts +8 -0
  467. package/src/sequence/analytics.ts +5 -5
  468. package/src/sequence/engine.ts +1 -1
  469. package/src/skills/catalog-files.ts +2 -8
  470. package/src/skills/include-graph.ts +5 -5
  471. package/src/skills/remote-skill-policy.ts +5 -5
  472. package/src/skills/skill-file-provider.ts +1 -1
  473. package/src/skills/skill-file-types.ts +13 -0
  474. package/src/skills/skillssh-audit-types.ts +28 -0
  475. package/src/skills/skillssh-registry.ts +8 -21
  476. package/src/telemetry/types.ts +2 -0
  477. package/src/telemetry/usage-telemetry-reporter.test.ts +21 -0
  478. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  479. package/src/tools/app-control/skill-proxy-bridge.ts +28 -0
  480. package/src/tools/apps/executors.ts +56 -69
  481. package/src/tools/browser/__tests__/browser-status.test.ts +21 -18
  482. package/src/tools/browser/browser-execution.ts +2 -2
  483. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +55 -4
  484. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +12 -6
  485. package/src/tools/browser/cdp-client/factory.ts +23 -24
  486. package/src/tools/browser/cdp-client/index.ts +1 -14
  487. package/src/tools/computer-use/definitions.ts +42 -20
  488. package/src/tools/executor.ts +2 -0
  489. package/src/tools/host-filesystem/edit.ts +26 -0
  490. package/src/tools/host-filesystem/read.ts +26 -0
  491. package/src/tools/host-filesystem/transfer.ts +31 -1
  492. package/src/tools/host-filesystem/write.ts +26 -0
  493. package/src/tools/host-terminal/host-shell.ts +58 -0
  494. package/src/tools/schedule/create.ts +6 -0
  495. package/src/tools/schedule/list.ts +2 -0
  496. package/src/tools/schedule/update.ts +10 -0
  497. package/src/tools/shared/filesystem/file-ops-service.ts +2 -0
  498. package/src/tools/shared/filesystem/path-policy.ts +25 -1
  499. package/src/tools/skills/load.ts +0 -32
  500. package/src/tools/tool-approval-handler.ts +1 -5
  501. package/src/tools/types.ts +4 -0
  502. package/src/usage/pricing.ts +1 -1
  503. package/src/workspace/hatched-date.ts +86 -0
  504. package/src/workspace/migrations/003-seed-device-id.ts +1 -1
  505. package/src/workspace/migrations/006-services-config.ts +8 -5
  506. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +3 -9
  507. package/src/workspace/migrations/021-move-signals-to-workspace.ts +4 -10
  508. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +4 -10
  509. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +4 -11
  510. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +3 -10
  511. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +3 -2
  512. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +2 -1
  513. package/src/workspace/migrations/059-move-pid-to-workspace.ts +3 -8
  514. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +3 -8
  515. package/src/workspace/migrations/AGENTS.md +1 -1
  516. package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -10
  517. package/src/workspace/migrations/utils.ts +21 -0
  518. package/src/__tests__/host-browser-e2e-cloud.test.ts +0 -443
  519. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +0 -226
  520. package/src/__tests__/host-browser-ws-events-e2e.test.ts +0 -427
  521. package/src/__tests__/twilio-rest.test.ts +0 -34
  522. package/src/backup/__tests__/backup-key.test.ts +0 -152
  523. package/src/backup/__tests__/backup-worker.test.ts +0 -782
  524. package/src/backup/__tests__/offsite-writer.test.ts +0 -641
  525. package/src/backup/__tests__/stream-crypt.test.ts +0 -228
  526. package/src/backup/backup-key.ts +0 -137
  527. package/src/backup/backup-worker.ts +0 -472
  528. package/src/backup/offsite-writer.ts +0 -222
  529. package/src/backup/stream-crypt.ts +0 -263
  530. package/src/daemon/message-types/pairing.ts +0 -58
  531. package/src/outbound-proxy/config.ts +0 -20
  532. package/src/outbound-proxy/health.ts +0 -18
  533. package/src/outbound-proxy/types.ts +0 -150
  534. package/src/runtime/capability-tokens.ts +0 -190
  535. package/src/signals/mcp-reload.ts +0 -18
@@ -42,7 +42,6 @@ export interface AppStoreWriter {
42
42
  icon?: string;
43
43
  schemaJson: string;
44
44
  htmlDefinition: string;
45
- pages?: Record<string, string>;
46
45
  formatVersion?: number;
47
46
  }): AppDefinition;
48
47
  updateApp(
@@ -77,12 +76,16 @@ export interface AppCreateInput {
77
76
  name: string;
78
77
  description?: string;
79
78
  schema_json?: string;
80
- html?: string;
81
- pages?: Record<string, string>;
79
+ /**
80
+ * Retired single-file shortcut. Kept in the type so legacy or stale callers
81
+ * get a clear tool error instead of silently creating a v2 app with stale
82
+ * scaffold content.
83
+ */
84
+ html?: unknown;
85
+ /** Retired single-file multi-page shortcut. */
86
+ pages?: unknown;
82
87
  auto_open?: boolean;
83
88
  preview?: Record<string, unknown>;
84
- /** When provided, controls multifile scaffold behavior. */
85
- featureFlags?: { multifileEnabled: boolean };
86
89
  }
87
90
 
88
91
  export async function executeAppCreate(
@@ -93,22 +96,26 @@ export async function executeAppCreate(
93
96
  const name = input.name;
94
97
  const description = input.description;
95
98
  const schemaJson = input.schema_json ?? "{}";
96
- // Default to a minimal scaffold only when html is truly omitted; reject
97
- // invalid types (e.g. object/number) so malformed tool calls surface errors.
98
- let htmlDefinition: string;
99
- if (typeof input.html === "string") {
100
- htmlDefinition = input.html;
101
- } else if (input.html == null) {
102
- htmlDefinition = "<!DOCTYPE html><html><head></head><body></body></html>";
103
- } else {
99
+
100
+ if (Object.prototype.hasOwnProperty.call(input, "html")) {
104
101
  return {
105
102
  content: JSON.stringify({
106
- error: `html must be a string, got ${typeof input.html}`,
103
+ error:
104
+ "app_create no longer accepts html. Create the app scaffold, write src/index.html and src/main.tsx with file_write, then call app_refresh.",
105
+ }),
106
+ isError: true,
107
+ };
108
+ }
109
+
110
+ if (Object.prototype.hasOwnProperty.call(input, "pages")) {
111
+ return {
112
+ content: JSON.stringify({
113
+ error:
114
+ "app_create no longer accepts pages. Build multi-file TSX apps under src/ and route inside the Preact app instead.",
107
115
  }),
108
116
  isError: true,
109
117
  };
110
118
  }
111
- const pages = input.pages;
112
119
  const autoOpen = input.auto_open !== false; // default true
113
120
  const preview = input.preview;
114
121
 
@@ -121,49 +128,30 @@ export async function executeAppCreate(
121
128
  isError: true,
122
129
  };
123
130
  }
124
- if (pages) {
125
- for (const [filename, content] of Object.entries(pages)) {
126
- if (typeof content !== "string") {
127
- return {
128
- content: JSON.stringify({
129
- error: `pages["${filename}"] must be a string, got ${typeof content}`,
130
- }),
131
- isError: true,
132
- };
133
- }
134
- }
135
- }
136
131
 
137
132
  // Extract icon from preview if provided - only persist emoji-like values,
138
133
  // not URLs which would render as raw strings in UI and bundle manifests.
139
134
  const rawIcon = preview?.icon as string | undefined;
140
135
  const icon = rawIcon && !rawIcon.startsWith("http") ? rawIcon : undefined;
141
136
 
142
- const multifileEnabled = input.featureFlags?.multifileEnabled === true;
143
-
144
137
  const app = store.createApp({
145
138
  name,
146
139
  description,
147
140
  icon,
148
141
  schemaJson,
149
- htmlDefinition: multifileEnabled ? "" : htmlDefinition,
150
- pages: multifileEnabled ? undefined : pages,
151
- formatVersion: multifileEnabled ? 2 : undefined,
142
+ htmlDefinition: "",
143
+ formatVersion: 2,
152
144
  });
153
145
 
154
146
  // Scaffold multifile app with src/ files and compile to dist/
155
- if (multifileEnabled) {
156
- const htmlSafeName = name
157
- .replace(/&/g, "&amp;")
158
- .replace(/</g, "&lt;")
159
- .replace(/>/g, "&gt;")
160
- .replace(/"/g, "&quot;");
161
- const jsxSafeName = name.replace(/[<>{}&"']/g, "");
162
-
163
- const indexHtml =
164
- typeof input.html === "string"
165
- ? input.html
166
- : `<!DOCTYPE html>
147
+ const htmlSafeName = name
148
+ .replace(/&/g, "&amp;")
149
+ .replace(/</g, "&lt;")
150
+ .replace(/>/g, "&gt;")
151
+ .replace(/"/g, "&quot;");
152
+ const jsxSafeName = name.replace(/[<>{}&"']/g, "");
153
+
154
+ const indexHtml = `<!DOCTYPE html>
167
155
  <html lang="en">
168
156
  <head>
169
157
  <meta charset="UTF-8">
@@ -175,7 +163,7 @@ export async function executeAppCreate(
175
163
  </body>
176
164
  </html>`;
177
165
 
178
- const mainTsx = `import { render } from 'preact';
166
+ const mainTsx = `import { render } from 'preact';
179
167
 
180
168
  function App() {
181
169
  return <div>{"Hello, ${jsxSafeName}!"}</div>;
@@ -184,31 +172,30 @@ function App() {
184
172
  render(<App />, document.getElementById('app')!);
185
173
  `;
186
174
 
187
- // Only write scaffold files when they don't already exist on disk.
188
- // The LLM may have written custom source files via file_write before
189
- // calling app_create, and overwriting them would destroy the real app
190
- // content, leaving only the scaffold placeholder.
191
- if (!store.appFileExists(app.id, "src/index.html")) {
192
- store.writeAppFile(app.id, "src/index.html", indexHtml);
193
- }
194
- if (!store.appFileExists(app.id, "src/main.tsx")) {
195
- store.writeAppFile(app.id, "src/main.tsx", mainTsx);
196
- }
175
+ // Only write scaffold files when they don't already exist on disk.
176
+ // The LLM may have written custom source files via file_write before
177
+ // calling app_create, and overwriting them would destroy the real app
178
+ // content, leaving only the scaffold placeholder.
179
+ if (!store.appFileExists(app.id, "src/index.html")) {
180
+ store.writeAppFile(app.id, "src/index.html", indexHtml);
181
+ }
182
+ if (!store.appFileExists(app.id, "src/main.tsx")) {
183
+ store.writeAppFile(app.id, "src/main.tsx", mainTsx);
184
+ }
197
185
 
198
- // Compile src/ → dist/
199
- const appDir = getAppDirPath(app.id);
200
- const compileResult = await compileApp(appDir);
201
- if (!compileResult.ok) {
202
- return {
203
- content: JSON.stringify({
204
- ...app,
205
- compile_errors: compileResult.errors,
206
- compile_warnings: compileResult.warnings,
207
- compile_duration_ms: compileResult.durationMs,
208
- }),
209
- isError: false,
210
- };
211
- }
186
+ // Compile src/ → dist/
187
+ const appDir = getAppDirPath(app.id);
188
+ const compileResult = await compileApp(appDir);
189
+ if (!compileResult.ok) {
190
+ return {
191
+ content: JSON.stringify({
192
+ ...app,
193
+ compile_errors: compileResult.errors,
194
+ compile_warnings: compileResult.warnings,
195
+ compile_duration_ms: compileResult.durationMs,
196
+ }),
197
+ isError: false,
198
+ };
212
199
  }
213
200
 
214
201
  // Emit the inline preview card via the proxy without opening a workspace panel.
@@ -85,11 +85,13 @@ mock.module("../browser-manager.js", () => ({
85
85
  /** Mutable proxy returned by HostBrowserProxy.instance. */
86
86
  let mockSingletonProxy: {
87
87
  isAvailable: () => boolean;
88
+ hasExtensionClient: () => boolean;
88
89
  request: unknown;
89
90
  } | null = null;
90
91
 
91
92
  const unavailableFallback = {
92
93
  isAvailable: () => false,
94
+ hasExtensionClient: () => false,
93
95
  request: () => Promise.reject(new Error("no extension")),
94
96
  };
95
97
 
@@ -159,7 +161,7 @@ describe("executeBrowserStatus", () => {
159
161
  });
160
162
 
161
163
  test("reports extension as connected when probe fails on restricted chrome:// page", async () => {
162
- mockSingletonProxy = { isAvailable: () => true, request: () => {} };
164
+ mockSingletonProxy = { isAvailable: () => true, hasExtensionClient: () => true, request: () => {} };
163
165
  probeOutcomes[BROWSER_STATUS_MODE.EXTENSION] = "fail";
164
166
  probeErrors[BROWSER_STATUS_MODE.EXTENSION] = new CdpError(
165
167
  "cdp_error",
@@ -181,7 +183,7 @@ describe("executeBrowserStatus", () => {
181
183
  // ── macOS host-browser proxy mode tests ─────────────────────────────
182
184
 
183
185
  test("reports extension as available when singleton proxy is connected", async () => {
184
- mockSingletonProxy = { isAvailable: () => true, request: () => {} };
186
+ mockSingletonProxy = { isAvailable: () => true, hasExtensionClient: () => true, request: () => {} };
185
187
  const result = await executeBrowserStatus(
186
188
  {},
187
189
  makeContext({
@@ -199,22 +201,23 @@ describe("executeBrowserStatus", () => {
199
201
  expect(extension.details.transport).toBe("extension-ws");
200
202
  });
201
203
 
202
- test("reports extension disconnected when singleton proxy is not available", async () => {
203
- // mockSingletonProxy = null → falls back to unavailableFallback
204
- const result = await executeBrowserStatus({}, makeContext());
205
- expect(result.isError).toBe(false);
206
- const payload = JSON.parse(result.content);
207
- const extension = payload.modes.find(
208
- (m: { mode: string }) => m.mode === BROWSER_STATUS_MODE.EXTENSION,
209
- );
210
- expect(extension).toBeDefined();
211
- expect(extension.available).toBe(false);
212
- expect(extension.summary).toContain("disconnected");
213
- expect(extension.details.transport).toBe("extension-ws");
214
- });
204
+ test("reports extension unavailable when no Chrome Extension is connected", async () => {
205
+ // mockSingletonProxy = null → falls back to unavailableFallback (hasExtensionClient = false)
206
+ const result = await executeBrowserStatus({}, makeContext());
207
+ expect(result.isError).toBe(false);
208
+ const payload = JSON.parse(result.content);
209
+ const extension = payload.modes.find(
210
+ (m: { mode: string }) => m.mode === BROWSER_STATUS_MODE.EXTENSION,
211
+ );
212
+ expect(extension).toBeDefined();
213
+ expect(extension.available).toBe(false);
214
+ expect(extension.summary).toContain("no Chrome Extension is connected");
215
+ expect(extension.verified).toBe("preflight");
216
+ expect(extension.details.transport).toBe("extension-ws");
217
+ });
215
218
 
216
219
  test("probe failure diagnostics include remediation actions", async () => {
217
- mockSingletonProxy = { isAvailable: () => true, request: () => {} };
220
+ mockSingletonProxy = { isAvailable: () => true, hasExtensionClient: () => true, request: () => {} };
218
221
  probeOutcomes[BROWSER_STATUS_MODE.EXTENSION] = "fail";
219
222
  probeErrors[BROWSER_STATUS_MODE.EXTENSION] = new CdpError(
220
223
  "transport_error",
@@ -233,7 +236,7 @@ describe("executeBrowserStatus", () => {
233
236
  });
234
237
 
235
238
  test("recommendation order follows auto candidate precedence with available extension", async () => {
236
- mockSingletonProxy = { isAvailable: () => true, request: () => {} };
239
+ mockSingletonProxy = { isAvailable: () => true, hasExtensionClient: () => true, request: () => {} };
237
240
  const result = await executeBrowserStatus({}, makeContext());
238
241
  expect(result.isError).toBe(false);
239
242
  const payload = JSON.parse(result.content);
@@ -252,7 +255,7 @@ describe("executeBrowserStatus", () => {
252
255
  });
253
256
 
254
257
  test("restricted chrome:// page probe includes transport details", async () => {
255
- mockSingletonProxy = { isAvailable: () => true, request: () => {} };
258
+ mockSingletonProxy = { isAvailable: () => true, hasExtensionClient: () => true, request: () => {} };
256
259
  probeOutcomes[BROWSER_STATUS_MODE.EXTENSION] = "fail";
257
260
  probeErrors[BROWSER_STATUS_MODE.EXTENSION] = new CdpError(
258
261
  "cdp_error",
@@ -2251,14 +2251,14 @@ async function checkExtensionModeStatus(
2251
2251
  ): Promise<BrowserStatusModeResult> {
2252
2252
  const proxy = HostBrowserProxy.instance;
2253
2253
 
2254
- if (!proxy.isAvailable()) {
2254
+ if (!proxy.hasExtensionClient()) {
2255
2255
  return {
2256
2256
  mode: BROWSER_STATUS_MODE.EXTENSION,
2257
2257
  available: false,
2258
2258
  verified: "preflight",
2259
2259
  autoCandidate,
2260
2260
  summary:
2261
- "Extension mode is unavailable: the extension transport is currently disconnected.",
2261
+ "Extension mode is unavailable: no Chrome Extension is connected.",
2262
2262
  userActions: extensionSetupActions(),
2263
2263
  tradeoffs: modeTradeoffs(BROWSER_STATUS_MODE.EXTENSION),
2264
2264
  details: { transport: "extension-ws" },
@@ -119,6 +119,7 @@ let mockSingletonProxy: HostBrowserProxy | null = null;
119
119
  /** Default proxy that reports unavailable — used when no test override is set. */
120
120
  const unavailableFallback: HostBrowserProxy = {
121
121
  isAvailable: () => false,
122
+ hasExtensionClient: () => false,
122
123
  request: () => Promise.reject(new Error("no extension")),
123
124
  resolve: () => {},
124
125
  hasPendingRequest: () => false,
@@ -161,23 +162,38 @@ function makeContext(
161
162
  }
162
163
 
163
164
  /**
164
- * Create a fake HostBrowserProxy that reports as available.
165
+ * Create a fake HostBrowserProxy with a Chrome Extension client connected.
166
+ * Both isAvailable() and hasExtensionClient() return true.
165
167
  */
166
168
  function makeAvailableProxy(): HostBrowserProxy {
167
169
  return {
168
170
  request: mock(async () => ({})),
169
171
  isAvailable: () => true,
172
+ hasExtensionClient: () => true,
173
+ } as unknown as HostBrowserProxy;
174
+ }
175
+
176
+ /**
177
+ * Create a fake HostBrowserProxy where only the macOS SSE bridge is connected
178
+ * (no Chrome Extension). isAvailable() is true but hasExtensionClient() is false.
179
+ */
180
+ function makeMacosBridgeOnlyProxy(): HostBrowserProxy {
181
+ return {
182
+ request: mock(async () => ({})),
183
+ isAvailable: () => true,
184
+ hasExtensionClient: () => false,
170
185
  } as unknown as HostBrowserProxy;
171
186
  }
172
187
 
173
188
  /**
174
189
  * Create a fake HostBrowserProxy that reports as unavailable
175
- * (proxy exists but client is disconnected).
190
+ * (proxy exists but no client of any kind is connected).
176
191
  */
177
192
  function makeUnavailableProxy(): HostBrowserProxy {
178
193
  return {
179
194
  request: mock(async () => ({})),
180
195
  isAvailable: () => false,
196
+ hasExtensionClient: () => false,
181
197
  } as unknown as HostBrowserProxy;
182
198
  }
183
199
 
@@ -682,6 +698,22 @@ describe("buildCandidateList", () => {
682
698
  expect(candidates[0].kind).toBe("local");
683
699
  });
684
700
 
701
+ test("excludes extension candidate when only macOS SSE bridge is connected", () => {
702
+ // isAvailable() = true but hasExtensionClient() = false: only macOS bridge.
703
+ // The macOS bridge routes through localhost:9222 on the host, so it must
704
+ // NOT be included under the "extension" candidate kind.
705
+ const fakeProxy = makeMacosBridgeOnlyProxy();
706
+ mockSingletonProxy = fakeProxy;
707
+ const ctx = makeContext({
708
+ conversationId: "candidates-macos-bridge-only",
709
+ });
710
+
711
+ const candidates = buildCandidateList(ctx);
712
+
713
+ expect(candidates.every((c) => c.kind !== "extension")).toBe(true);
714
+ expect(candidates[candidates.length - 1].kind).toBe("local");
715
+ });
716
+
685
717
  test("includes cdp-inspect candidate when enabled in config", () => {
686
718
  cdpInspectEnabled = true;
687
719
  const ctx = makeContext({ conversationId: "candidates-inspect" });
@@ -1435,7 +1467,7 @@ describe("pinned-mode selection", () => {
1435
1467
  const cdpErr = err as CdpError;
1436
1468
  expect(cdpErr.code).toBe("transport_error");
1437
1469
  expect(cdpErr.message).toContain('Pinned mode "extension" unavailable');
1438
- expect(cdpErr.message).toContain("no active extension connection");
1470
+ expect(cdpErr.message).toContain("no Chrome Extension connected");
1439
1471
  expect(cdpErr.attemptDiagnostics).toBeDefined();
1440
1472
  expect(cdpErr.attemptDiagnostics).toHaveLength(1);
1441
1473
  expect(cdpErr.attemptDiagnostics![0].candidateKind).toBe("extension");
@@ -1457,7 +1489,7 @@ describe("pinned-mode selection", () => {
1457
1489
  } catch (err) {
1458
1490
  const cdpErr = err as CdpError;
1459
1491
  expect(cdpErr.code).toBe("transport_error");
1460
- expect(cdpErr.message).toContain("no active extension connection");
1492
+ expect(cdpErr.message).toContain("no Chrome Extension connected");
1461
1493
  expect(cdpErr.attemptDiagnostics![0].stage).toBe("candidate_selection");
1462
1494
  }
1463
1495
  });
@@ -1667,6 +1699,25 @@ describe("buildPinnedCandidateList", () => {
1667
1699
  });
1668
1700
  }
1669
1701
  });
1702
+
1703
+ test("extension mode throws when only macOS SSE bridge is connected", () => {
1704
+ // This is the bug case: isAvailable() = true but hasExtensionClient() = false.
1705
+ // Before the fix, this would build an extension candidate that silently
1706
+ // dispatched to the macOS bridge and failed with a misleading localhost:9222 error.
1707
+ const fakeProxy = makeMacosBridgeOnlyProxy();
1708
+ mockSingletonProxy = fakeProxy;
1709
+ const ctx = makeContext({ conversationId: "bpl-ext-macos-bridge-only" });
1710
+
1711
+ try {
1712
+ buildPinnedCandidateList(ctx, "extension");
1713
+ expect(true).toBe(false); // should not reach
1714
+ } catch (err) {
1715
+ expect(err).toBeInstanceOf(CdpError);
1716
+ const cdpErr = err as CdpError;
1717
+ expect(cdpErr.code).toBe("transport_error");
1718
+ expect(cdpErr.message).toContain("no Chrome Extension connected");
1719
+ }
1720
+ });
1670
1721
  });
1671
1722
 
1672
1723
  // ── Attempt diagnostics & fallback log tests ─────────────────────────────
@@ -511,14 +511,19 @@ describe("connectCdpWsTransport", () => {
511
511
  });
512
512
 
513
513
  test("addEventListener returns an unsubscribe function", async () => {
514
+ // Use a sentinel request to gate event emission on the server: the
515
+ // listener is registered before send() runs, so by the time the server
516
+ // receives the sentinel and starts emitting events the client listener
517
+ // is guaranteed to be attached. A bare setTimeout-after-open race is
518
+ // tight enough to flake on busy CI runners.
514
519
  const server = startFakeWsServer({
515
- onOpen(ws) {
520
+ onMessage(ws, frame) {
521
+ if (frame.method !== "Test.startEvents") return;
522
+ ws.send(JSON.stringify({ id: frame.id, result: {} }));
523
+ ws.send(JSON.stringify({ method: "Ev.first", params: {} }));
516
524
  setTimeout(() => {
517
- ws.send(JSON.stringify({ method: "Ev.first", params: {} }));
518
- setTimeout(() => {
519
- ws.send(JSON.stringify({ method: "Ev.second", params: {} }));
520
- }, 10);
521
- }, 5);
525
+ ws.send(JSON.stringify({ method: "Ev.second", params: {} }));
526
+ }, 10);
522
527
  },
523
528
  });
524
529
  try {
@@ -528,6 +533,7 @@ describe("connectCdpWsTransport", () => {
528
533
  received.push(ev.method);
529
534
  if (ev.method === "Ev.first") unsub();
530
535
  });
536
+ await transport.send("Test.startEvents");
531
537
  await new Promise((r) => setTimeout(r, 60));
532
538
  expect(received).toEqual(["Ev.first"]);
533
539
  });
@@ -187,25 +187,25 @@ export function buildPinnedCandidateList(
187
187
 
188
188
  switch (mode) {
189
189
  case "extension": {
190
- const hostBrowserProxy = HostBrowserProxy.instance;
191
- if (!hostBrowserProxy.isAvailable()) {
192
- throw new CdpError(
193
- "transport_error",
194
- `Pinned mode "extension" unavailable: no active extension connection`,
195
- {
196
- attemptDiagnostics: [
197
- {
198
- candidateKind: "extension",
199
- inclusionReason: `pinned mode: extension`,
200
- stage: "candidate_selection",
201
- errorCode: "transport_error",
202
- errorMessage: "no active extension connection",
203
- },
204
- ],
205
- },
206
- );
207
- }
208
- return [
190
+ const hostBrowserProxy = HostBrowserProxy.instance;
191
+ if (!hostBrowserProxy.hasExtensionClient()) {
192
+ throw new CdpError(
193
+ "transport_error",
194
+ `Pinned mode "extension" unavailable: no Chrome Extension connected`,
195
+ {
196
+ attemptDiagnostics: [
197
+ {
198
+ candidateKind: "extension",
199
+ inclusionReason: `pinned mode: extension`,
200
+ stage: "candidate_selection",
201
+ errorCode: "transport_error",
202
+ errorMessage: "no Chrome Extension connected",
203
+ },
204
+ ],
205
+ },
206
+ );
207
+ }
208
+ return [
209
209
  {
210
210
  kind: "extension",
211
211
  reason: "pinned mode: extension",
@@ -291,12 +291,11 @@ export function buildCandidateList(context: ToolContext): BackendCandidate[] {
291
291
  const candidates: BackendCandidate[] = [];
292
292
  const hostBrowserProxy = HostBrowserProxy.instance;
293
293
 
294
- // 1. Extension -- preferred when the singleton proxy reports an active
295
- // extension connection is available.
296
- if (hostBrowserProxy.isAvailable()) {
294
+ // 1. Extension -- preferred when a Chrome Extension client is connected.
295
+ if (hostBrowserProxy.hasExtensionClient()) {
297
296
  candidates.push({
298
297
  kind: "extension",
299
- reason: "extension connected via registry singleton",
298
+ reason: "Chrome Extension connected via registry singleton",
300
299
  create() {
301
300
  const client = createExtensionCdpClient(
302
301
  hostBrowserProxy,
@@ -314,7 +313,7 @@ export function buildCandidateList(context: ToolContext): BackendCandidate[] {
314
313
  } else {
315
314
  log.debug(
316
315
  { conversationId },
317
- "CDP factory: no active extension connection, skipping extension candidate",
316
+ "CDP factory: no Chrome Extension connected, skipping extension candidate",
318
317
  );
319
318
  }
320
319
 
@@ -1,15 +1,2 @@
1
- export {
2
- type CdpInspectClientOptions,
3
- type CdpInspectHelpers,
4
- } from "./cdp-inspect-client.js";
5
1
  export { CdpError, type CdpErrorCode } from "./errors.js";
6
- export { type GetCdpClientOptions } from "./factory.js";
7
- export type {
8
- AttemptDiagnostic,
9
- AttemptStage,
10
- BackendCandidate,
11
- BrowserMode,
12
- CdpClient,
13
- CdpClientKind,
14
- ScopedCdpClient,
15
- } from "./types.js";
2
+ export type { CdpClient, CdpClientKind, ScopedCdpClient } from "./types.js";