@vellumai/assistant 0.7.0 → 0.7.1

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 (666) hide show
  1. package/ARCHITECTURE.md +6 -7
  2. package/Dockerfile +1 -0
  3. package/README.md +2 -2
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +79 -139
  5. package/bun.lock +3 -0
  6. package/docs/architecture/security.md +18 -16
  7. package/knip.json +1 -0
  8. package/node_modules/@vellumai/skill-host-contracts/__tests__/client.test.ts +1 -5
  9. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -5
  10. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -16
  11. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +1 -9
  12. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +12 -12
  13. package/node_modules/@vellumai/slack-text/bun.lock +24 -0
  14. package/node_modules/@vellumai/slack-text/package.json +18 -0
  15. package/node_modules/@vellumai/slack-text/src/index.test.ts +153 -0
  16. package/node_modules/@vellumai/slack-text/src/index.ts +235 -0
  17. package/node_modules/@vellumai/slack-text/tsconfig.json +20 -0
  18. package/openapi.yaml +294 -107
  19. package/package.json +4 -2
  20. package/scripts/generate-openapi.ts +16 -111
  21. package/src/__tests__/agent-wake-override-profile.test.ts +23 -1
  22. package/src/__tests__/anthropic-provider.test.ts +56 -13
  23. package/src/__tests__/app-conversation-ids-backfill.test.ts +278 -0
  24. package/src/__tests__/app-conversation-ids.test.ts +151 -0
  25. package/src/__tests__/approval-cascade.test.ts +0 -15
  26. package/src/__tests__/approval-routes-http.test.ts +6 -17
  27. package/src/__tests__/assistant-event-hub.test.ts +126 -77
  28. package/src/__tests__/assistant-event.test.ts +0 -5
  29. package/src/__tests__/assistant-events-sse-hardening.test.ts +37 -15
  30. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -29
  31. package/src/__tests__/background-shell-host-bash.test.ts +34 -43
  32. package/src/__tests__/call-controller.test.ts +1 -1
  33. package/src/__tests__/call-site-routing-provider.test.ts +193 -0
  34. package/src/__tests__/channel-approval-routes.test.ts +10 -296
  35. package/src/__tests__/channel-approvals.test.ts +25 -17
  36. package/src/__tests__/channel-guardian.test.ts +100 -146
  37. package/src/__tests__/checker.test.ts +20 -34
  38. package/src/__tests__/compact-event-conversation-id-guard.test.ts +50 -0
  39. package/src/__tests__/compaction-events.test.ts +2 -0
  40. package/src/__tests__/config-schema.test.ts +6 -48
  41. package/src/__tests__/config-watcher.test.ts +12 -0
  42. package/src/__tests__/connection-policy.test.ts +1 -52
  43. package/src/__tests__/contacts-write.test.ts +2 -64
  44. package/src/__tests__/context-image-dimensions.test.ts +1 -1
  45. package/src/__tests__/context-search-memory-source.test.ts +120 -1
  46. package/src/__tests__/context-search-memory-v2-source.test.ts +383 -0
  47. package/src/__tests__/context-search-pkb-source.test.ts +49 -0
  48. package/src/__tests__/context-search-workspace-source.test.ts +9 -22
  49. package/src/__tests__/context-window-manager.test.ts +46 -0
  50. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  51. package/src/__tests__/conversation-agent-loop-overflow.test.ts +102 -29
  52. package/src/__tests__/conversation-agent-loop.test.ts +980 -13
  53. package/src/__tests__/conversation-analysis-routes.test.ts +12 -10
  54. package/src/__tests__/conversation-attention-telegram.test.ts +11 -3
  55. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -291
  56. package/src/__tests__/conversation-history-web-search.test.ts +4 -3
  57. package/src/__tests__/conversation-inference-profile-route.test.ts +12 -23
  58. package/src/__tests__/conversation-lifecycle.test.ts +4 -4
  59. package/src/__tests__/conversation-process-callsite.test.ts +79 -2
  60. package/src/__tests__/conversation-queue.test.ts +3 -8
  61. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -161
  62. package/src/__tests__/conversation-routes-guardian-reply.test.ts +0 -32
  63. package/src/__tests__/conversation-routes-slash-commands.test.ts +75 -66
  64. package/src/__tests__/conversation-runtime-assembly.test.ts +257 -3
  65. package/src/__tests__/conversation-slash-commands.test.ts +24 -4
  66. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  67. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  68. package/src/__tests__/conversation-starter-routes.test.ts +79 -2
  69. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -5
  70. package/src/__tests__/conversation-surfaces-standalone.test.ts +18 -14
  71. package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -2
  72. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +8 -46
  73. package/src/__tests__/conversation-usage.test.ts +253 -3
  74. package/src/__tests__/credential-execution-shell-lockdown.test.ts +0 -39
  75. package/src/__tests__/credential-health-service.test.ts +68 -0
  76. package/src/__tests__/credential-security-e2e.test.ts +4 -3
  77. package/src/__tests__/credential-security-invariants.test.ts +1 -5
  78. package/src/__tests__/credential-token-resolver.test.ts +180 -0
  79. package/src/__tests__/cu-unified-flow.test.ts +33 -16
  80. package/src/__tests__/daemon-assistant-events.test.ts +34 -21
  81. package/src/__tests__/daemon-credential-client.test.ts +4 -1
  82. package/src/__tests__/db-connection-isolation.test.ts +125 -0
  83. package/src/__tests__/db-migration-rollback.test.ts +101 -0
  84. package/src/__tests__/db-slack-compaction-watermark-migration.test.ts +169 -0
  85. package/src/__tests__/deterministic-verification-control-plane.test.ts +7 -80
  86. package/src/__tests__/document-conversations.test.ts +332 -0
  87. package/src/__tests__/embedding-managed-proxy-selection.test.ts +2 -2
  88. package/src/__tests__/emit-event-signal.test.ts +4 -6
  89. package/src/__tests__/events-client-registration.test.ts +193 -49
  90. package/src/__tests__/filing-service.test.ts +58 -7
  91. package/src/__tests__/first-greeting.test.ts +156 -150
  92. package/src/__tests__/fixtures/mock-chrome-extension.ts +108 -66
  93. package/src/__tests__/get-skill-detail-audit.test.ts +3 -8
  94. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  95. package/src/__tests__/guardian-dispatch.test.ts +1 -1
  96. package/src/__tests__/guardian-grant-minting.test.ts +7 -2
  97. package/src/__tests__/guardian-routing-invariants.test.ts +7 -2
  98. package/src/__tests__/guardian-routing-state.test.ts +1 -1
  99. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +32 -11
  100. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +2 -83
  101. package/src/__tests__/headless-browser-mode.test.ts +4 -9
  102. package/src/__tests__/headless-browser-navigate.test.ts +21 -20
  103. package/src/__tests__/heartbeat-service.test.ts +289 -7
  104. package/src/__tests__/helpers/channel-test-adapter.ts +2 -2
  105. package/src/__tests__/helpers/create-guardian-binding.ts +91 -0
  106. package/src/__tests__/host-bash-proxy.test.ts +46 -122
  107. package/src/__tests__/host-browser-e2e-cloud.test.ts +36 -497
  108. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +26 -96
  109. package/src/__tests__/host-browser-proxy.test.ts +111 -185
  110. package/src/__tests__/host-browser-routes.test.ts +45 -75
  111. package/src/__tests__/host-browser-ws-events-e2e.test.ts +26 -30
  112. package/src/__tests__/host-cu-proxy.test.ts +56 -111
  113. package/src/__tests__/host-file-proxy.test.ts +44 -98
  114. package/src/__tests__/host-file-read-tool.test.ts +42 -21
  115. package/src/__tests__/host-shell-tool.test.ts +33 -68
  116. package/src/__tests__/host-transfer-pending-interactions.test.ts +2 -18
  117. package/src/__tests__/host-transfer-proxy.test.ts +43 -53
  118. package/src/__tests__/http-user-message-parity.test.ts +0 -6
  119. package/src/__tests__/inbound-slack-persistence.test.ts +31 -0
  120. package/src/__tests__/injector-chain.test.ts +10 -5
  121. package/src/__tests__/injector-pkb-v2-silenced.test.ts +124 -0
  122. package/src/__tests__/inline-command-runner.test.ts +0 -66
  123. package/src/__tests__/inline-skill-load-permissions.test.ts +0 -2
  124. package/src/__tests__/install-skill-routing.test.ts +1 -13
  125. package/src/__tests__/llm-callsite-catalog.test.ts +34 -0
  126. package/src/__tests__/llm-catalog-parity.test.ts +90 -0
  127. package/src/__tests__/llm-context-resolution.test.ts +180 -0
  128. package/src/__tests__/llm-resolver.test.ts +80 -12
  129. package/src/__tests__/llm-usage-store.test.ts +269 -4
  130. package/src/__tests__/log-export-routes.test.ts +89 -0
  131. package/src/__tests__/managed-profile-guard.test.ts +225 -0
  132. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -10
  133. package/src/__tests__/manual-token-reconciliation.test.ts +334 -0
  134. package/src/__tests__/memory-v2-static-injector.test.ts +95 -0
  135. package/src/__tests__/migration-cross-version-compatibility.test.ts +197 -291
  136. package/src/__tests__/migration-export-http.test.ts +33 -26
  137. package/src/__tests__/migration-export-streaming.test.ts +18 -10
  138. package/src/__tests__/migration-export-to-gcs.test.ts +49 -9
  139. package/src/__tests__/migration-import-commit-http.test.ts +66 -21
  140. package/src/__tests__/migration-import-from-gcs.test.ts +50 -9
  141. package/src/__tests__/migration-import-from-url.test.ts +20 -6
  142. package/src/__tests__/migration-import-preflight-http.test.ts +95 -95
  143. package/src/__tests__/migration-parity-persistence.test.ts +62 -25
  144. package/src/__tests__/migration-transport.test.ts +115 -23
  145. package/src/__tests__/migration-validate-http.test.ts +105 -80
  146. package/src/__tests__/migration-wizard.test.ts +133 -27
  147. package/src/__tests__/non-member-access-request.test.ts +1 -1
  148. package/src/__tests__/notification-guardian-path.test.ts +1 -1
  149. package/src/__tests__/oauth-store.test.ts +19 -0
  150. package/src/__tests__/platform-bash-auto-approve.test.ts +21 -12
  151. package/src/__tests__/prechat-onboarding-contract.test.ts +31 -7
  152. package/src/__tests__/pricing.test.ts +68 -4
  153. package/src/__tests__/process-message-background-slack.test.ts +331 -0
  154. package/src/__tests__/provider-managed-proxy-integration.test.ts +153 -17
  155. package/src/__tests__/provider-send-message-override-profile.test.ts +50 -0
  156. package/src/__tests__/provider-usage-tracking.test.ts +208 -0
  157. package/src/__tests__/reaction-persistence.test.ts +9 -6
  158. package/src/__tests__/rebind-secrets-screen.test.ts +53 -16
  159. package/src/__tests__/recording-handler.test.ts +64 -81
  160. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +4 -3
  161. package/src/__tests__/relay-server.test.ts +18 -13
  162. package/src/__tests__/require-fresh-approval.test.ts +13 -22
  163. package/src/__tests__/runtime-attachment-metadata.test.ts +1 -1
  164. package/src/__tests__/runtime-events-sse-parity.test.ts +3 -4
  165. package/src/__tests__/runtime-events-sse.test.ts +3 -12
  166. package/src/__tests__/search-skills-unified.test.ts +9 -15
  167. package/src/__tests__/secret-ingress-cli.test.ts +2 -5
  168. package/src/__tests__/secret-ingress-http.test.ts +0 -4
  169. package/src/__tests__/secret-onetime-send.test.ts +4 -2
  170. package/src/__tests__/secret-prompt-log-hygiene.test.ts +24 -7
  171. package/src/__tests__/secret-prompter-channel-fallback.test.ts +42 -47
  172. package/src/__tests__/secret-response-routing.test.ts +29 -15
  173. package/src/__tests__/secret-routes-managed-proxy.test.ts +5 -1
  174. package/src/__tests__/secret-scanner.test.ts +2 -545
  175. package/src/__tests__/send-endpoint-busy.test.ts +9 -24
  176. package/src/__tests__/settings-routes.test.ts +1 -1
  177. package/src/__tests__/shell-credential-ref.test.ts +0 -8
  178. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -56
  179. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -11
  180. package/src/__tests__/skill-tool-factory.test.ts +97 -0
  181. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -30
  182. package/src/__tests__/skills-files-catalog-fallback.test.ts +11 -17
  183. package/src/__tests__/slack-inbound-verification.test.ts +1 -62
  184. package/src/__tests__/subagent-fork-notifications.test.ts +57 -47
  185. package/src/__tests__/subagent-manager-notify.test.ts +70 -70
  186. package/src/__tests__/subagent-notify-parent.test.ts +80 -83
  187. package/src/__tests__/system-prompt.test.ts +115 -13
  188. package/src/__tests__/terminal-tools.test.ts +0 -89
  189. package/src/__tests__/thread-backfill.test.ts +945 -31
  190. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -36
  191. package/src/__tests__/tool-execute-pipeline.test.ts +0 -6
  192. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -16
  193. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +9 -19
  194. package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -7
  195. package/src/__tests__/tool-executor.test.ts +12 -19
  196. package/src/__tests__/tool-metrics-listener.test.ts +0 -35
  197. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
  198. package/src/__tests__/tool-trace-listener.test.ts +0 -17
  199. package/src/__tests__/transfer-progress-screen.test.ts +63 -26
  200. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -149
  201. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -4
  202. package/src/__tests__/trusted-contact-verification.test.ts +1 -1
  203. package/src/__tests__/tts-catalog-parity.test.ts +16 -5
  204. package/src/__tests__/usage-attribution.test.ts +247 -0
  205. package/src/__tests__/usage-cli.test.ts +143 -0
  206. package/src/__tests__/usage-grouped-buckets.test.ts +155 -0
  207. package/src/__tests__/usage-routes.test.ts +150 -0
  208. package/src/__tests__/validation-results-screen.test.ts +39 -16
  209. package/src/__tests__/vbundle-pax-and-symlink.test.ts +12 -3
  210. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +49 -137
  211. package/src/__tests__/verification-control-plane-policy.test.ts +4 -7
  212. package/src/__tests__/voice-session-bridge.test.ts +5 -5
  213. package/src/__tests__/workspace-migration-062-drop-memory-v2-edges-json.test.ts +103 -0
  214. package/src/__tests__/workspace-migration-063-release-notes-dynamic-model-context.test.ts +77 -0
  215. package/src/__tests__/workspace-migration-064-unwind-main-agent-opus-seed.test.ts +225 -0
  216. package/src/__tests__/workspace-migration-memory-v2-init.test.ts +8 -30
  217. package/src/acp/index.ts +0 -15
  218. package/src/acp/session-manager.ts +37 -34
  219. package/src/agent/loop.ts +16 -1
  220. package/src/approvals/AGENTS.md +4 -0
  221. package/src/approvals/__tests__/guardian-feed-event.test.ts +10 -3
  222. package/src/approvals/guardian-request-resolvers.ts +10 -2
  223. package/src/backup/__tests__/backup-worker.test.ts +36 -8
  224. package/src/backup/__tests__/paths.test.ts +2 -2
  225. package/src/backup/__tests__/restore.test.ts +45 -28
  226. package/src/backup/backup-worker.ts +36 -2
  227. package/src/backup/paths.ts +9 -6
  228. package/src/browser-session/events.ts +0 -9
  229. package/src/calls/call-store.ts +1 -34
  230. package/src/calls/guardian-question-copy.ts +0 -108
  231. package/src/calls/relay-server.ts +0 -24
  232. package/src/calls/twilio-rest.ts +0 -38
  233. package/src/calls/twilio-routes.ts +1 -1
  234. package/src/calls/voice-session-bridge.ts +7 -38
  235. package/src/channels/types.ts +1 -36
  236. package/src/cli/commands/__tests__/cache.test.ts +152 -5
  237. package/src/cli/commands/__tests__/memory-v2.test.ts +14 -28
  238. package/src/cli/commands/__tests__/trust.test.ts +21 -387
  239. package/src/cli/commands/backup.ts +4 -4
  240. package/src/cli/commands/cache-fs.ts +8 -0
  241. package/src/cli/commands/cache.ts +153 -82
  242. package/src/cli/commands/clients.ts +63 -5
  243. package/src/cli/commands/completions.ts +3 -3
  244. package/src/cli/commands/contacts.ts +231 -76
  245. package/src/cli/commands/keys.ts +4 -1
  246. package/src/cli/commands/memory-v2.ts +24 -52
  247. package/src/cli/commands/oauth/shared.ts +2 -29
  248. package/src/cli/commands/pending.ts +102 -0
  249. package/src/cli/commands/skills.ts +77 -35
  250. package/src/cli/commands/trust.ts +70 -430
  251. package/src/cli/commands/usage.ts +25 -16
  252. package/src/cli/lib/daemon-credential-client.ts +14 -0
  253. package/src/cli/program.ts +2 -0
  254. package/src/cli.ts +0 -21
  255. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  256. package/src/config/bundled-skills/messaging/TOOLS.json +14 -4
  257. package/src/config/env-registry.ts +12 -2
  258. package/src/config/env.ts +3 -14
  259. package/src/config/feature-flag-registry.json +30 -30
  260. package/src/config/llm-callsite-catalog.ts +12 -0
  261. package/src/config/llm-context-resolution.ts +80 -0
  262. package/src/config/llm-resolver.ts +58 -22
  263. package/src/config/loader.ts +3 -3
  264. package/src/config/schema.ts +2 -158
  265. package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
  266. package/src/config/schemas/call-site-catalog.ts +271 -0
  267. package/src/config/schemas/calls.ts +5 -5
  268. package/src/config/schemas/inference.ts +1 -1
  269. package/src/config/schemas/ingress.ts +1 -1
  270. package/src/config/schemas/llm.ts +31 -3
  271. package/src/config/schemas/memory-retrieval.ts +2 -2
  272. package/src/config/schemas/memory-v2.ts +9 -0
  273. package/src/config/schemas/security.ts +1 -42
  274. package/src/config/schemas/services.ts +6 -6
  275. package/src/config/schemas/skills.ts +5 -5
  276. package/src/config/schemas/tts.ts +1 -1
  277. package/src/config/seed-inference-profiles.ts +117 -0
  278. package/src/config/skills.ts +0 -90
  279. package/src/config/types.ts +3 -6
  280. package/src/contacts/contact-store.ts +0 -17
  281. package/src/contacts/contacts-write.ts +1 -105
  282. package/src/context/window-manager.ts +44 -5
  283. package/src/credential-execution/process-manager.ts +34 -10
  284. package/src/credential-health/credential-health-service.ts +21 -16
  285. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +75 -82
  286. package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -9
  287. package/src/daemon/connection-policy.ts +1 -26
  288. package/src/daemon/conversation-agent-loop-handlers.ts +53 -4
  289. package/src/daemon/conversation-agent-loop.ts +277 -36
  290. package/src/daemon/conversation-history.ts +8 -8
  291. package/src/daemon/conversation-launch.ts +20 -135
  292. package/src/daemon/conversation-lifecycle.ts +1 -1
  293. package/src/daemon/conversation-messaging.ts +1 -0
  294. package/src/daemon/conversation-process.ts +83 -163
  295. package/src/daemon/conversation-runtime-assembly.ts +219 -76
  296. package/src/daemon/conversation-slash.ts +47 -5
  297. package/src/daemon/conversation-store.ts +7 -31
  298. package/src/daemon/conversation-surfaces.ts +22 -28
  299. package/src/daemon/conversation-tool-setup.ts +3 -33
  300. package/src/daemon/conversation-usage.ts +36 -0
  301. package/src/daemon/conversation.ts +117 -233
  302. package/src/daemon/daemon-control.ts +3 -71
  303. package/src/daemon/daemon-skill-host.ts +8 -11
  304. package/src/daemon/dictation-profile-store.ts +2 -26
  305. package/src/daemon/first-greeting.ts +44 -156
  306. package/src/daemon/handlers/config-channels.ts +12 -12
  307. package/src/daemon/handlers/config-ingress.ts +4 -165
  308. package/src/daemon/handlers/config-model.ts +1 -1
  309. package/src/daemon/handlers/config-voice.ts +0 -42
  310. package/src/daemon/handlers/conversations.ts +11 -190
  311. package/src/daemon/handlers/recording.ts +26 -158
  312. package/src/daemon/handlers/shared.ts +23 -71
  313. package/src/daemon/handlers/skills.ts +42 -93
  314. package/src/daemon/host-bash-proxy.ts +67 -45
  315. package/src/daemon/host-browser-proxy.ts +65 -27
  316. package/src/daemon/host-cu-proxy.ts +40 -39
  317. package/src/daemon/host-file-proxy.ts +58 -37
  318. package/src/daemon/host-transfer-proxy.ts +84 -46
  319. package/src/daemon/lifecycle.ts +49 -15
  320. package/src/daemon/message-types/conversations.ts +7 -0
  321. package/src/daemon/message-types/host-bash.ts +1 -0
  322. package/src/daemon/message-types/host-cu.ts +1 -0
  323. package/src/daemon/message-types/host-file.ts +1 -0
  324. package/src/daemon/message-types/host-transfer.ts +1 -0
  325. package/src/daemon/message-types/messages.ts +10 -9
  326. package/src/daemon/message-types/workspace.ts +1 -1
  327. package/src/daemon/process-message.ts +102 -239
  328. package/src/daemon/server.ts +13 -462
  329. package/src/daemon/shutdown-handlers.ts +2 -2
  330. package/src/daemon/tool-side-effects.ts +125 -107
  331. package/src/daemon/trust-context.ts +13 -0
  332. package/src/daemon/wake-target-adapter.ts +4 -9
  333. package/src/events/domain-events.ts +0 -8
  334. package/src/events/tool-audit-listener.ts +3 -1
  335. package/src/events/tool-domain-event-publisher.ts +0 -10
  336. package/src/events/tool-metrics-listener.ts +0 -17
  337. package/src/events/tool-trace-listener.ts +0 -14
  338. package/src/filing/filing-service.ts +13 -1
  339. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +6 -2
  340. package/src/heartbeat/heartbeat-service.ts +23 -5
  341. package/src/home/__tests__/feed-writer.test.ts +0 -4
  342. package/src/home/__tests__/relationship-state-writer.test.ts +30 -0
  343. package/src/home/feed-writer.ts +1 -2
  344. package/src/home/relationship-state-writer.ts +16 -3
  345. package/src/ipc/__tests__/browser-ipc.test.ts +2 -12
  346. package/src/ipc/__tests__/skill-server-bidirectional.test.ts +0 -1
  347. package/src/ipc/assistant-server.ts +3 -10
  348. package/src/ipc/routes/__tests__/memory-v2-backfill.test.ts +39 -20
  349. package/src/ipc/routes/route-adapter.ts +1 -1
  350. package/src/ipc/routes/trust-rules.test.ts +0 -95
  351. package/src/ipc/skill-ipc-types.ts +41 -0
  352. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +13 -27
  353. package/src/ipc/skill-routes/__tests__/identity.test.ts +4 -23
  354. package/src/ipc/skill-routes/events.ts +12 -23
  355. package/src/ipc/skill-routes/identity.ts +4 -17
  356. package/src/ipc/skill-routes/index.ts +1 -1
  357. package/src/ipc/skill-server.ts +6 -39
  358. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +0 -8
  359. package/src/live-voice/protocol.ts +4 -13
  360. package/src/mcp/manager.ts +0 -5
  361. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +55 -0
  362. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +127 -0
  363. package/src/memory/app-git-service.ts +0 -32
  364. package/src/memory/app-store.ts +154 -0
  365. package/src/memory/attachments-store.ts +6 -0
  366. package/src/memory/context-search/sources/memory-v2.ts +578 -0
  367. package/src/memory/context-search/sources/memory.ts +5 -0
  368. package/src/memory/context-search/sources/pkb.ts +10 -1
  369. package/src/memory/context-search/sources/workspace.ts +3 -2
  370. package/src/memory/conversation-crud.ts +29 -4
  371. package/src/memory/conversation-disk-view.ts +1 -5
  372. package/src/memory/conversation-starter-checkpoints.ts +63 -0
  373. package/src/memory/db-connection.ts +62 -0
  374. package/src/memory/db-init.ts +14 -0
  375. package/src/memory/embedding-backend.ts +3 -21
  376. package/src/memory/embedding-gemini.ts +0 -2
  377. package/src/memory/embedding-local.ts +6 -6
  378. package/src/memory/embedding-ollama.ts +6 -6
  379. package/src/memory/embedding-openai.ts +6 -6
  380. package/src/memory/embedding-types.ts +21 -0
  381. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +3 -7
  382. package/src/memory/graph/conversation-graph-memory.ts +35 -13
  383. package/src/memory/graph/injection.test.ts +2 -2
  384. package/src/memory/graph/injection.ts +1 -1
  385. package/src/memory/guardian-action-store.ts +0 -83
  386. package/src/memory/guardian-approvals.ts +0 -48
  387. package/src/memory/indexer.ts +1 -15
  388. package/src/memory/job-handlers/conversation-starters.ts +36 -53
  389. package/src/memory/job-utils.ts +0 -6
  390. package/src/memory/jobs-store.ts +0 -1
  391. package/src/memory/jobs-worker.ts +2 -16
  392. package/src/memory/llm-request-log-store.ts +0 -41
  393. package/src/memory/llm-usage-store.ts +129 -43
  394. package/src/memory/memory-v2-activation-log-store.ts +115 -0
  395. package/src/memory/migrations/233-document-conversations.ts +54 -0
  396. package/src/memory/migrations/234-memory-v2-activation-logs.ts +55 -0
  397. package/src/memory/migrations/235-llm-usage-attribution.ts +31 -0
  398. package/src/memory/migrations/235-slack-compaction-watermark.ts +44 -0
  399. package/src/memory/migrations/236-tool-invocations-matched-rule-id.ts +26 -0
  400. package/src/memory/migrations/__tests__/234-memory-v2-activation-logs.test.ts +182 -0
  401. package/src/memory/migrations/index.ts +14 -0
  402. package/src/memory/migrations/registry.ts +24 -0
  403. package/src/memory/raw-query.ts +2 -68
  404. package/src/memory/schema/conversations.ts +7 -0
  405. package/src/memory/schema/infrastructure.ts +25 -0
  406. package/src/memory/search/semantic.ts +5 -16
  407. package/src/memory/tool-usage-store.ts +2 -0
  408. package/src/memory/usage-buckets.ts +40 -1
  409. package/src/memory/usage-grouped-buckets.ts +127 -0
  410. package/src/memory/v2/__tests__/activation.test.ts +289 -90
  411. package/src/memory/v2/__tests__/backfill-jobs.test.ts +2 -129
  412. package/src/memory/v2/__tests__/consolidation-job.test.ts +28 -11
  413. package/src/memory/v2/__tests__/edge-index.test.ts +278 -0
  414. package/src/memory/v2/__tests__/injection.test.ts +384 -15
  415. package/src/memory/v2/__tests__/migration.test.ts +64 -36
  416. package/src/memory/v2/__tests__/page-store.test.ts +191 -8
  417. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +181 -0
  418. package/src/memory/v2/__tests__/skill-store.test.ts +115 -3
  419. package/src/memory/v2/__tests__/static-context.test.ts +153 -0
  420. package/src/memory/v2/activation.ts +168 -97
  421. package/src/memory/v2/backfill-jobs.ts +15 -100
  422. package/src/memory/v2/consolidation-job.ts +14 -12
  423. package/src/memory/v2/edge-index.ts +191 -0
  424. package/src/memory/v2/injection.ts +182 -58
  425. package/src/memory/v2/migration.ts +57 -64
  426. package/src/memory/v2/now-text.ts +2 -3
  427. package/src/memory/v2/page-store.ts +168 -31
  428. package/src/memory/v2/prompts/consolidation.ts +118 -42
  429. package/src/memory/v2/prompts/sweep.ts +3 -3
  430. package/src/memory/v2/skill-store.ts +55 -7
  431. package/src/memory/v2/static-context.ts +62 -0
  432. package/src/memory/v2/types.ts +10 -20
  433. package/src/memory/validation.ts +0 -11
  434. package/src/messaging/draft-store.ts +0 -6
  435. package/src/messaging/provider-types.ts +8 -0
  436. package/src/messaging/provider.ts +7 -0
  437. package/src/messaging/providers/gmail/client.ts +1 -121
  438. package/src/messaging/providers/outlook/client.ts +0 -73
  439. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +226 -0
  440. package/src/messaging/providers/slack/adapter.ts +122 -21
  441. package/src/messaging/providers/slack/backfill.test.ts +95 -6
  442. package/src/messaging/providers/slack/backfill.ts +89 -11
  443. package/src/messaging/providers/slack/client.ts +10 -124
  444. package/src/messaging/providers/slack/message-metadata.ts +12 -2
  445. package/src/messaging/providers/slack/render-transcript.test.ts +56 -0
  446. package/src/messaging/providers/slack/render-transcript.ts +126 -25
  447. package/src/messaging/providers/slack/types.ts +1 -0
  448. package/src/oauth/connection-resolver.test.ts +8 -0
  449. package/src/oauth/connection-resolver.ts +8 -16
  450. package/src/oauth/credential-token-resolver.ts +97 -0
  451. package/src/oauth/manual-token-connection.ts +30 -34
  452. package/src/oauth/oauth-store.ts +6 -4
  453. package/src/outbound-proxy/certs.ts +0 -7
  454. package/src/outbound-proxy/config.ts +0 -74
  455. package/src/outbound-proxy/health.ts +0 -44
  456. package/src/outbound-proxy/index.ts +0 -22
  457. package/src/permissions/approval-provenance.test.ts +184 -0
  458. package/src/permissions/approval-provenance.ts +70 -0
  459. package/src/permissions/checker.ts +4 -1
  460. package/src/permissions/gateway-threshold-reader.ts +4 -1
  461. package/src/permissions/prompter.ts +9 -2
  462. package/src/permissions/secret-prompter.ts +21 -48
  463. package/src/permissions/types.ts +33 -0
  464. package/src/permissions/workspace-policy.ts +0 -5
  465. package/src/platform/sync-identity.ts +0 -8
  466. package/src/plugins/defaults/injectors.ts +69 -2
  467. package/src/plugins/defaults/overflow-reduce.ts +3 -2
  468. package/src/plugins/types.ts +8 -0
  469. package/src/prompts/system-prompt.ts +34 -70
  470. package/src/prompts/templates/BOOTSTRAP.md +52 -6
  471. package/src/prompts/update-bulletin-job.ts +2 -0
  472. package/src/providers/__tests__/retry-callsite.test.ts +138 -1
  473. package/src/providers/anthropic/client.ts +72 -33
  474. package/src/providers/call-site-routing.ts +42 -3
  475. package/src/providers/gemini/client.ts +18 -2
  476. package/src/providers/managed-proxy/context.ts +0 -5
  477. package/src/providers/model-catalog.ts +105 -19
  478. package/src/providers/openai/chat-completions-provider.ts +6 -0
  479. package/src/providers/openai/responses-provider.ts +7 -1
  480. package/src/providers/provider-send-message.ts +45 -2
  481. package/src/providers/ratelimit.ts +7 -2
  482. package/src/providers/registry.ts +14 -9
  483. package/src/providers/retry.ts +96 -8
  484. package/src/providers/types.ts +13 -0
  485. package/src/providers/usage-tracking.ts +96 -0
  486. package/src/runtime/AGENTS.md +10 -6
  487. package/src/runtime/__tests__/agent-wake.test.ts +89 -0
  488. package/src/runtime/agent-wake.ts +39 -2
  489. package/src/runtime/assistant-event-hub.ts +541 -45
  490. package/src/runtime/assistant-event.ts +1 -6
  491. package/src/runtime/auth/context.ts +0 -9
  492. package/src/runtime/auth/middleware.ts +1 -1
  493. package/src/runtime/auth/route-policy.ts +11 -9
  494. package/src/runtime/auth/token-service.ts +0 -11
  495. package/src/runtime/channel-approvals.ts +6 -2
  496. package/src/runtime/channel-verification-service.ts +3 -5
  497. package/src/runtime/http-errors.ts +0 -34
  498. package/src/runtime/http-router.ts +6 -3
  499. package/src/runtime/http-server.ts +22 -82
  500. package/src/runtime/http-types.ts +5 -0
  501. package/src/runtime/interactive-ui.ts +0 -1
  502. package/src/runtime/middleware/auth.ts +0 -20
  503. package/src/runtime/migrations/__tests__/v1-test-helpers.ts +112 -0
  504. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +11 -4
  505. package/src/runtime/migrations/__tests__/vbundle-builder-v1-shape.test.ts +253 -0
  506. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +19 -6
  507. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +71 -27
  508. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +41 -2
  509. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +143 -79
  510. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +143 -23
  511. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +2 -2
  512. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +371 -0
  513. package/src/runtime/migrations/migration-transport.ts +46 -13
  514. package/src/runtime/migrations/migration-wizard.ts +2 -2
  515. package/src/runtime/migrations/origin-mode.ts +40 -0
  516. package/src/runtime/migrations/vbundle-builder.ts +133 -79
  517. package/src/runtime/migrations/vbundle-import-analyzer.ts +9 -7
  518. package/src/runtime/migrations/vbundle-importer.ts +7 -7
  519. package/src/runtime/migrations/vbundle-metadata-merge.ts +1 -1
  520. package/src/runtime/migrations/vbundle-streaming-importer.ts +3 -3
  521. package/src/runtime/migrations/vbundle-streaming-validator.ts +48 -26
  522. package/src/runtime/migrations/vbundle-validator.ts +214 -41
  523. package/src/runtime/pending-interactions.ts +13 -4
  524. package/src/runtime/routes/__tests__/acp-routes.test.ts +0 -1
  525. package/src/runtime/routes/__tests__/backup-routes.test.ts +28 -19
  526. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +235 -0
  527. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +58 -0
  528. package/src/runtime/routes/__tests__/migration-export-secrets-redacted.test.ts +54 -0
  529. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +19 -6
  530. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +7 -7
  531. package/src/runtime/routes/acp-routes.test.ts +0 -3
  532. package/src/runtime/routes/acp-routes.ts +3 -7
  533. package/src/runtime/routes/app-management-routes.ts +18 -9
  534. package/src/runtime/routes/approval-routes.ts +55 -14
  535. package/src/runtime/routes/avatar-routes.ts +3 -5
  536. package/src/runtime/routes/browser-routes.ts +1 -15
  537. package/src/runtime/routes/channel-guardian-routes.ts +1 -5
  538. package/src/runtime/routes/channel-readiness-routes.ts +3 -7
  539. package/src/runtime/routes/channel-route-shared.ts +2 -28
  540. package/src/runtime/routes/client-routes.ts +45 -12
  541. package/src/runtime/routes/consolidation-routes.ts +115 -0
  542. package/src/runtime/routes/conversation-list-routes.ts +12 -29
  543. package/src/runtime/routes/conversation-management-routes.ts +14 -51
  544. package/src/runtime/routes/conversation-query-routes.ts +120 -8
  545. package/src/runtime/routes/conversation-routes.ts +44 -528
  546. package/src/runtime/routes/conversation-starter-routes.ts +19 -40
  547. package/src/runtime/routes/documents-routes.ts +53 -18
  548. package/src/runtime/routes/events-routes.ts +59 -91
  549. package/src/runtime/routes/filing-routes.ts +18 -1
  550. package/src/runtime/routes/guardian-action-routes.ts +4 -9
  551. package/src/runtime/routes/host-bash-routes.ts +3 -2
  552. package/src/runtime/routes/host-browser-routes.ts +9 -33
  553. package/src/runtime/routes/host-cu-routes.ts +6 -1
  554. package/src/runtime/routes/host-file-routes.ts +3 -2
  555. package/src/runtime/routes/host-transfer-routes.ts +11 -15
  556. package/src/runtime/routes/identity-routes.ts +78 -6
  557. package/src/runtime/routes/inbound-message-handler.ts +580 -137
  558. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -88
  559. package/src/runtime/routes/inbound-stages/background-dispatch.ts +3 -0
  560. package/src/runtime/routes/index.ts +4 -0
  561. package/src/runtime/routes/integrations/slack/channel.ts +0 -24
  562. package/src/runtime/routes/llm-call-sites-routes.ts +22 -0
  563. package/src/runtime/routes/memory-v2-routes.ts +10 -15
  564. package/src/runtime/routes/migration-routes.ts +188 -31
  565. package/src/runtime/routes/playground/guard.ts +1 -1
  566. package/src/runtime/routes/playground/index.ts +0 -2
  567. package/src/runtime/routes/recording-routes.ts +4 -24
  568. package/src/runtime/routes/rename-conversation-routes.ts +2 -6
  569. package/src/runtime/routes/schedule-routes.ts +3 -6
  570. package/src/runtime/routes/secret-routes.ts +87 -18
  571. package/src/runtime/routes/settings-routes.ts +29 -28
  572. package/src/runtime/routes/skills-routes.ts +12 -31
  573. package/src/runtime/routes/suggest-trust-rule-routes.ts +32 -1
  574. package/src/runtime/routes/task-routes.ts +6 -6
  575. package/src/runtime/routes/trust-rules-routes.ts +3 -94
  576. package/src/runtime/routes/types.ts +4 -4
  577. package/src/runtime/routes/upgrade-broadcast-routes.ts +3 -10
  578. package/src/runtime/routes/usage-routes.ts +87 -10
  579. package/src/runtime/routes/user-routes.ts +17 -31
  580. package/src/runtime/routes/work-items-routes.ts +1 -4
  581. package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -2
  582. package/src/runtime/services/analyze-conversation.ts +7 -17
  583. package/src/runtime/services/conversation-serializer.ts +2 -4
  584. package/src/runtime/verification-outbound-actions.ts +1 -1
  585. package/src/runtime/verification-rate-limiter.ts +1 -1
  586. package/src/schedule/schedule-store.ts +0 -16
  587. package/src/security/secret-scanner.ts +14 -547
  588. package/src/security/secure-keys.ts +31 -11
  589. package/src/security/token-manager.ts +7 -3
  590. package/src/signals/cancel.ts +16 -25
  591. package/src/signals/conversation-undo.ts +2 -27
  592. package/src/signals/emit-event.ts +1 -2
  593. package/src/signals/user-message.ts +108 -22
  594. package/src/skills/catalog-install.ts +1 -0
  595. package/src/skills/clawhub.ts +2 -2
  596. package/src/skills/inline-command-runner.ts +1 -7
  597. package/src/subagent/manager.ts +67 -84
  598. package/src/tasks/task-store.ts +1 -28
  599. package/src/telemetry/types.ts +6 -0
  600. package/src/telemetry/usage-telemetry-reporter.test.ts +38 -15
  601. package/src/telemetry/usage-telemetry-reporter.ts +3 -5
  602. package/src/tools/acp/spawn.test.ts +1 -2
  603. package/src/tools/acp/steer.test.ts +1 -2
  604. package/src/tools/browser/__tests__/browser-status.test.ts +44 -127
  605. package/src/tools/browser/browser-execution.ts +31 -147
  606. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +92 -68
  607. package/src/tools/browser/cdp-client/factory.ts +48 -76
  608. package/src/tools/browser/cdp-client/index.ts +1 -14
  609. package/src/tools/executor.ts +44 -31
  610. package/src/tools/host-filesystem/edit.ts +3 -2
  611. package/src/tools/host-filesystem/read.ts +3 -2
  612. package/src/tools/host-filesystem/transfer.test.ts +45 -42
  613. package/src/tools/host-filesystem/transfer.ts +4 -3
  614. package/src/tools/host-filesystem/write.ts +3 -2
  615. package/src/tools/host-terminal/host-shell.ts +4 -3
  616. package/src/tools/network/script-proxy/index.ts +1 -10
  617. package/src/tools/permission-checker.ts +66 -1
  618. package/src/tools/skills/sandbox-runner.ts +1 -6
  619. package/src/tools/skills/skill-tool-factory.ts +32 -0
  620. package/src/tools/terminal/safe-env.ts +1 -0
  621. package/src/tools/terminal/shell.ts +2 -78
  622. package/src/tools/types.ts +12 -39
  623. package/src/tts/__tests__/provider-catalog.test.ts +2 -2
  624. package/src/tts/provider-catalog.ts +1 -1
  625. package/src/usage/actors.ts +2 -1
  626. package/src/usage/attribution.ts +185 -0
  627. package/src/usage/pricing.ts +166 -0
  628. package/src/usage/types.ts +14 -0
  629. package/src/util/json.ts +13 -0
  630. package/src/util/logger.ts +3 -3
  631. package/src/util/pricing.ts +50 -3
  632. package/src/work-items/work-item-runner.ts +15 -42
  633. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +4 -3
  634. package/src/workspace/migrations/052-seed-default-inference-profiles.ts +3 -3
  635. package/src/workspace/migrations/060-memory-v2-init.ts +2 -18
  636. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +59 -0
  637. package/src/workspace/migrations/062-drop-memory-v2-edges-json.ts +27 -0
  638. package/src/workspace/migrations/063-release-notes-dynamic-model-context.ts +70 -0
  639. package/src/workspace/migrations/064-unwind-main-agent-opus-seed.ts +64 -0
  640. package/src/workspace/migrations/registry.ts +8 -0
  641. package/src/workspace/provider-commit-message-generator.ts +3 -3
  642. package/src/__tests__/sandbox-diagnostics.test.ts +0 -138
  643. package/src/__tests__/sandbox-host-parity.test.ts +0 -1024
  644. package/src/__tests__/secret-detection-handler.test.ts +0 -67
  645. package/src/__tests__/secret-scanner-executor.test.ts +0 -450
  646. package/src/__tests__/tcc-sandbox-deny.test.ts +0 -198
  647. package/src/__tests__/terminal-sandbox.test.ts +0 -374
  648. package/src/__tests__/tool-notification-listener.test.ts +0 -65
  649. package/src/context/__tests__/microcompact.test.ts +0 -805
  650. package/src/context/microcompact.ts +0 -443
  651. package/src/daemon/handlers/slack-channel-oauth-install.ts +0 -197
  652. package/src/events/tool-notification-listener.ts +0 -17
  653. package/src/ipc/routes/__tests__/memory-v2-validate.test.ts +0 -219
  654. package/src/memory/v2/__tests__/edges.test.ts +0 -435
  655. package/src/memory/v2/edges.ts +0 -217
  656. package/src/prompts/__tests__/system-prompt-memory-v2.test.ts +0 -197
  657. package/src/runtime/__tests__/chrome-extension-registry.test.ts +0 -518
  658. package/src/runtime/__tests__/client-registry.test.ts +0 -271
  659. package/src/runtime/chrome-extension-registry.ts +0 -368
  660. package/src/runtime/client-registry.ts +0 -254
  661. package/src/runtime/routes/inbound-stages/verification-intercept.ts +0 -329
  662. package/src/tools/secret-detection-handler.ts +0 -269
  663. package/src/tools/terminal/backends/native.ts +0 -327
  664. package/src/tools/terminal/backends/types.ts +0 -37
  665. package/src/tools/terminal/sandbox-diagnostics.ts +0 -87
  666. package/src/tools/terminal/sandbox.ts +0 -40
@@ -3,15 +3,15 @@
3
3
  *
4
4
  * Boots the runtime HTTP server in-process, opens a mock chrome-extension
5
5
  * WebSocket against `/v1/browser-relay`, and drives
6
- * `HostBrowserProxy.request()` end-to-end:
6
+ * `HostBrowserProxy.instance.request()` end-to-end:
7
7
  *
8
8
  * proxy.request()
9
- * → sendToClient (routed via ChromeExtensionRegistry by guardianId)
9
+ * → sendToExtension (routed via assistant event hub)
10
10
  * → mock extension WebSocket receives host_browser_request
11
11
  * → mock CDP handler (Browser.getVersion fake)
12
- * → POST /v1/host-browser-result
13
- * → handleHostBrowserResultconversation.resolveHostBrowser
14
- * → proxy.resolve() → request() resolves
12
+ * → POST /v1/host-browser-result (or WS host_browser_result frame)
13
+ * → resolveHostBrowserResultByRequestIdproxy.resolve()
14
+ * → request() resolves
15
15
  *
16
16
  * Covers:
17
17
  * - Happy path: Browser.getVersion round-trips and returns the fake
@@ -63,76 +63,17 @@ mock.module("../config/loader.js", () => ({
63
63
 
64
64
  // ── Real imports (after mocks) ──────────────────────────────────────
65
65
 
66
- import type { Conversation } from "../daemon/conversation.js";
67
66
  import { HostBrowserProxy } from "../daemon/host-browser-proxy.js";
68
- import type { ServerMessage } from "../daemon/message-protocol.js";
69
67
  import { getDb } from "../memory/db-connection.js";
70
68
  import { initializeDb } from "../memory/db-init.js";
69
+ import { assistantEventHub } from "../runtime/assistant-event-hub.js";
71
70
  import { mintToken } from "../runtime/auth/token-service.js";
72
- import {
73
- __resetChromeExtensionRegistryForTests,
74
- getChromeExtensionRegistry,
75
- } from "../runtime/chrome-extension-registry.js";
76
71
  import { RuntimeHttpServer } from "../runtime/http-server.js";
77
- import * as pendingInteractions from "../runtime/pending-interactions.js";
78
72
 
79
73
  initializeDb();
80
74
 
81
75
  // ── Helpers ─────────────────────────────────────────────────────────
82
76
 
83
- /**
84
- * Wrap a HostBrowserProxy in a sendToClient that:
85
- * 1. Routes host_browser_request/host_browser_cancel via the Chrome
86
- * extension registry for the given guardianId.
87
- * 2. Registers a pending interaction for each request so the
88
- * `/v1/host-browser-result` HTTP route can find the stub
89
- * conversation and call `resolveHostBrowser` on it.
90
- *
91
- * Returns the proxy and its stub conversation. In production this
92
- * wiring lives in `conversation-routes.ts` `makeHubPublisher`; the test
93
- * reproduces the minimum surface needed for the round-trip.
94
- */
95
- function createBoundProxy(
96
- guardianId: string,
97
- conversationId: string,
98
- ): { proxy: HostBrowserProxy; conversation: Conversation } {
99
- // The stub Conversation's `resolveHostBrowser` routes straight back
100
- // to the real proxy. Declare the proxy reference first so the stub
101
- // can close over it before the proxy itself is constructed below.
102
- let proxyRef: HostBrowserProxy | null = null;
103
- const conversation = {
104
- resolveHostBrowser(
105
- requestId: string,
106
- response: { content: string; isError: boolean },
107
- ) {
108
- proxyRef?.resolve(requestId, response);
109
- },
110
- } as unknown as Conversation;
111
-
112
- const sendToClient = (msg: ServerMessage) => {
113
- // Register pending interactions for host_browser_request envelopes
114
- // so the /v1/host-browser-result route can look them up.
115
- if ((msg as { type: string }).type === "host_browser_request") {
116
- const requestId = (msg as { requestId: string }).requestId;
117
- pendingInteractions.register(requestId, {
118
- conversation,
119
- conversationId,
120
- kind: "host_browser",
121
- });
122
- }
123
- const ok = getChromeExtensionRegistry().send(guardianId, msg);
124
- if (!ok) {
125
- throw new Error(
126
- `chrome-extension host_browser send failed: no active connection for guardian ${guardianId}`,
127
- );
128
- }
129
- };
130
-
131
- const proxy = new HostBrowserProxy(sendToClient);
132
- proxyRef = proxy;
133
- return { proxy, conversation };
134
- }
135
-
136
77
  /**
137
78
  * Mint an actor-bound JWT for the given guardianId. The WebSocket
138
79
  * upgrade handler parses `sub=actor:<assistantId>:<actorPrincipalId>`
@@ -156,13 +97,10 @@ describe("host_browser cloud-hosted e2e round-trip", () => {
156
97
  let runtimeBaseUrl: string;
157
98
 
158
99
  beforeEach(async () => {
159
- // Each test gets a clean DB and a fresh registry so connection
160
- // state doesn't leak between cases.
161
100
  const db = getDb();
162
101
  db.run("DELETE FROM contact_channels");
163
102
  db.run("DELETE FROM contacts");
164
- pendingInteractions.clear();
165
- __resetChromeExtensionRegistryForTests();
103
+ HostBrowserProxy.reset();
166
104
 
167
105
  port = 19800 + Math.floor(Math.random() * 200);
168
106
  runtimeBaseUrl = `http://127.0.0.1:${port}`;
@@ -172,17 +110,13 @@ describe("host_browser cloud-hosted e2e round-trip", () => {
172
110
 
173
111
  afterEach(async () => {
174
112
  await server?.stop();
175
- pendingInteractions.clear();
176
- __resetChromeExtensionRegistryForTests();
113
+ HostBrowserProxy.reset();
177
114
  });
178
115
 
179
116
  test("happy path: Browser.getVersion round-trips through the mock extension", async () => {
180
117
  const guardianId = `test-guardian-${crypto.randomUUID()}`;
181
118
  const token = mintActorToken(guardianId);
182
119
 
183
- // Dynamic import keeps the module cache warm across tests but avoids
184
- // binding the fixture at file-load time (where the mocks might not
185
- // yet have applied for a freshly forked test worker).
186
120
  const { createMockChromeExtension } =
187
121
  await import("./fixtures/mock-chrome-extension.js");
188
122
  const mockExt = createMockChromeExtension({
@@ -191,13 +125,9 @@ describe("host_browser cloud-hosted e2e round-trip", () => {
191
125
  });
192
126
  await mockExt.start();
193
127
  await mockExt.waitForConnection();
194
-
195
- // Give the open handler a tick to register the connection in the
196
- // ChromeExtensionRegistry (Bun's WebSocket open callback fires
197
- // asynchronously after the upgrade handler returns).
198
128
  await waitForRegistryEntry(guardianId);
199
129
 
200
- const { proxy } = createBoundProxy(guardianId, "conv-happy");
130
+ const proxy = HostBrowserProxy.instance;
201
131
 
202
132
  const result = await proxy.request(
203
133
  { cdpMethod: "Browser.getVersion" },
@@ -213,7 +143,6 @@ describe("host_browser cloud-hosted e2e round-trip", () => {
213
143
  expect(typeof received[0].requestId).toBe("string");
214
144
  expect(received[0].conversationId).toBe("conv-happy");
215
145
 
216
- proxy.dispose();
217
146
  await mockExt.stop();
218
147
  });
219
148
 
@@ -223,10 +152,6 @@ describe("host_browser cloud-hosted e2e round-trip", () => {
223
152
 
224
153
  const { createMockChromeExtension } =
225
154
  await import("./fixtures/mock-chrome-extension.js");
226
- // Same fixture as the HTTP happy path, but configured to return
227
- // results over the /v1/browser-relay WebSocket instead of POSTing
228
- // /v1/host-browser-result. This exercises the runtime WS
229
- // `message` handler's host_browser_result dispatch path.
230
155
  const mockExt = createMockChromeExtension({
231
156
  runtimeBaseUrl,
232
157
  token,
@@ -236,7 +161,7 @@ describe("host_browser cloud-hosted e2e round-trip", () => {
236
161
  await mockExt.waitForConnection();
237
162
  await waitForRegistryEntry(guardianId);
238
163
 
239
- const { proxy } = createBoundProxy(guardianId, "conv-happy-ws");
164
+ const proxy = HostBrowserProxy.instance;
240
165
 
241
166
  const result = await proxy.request(
242
167
  { cdpMethod: "Browser.getVersion" },
@@ -251,12 +176,8 @@ describe("host_browser cloud-hosted e2e round-trip", () => {
251
176
  expect(received[0].cdpMethod).toBe("Browser.getVersion");
252
177
  expect(received[0].conversationId).toBe("conv-happy-ws");
253
178
 
254
- // The pending interaction must be fully consumed — if the WS
255
- // handler silently no-op'd, the entry would still be registered
256
- // after the proxy resolves.
257
- expect(pendingInteractions.get(received[0].requestId)).toBeUndefined();
179
+ expect(proxy.hasPendingRequest(received[0].requestId)).toBe(false);
258
180
 
259
- proxy.dispose();
260
181
  await mockExt.stop();
261
182
  });
262
183
 
@@ -269,15 +190,13 @@ describe("host_browser cloud-hosted e2e round-trip", () => {
269
190
  const mockExt = createMockChromeExtension({
270
191
  runtimeBaseUrl,
271
192
  token,
272
- // Hang forever so we can abort mid-flight without a race against
273
- // the default handler's immediate response.
274
193
  cdpHandler: () => new Promise(() => {}),
275
194
  });
276
195
  await mockExt.start();
277
196
  await mockExt.waitForConnection();
278
197
  await waitForRegistryEntry(guardianId);
279
198
 
280
- const { proxy } = createBoundProxy(guardianId, "conv-abort");
199
+ const proxy = HostBrowserProxy.instance;
281
200
 
282
201
  const controller = new AbortController();
283
202
  const resultPromise = proxy.request(
@@ -286,8 +205,6 @@ describe("host_browser cloud-hosted e2e round-trip", () => {
286
205
  controller.signal,
287
206
  );
288
207
 
289
- // Wait for the mock extension to observe the request, then abort so
290
- // the cancel envelope has somewhere to land.
291
208
  await waitFor(() => mockExt.receivedRequests().length === 1);
292
209
 
293
210
  controller.abort();
@@ -296,33 +213,15 @@ describe("host_browser cloud-hosted e2e round-trip", () => {
296
213
  expect(result.content).toBe("Aborted");
297
214
  expect(result.isError).toBe(true);
298
215
 
299
- // The cancel frame is dispatched synchronously from the abort
300
- // listener, but the WebSocket delivers it asynchronously — give it a
301
- // few turns to arrive before asserting.
302
216
  await waitFor(() => mockExt.receivedCancels().length === 1);
303
217
  const cancels = mockExt.receivedCancels();
304
218
  expect(cancels).toHaveLength(1);
305
219
  expect(cancels[0].requestId).toBe(mockExt.receivedRequests()[0].requestId);
306
220
 
307
- proxy.dispose();
308
221
  await mockExt.stop();
309
222
  });
310
223
 
311
224
  test("abort: late /v1/host-browser-result POST after cancel is ignored (no ghost completion)", async () => {
312
- // The daemon-side proxy must treat a late result POST — arriving
313
- // after the caller has already been resolved with "Aborted" —
314
- // as a benign race, not a noisy false-positive timeout. It must
315
- // also NOT resolve the caller a second time.
316
- //
317
- // We exercise this from the daemon's perspective by:
318
- // 1. Starting a request with an AbortSignal.
319
- // 2. Aborting the signal so the proxy resolves with "Aborted".
320
- // 3. Manually POSTing a host_browser_result for the same
321
- // requestId straight to /v1/host-browser-result (bypassing
322
- // the compliant dispatcher's cancel-suppression).
323
- // 4. Verifying the POST is accepted by the runtime (i.e. the
324
- // HTTP layer doesn't explode) and the caller's promise
325
- // never fulfils twice.
326
225
  const guardianId = `test-guardian-${crypto.randomUUID()}`;
327
226
  const token = mintActorToken(guardianId);
328
227
 
@@ -331,15 +230,13 @@ describe("host_browser cloud-hosted e2e round-trip", () => {
331
230
  const mockExt = createMockChromeExtension({
332
231
  runtimeBaseUrl,
333
232
  token,
334
- // Hang forever — same gating trick as the plain abort test,
335
- // so we can cancel before the handler returns anything.
336
233
  cdpHandler: () => new Promise(() => {}),
337
234
  });
338
235
  await mockExt.start();
339
236
  await mockExt.waitForConnection();
340
237
  await waitForRegistryEntry(guardianId);
341
238
 
342
- const { proxy } = createBoundProxy(guardianId, "conv-abort-late");
239
+ const proxy = HostBrowserProxy.instance;
343
240
 
344
241
  let resolveCount = 0;
345
242
  const controller = new AbortController();
@@ -363,10 +260,10 @@ describe("host_browser cloud-hosted e2e round-trip", () => {
363
260
  expect(result.isError).toBe(true);
364
261
  expect(resolveCount).toBe(1);
365
262
 
366
- // Now manually submit a late result for the same requestId —
263
+ // Manually submit a late result for the same requestId —
367
264
  // simulating a non-compliant client that failed to honour the
368
- // cancel envelope. The runtime must accept the POST without
369
- // error and the proxy must NOT resolve the caller a second time.
265
+ // cancel envelope. The runtime must accept the POST without error
266
+ // and the proxy must NOT resolve the caller a second time.
370
267
  const lateResp = await fetch(`${runtimeBaseUrl}/v1/host-browser-result`, {
371
268
  method: "POST",
372
269
  headers: {
@@ -381,13 +278,9 @@ describe("host_browser cloud-hosted e2e round-trip", () => {
381
278
  });
382
279
  await lateResp.body?.cancel();
383
280
 
384
- // Give the runtime a few turns to process the POST and hit its
385
- // "no pending request" debug branch. If the proxy resolved a
386
- // second time, `resolveCount` would be 2 here.
387
281
  await new Promise((r) => setTimeout(r, 20));
388
282
  expect(resolveCount).toBe(1);
389
283
 
390
- proxy.dispose();
391
284
  await mockExt.stop();
392
285
  });
393
286
 
@@ -397,10 +290,6 @@ describe("host_browser cloud-hosted e2e round-trip", () => {
397
290
 
398
291
  const { createMockChromeExtension } =
399
292
  await import("./fixtures/mock-chrome-extension.js");
400
- // CDP handler that never resolves — the request frame reaches the
401
- // mock extension successfully, but no result is ever POSTed back.
402
- // This exercises the proxy's `setTimeout` path (as opposed to a
403
- // synchronous send failure, which is a separate code path).
404
293
  const mockExt = createMockChromeExtension({
405
294
  runtimeBaseUrl,
406
295
  token,
@@ -410,51 +299,28 @@ describe("host_browser cloud-hosted e2e round-trip", () => {
410
299
  await mockExt.waitForConnection();
411
300
  await waitForRegistryEntry(guardianId);
412
301
 
413
- const { proxy } = createBoundProxy(guardianId, "conv-timeout");
302
+ const proxy = HostBrowserProxy.instance;
414
303
 
415
- // 50ms timeout — short enough to keep the test fast, long enough
416
- // for the request frame to make the WS round-trip to the mock
417
- // extension before the timer fires.
418
- const result = await proxy.request(
419
- { cdpMethod: "Browser.getVersion", timeout_seconds: 0.05 },
304
+ const resultPromise = proxy.request(
305
+ { cdpMethod: "Browser.getVersion", timeout_seconds: 0.5 },
420
306
  "conv-timeout",
421
307
  );
422
308
 
309
+ await waitFor(() => mockExt.receivedRequests().length === 1);
310
+
311
+ const result = await resultPromise;
312
+
423
313
  expect(result.isError).toBe(true);
424
314
  expect(result.content).toContain("timed out");
425
315
 
426
- // Sanity check: the frame actually reached the mock extension (so
427
- // we know we're exercising the proxy's timer, not a send failure).
428
316
  expect(mockExt.receivedRequests()).toHaveLength(1);
429
317
  expect(mockExt.receivedRequests()[0].cdpMethod).toBe("Browser.getVersion");
430
318
 
431
- proxy.dispose();
432
319
  await mockExt.stop();
433
320
  });
434
321
  });
435
322
 
436
323
  // ── macOS message ingress with connected extension ──────────────────
437
- //
438
- // Verifies the end-to-end path for macOS-originated turns when the user
439
- // has the chrome extension connected. On macOS, browser commands should
440
- // route through the registry-backed host browser flow (extension → user's
441
- // real Chrome session) rather than falling back to local Playwright.
442
- //
443
- // The macOS browser backend preference order is:
444
- //
445
- // macOS + extension connected → extension backend (registry-routed)
446
- // macOS + extension absent → cdp-inspect (desktop-auto) → local
447
- //
448
- // NOTE: These tests construct a HostBrowserProxy directly and call
449
- // proxy.request(), which validates the extension relay round-trip but
450
- // bypasses handleSendMessage / conversation-routes. Full ingress-path
451
- // coverage (interface propagation, resolveHostBrowserSender wiring, and
452
- // CDP factory candidate selection) is exercised by the route-level tests
453
- // in conversation-routes-disk-view.test.ts.
454
- //
455
- // If future refactors break the wiring between conversation-routes
456
- // (`resolveHostBrowserSender`) and the CDP factory's candidate list, those
457
- // route-level tests will fail.
458
324
 
459
325
  describe("macOS message ingress with connected extension", () => {
460
326
  let server: RuntimeHttpServer;
@@ -465,8 +331,7 @@ describe("macOS message ingress with connected extension", () => {
465
331
  const db = getDb();
466
332
  db.run("DELETE FROM contact_channels");
467
333
  db.run("DELETE FROM contacts");
468
- pendingInteractions.clear();
469
- __resetChromeExtensionRegistryForTests();
334
+ HostBrowserProxy.reset();
470
335
 
471
336
  port = 20000 + Math.floor(Math.random() * 200);
472
337
  runtimeBaseUrl = `http://127.0.0.1:${port}`;
@@ -476,12 +341,10 @@ describe("macOS message ingress with connected extension", () => {
476
341
 
477
342
  afterEach(async () => {
478
343
  await server?.stop();
479
- pendingInteractions.clear();
480
- __resetChromeExtensionRegistryForTests();
344
+ HostBrowserProxy.reset();
481
345
  });
482
346
 
483
- test("macOS turn routes Browser.getVersion through the registry-backed extension, not local Playwright", async () => {
484
- // Arrange: connect a mock extension for a given guardianId.
347
+ test("macOS turn routes Browser.getVersion through the registry-backed extension", async () => {
485
348
  const guardianId = `test-guardian-macos-${crypto.randomUUID()}`;
486
349
  const token = mintActorToken(guardianId);
487
350
 
@@ -495,20 +358,13 @@ describe("macOS message ingress with connected extension", () => {
495
358
  await mockExt.waitForConnection();
496
359
  await waitForRegistryEntry(guardianId);
497
360
 
498
- // Build a proxy bound to the guardian's extension connection, mimicking
499
- // the wiring that conversation-routes.ts performs for macOS turns when
500
- // the ChromeExtensionRegistry has an active entry for the guardian.
501
- const { proxy } = createBoundProxy(guardianId, "conv-macos-ext");
361
+ const proxy = HostBrowserProxy.instance;
502
362
 
503
- // Act: issue a CDP command through the proxy (same as how browser tools
504
- // dispatch commands during a macOS turn with extension override).
505
363
  const result = await proxy.request(
506
364
  { cdpMethod: "Browser.getVersion" },
507
365
  "conv-macos-ext",
508
366
  );
509
367
 
510
- // Assert: the command reached the mock extension (not local Playwright)
511
- // and the round-trip completed successfully.
512
368
  expect(result.isError).toBe(false);
513
369
  expect(result.content).toContain("Chrome/MockTest");
514
370
 
@@ -517,12 +373,10 @@ describe("macOS message ingress with connected extension", () => {
517
373
  expect(received[0].cdpMethod).toBe("Browser.getVersion");
518
374
  expect(received[0].conversationId).toBe("conv-macos-ext");
519
375
 
520
- proxy.dispose();
521
376
  await mockExt.stop();
522
377
  });
523
378
 
524
- test("macOS turn with extension disconnected mid-conversation does not hang (proxy detects unavailability)", async () => {
525
- // Arrange: connect a mock extension then forcibly disconnect it.
379
+ test("macOS turn with extension disconnected mid-conversation rejects (proxy detects unavailability)", async () => {
526
380
  const guardianId = `test-guardian-macos-disco-${crypto.randomUUID()}`;
527
381
  const token = mintActorToken(guardianId);
528
382
 
@@ -536,346 +390,30 @@ describe("macOS message ingress with connected extension", () => {
536
390
  await mockExt.waitForConnection();
537
391
  await waitForRegistryEntry(guardianId);
538
392
 
539
- // The proxy is bound while the extension is still connected.
540
- const { proxy } = createBoundProxy(guardianId, "conv-macos-disco");
541
-
542
- // Disconnect the extension before sending any commands.
543
393
  mockExt.forceDisconnect();
544
394
 
545
- // Wait for the registry to notice the close event.
546
395
  await waitFor(
547
- () => getChromeExtensionRegistry().get(guardianId) === undefined,
396
+ () =>
397
+ assistantEventHub.getMostRecentClientByCapability("host_browser") ==
398
+ null,
548
399
  );
549
400
 
550
- // Act: attempt a CDP command through the proxy. The registry send should
551
- // fail because the connection is gone, and the proxy's sendToClient
552
- // wrapper throws immediately.
401
+ const proxy = HostBrowserProxy.instance;
402
+
553
403
  try {
554
404
  await proxy.request(
555
405
  { cdpMethod: "Browser.getVersion", timeout_seconds: 0.5 },
556
406
  "conv-macos-disco",
557
407
  );
558
- // If we reach here, the test should still verify the result indicates
559
- // an error rather than a successful extension round-trip.
560
408
  expect(true).toBe(false); // Should not reach here
561
409
  } catch {
562
410
  // Expected: the send failed because the extension is disconnected.
563
- // This confirms the macOS path detects disconnection rather than
564
- // silently routing to the wrong backend.
565
411
  }
566
412
 
567
- proxy.dispose();
568
413
  await mockExt.stop();
569
414
  });
570
415
  });
571
416
 
572
- // ── macOS SSE bridge ingress (no extension registry) ────────────────
573
- //
574
- // Exercises the cloud-hosted + desktop SSE bridge path for macOS turns
575
- // WITHOUT relying on the ChromeExtensionRegistry. This validates the
576
- // native macOS host-browser proxy path where `host_browser_request`
577
- // frames travel through `assistantEventHub` (SSE) rather than the
578
- // extension WebSocket.
579
- //
580
- // In production, this path is used when:
581
- // - The macOS desktop client is connected to the assistant via SSE
582
- // - The user does NOT have the Chrome extension installed
583
- // - The desktop client receives `host_browser_request` frames via SSE,
584
- // executes CDP commands against the local Chrome, and POSTs results
585
- // back to `/v1/host-browser-result`
586
- //
587
- // The test constructs a HostBrowserProxy wired to a mock SSE sender
588
- // (simulating the `onEvent` hub publisher) and a mock macOS client that
589
- // observes the sent frames and returns results via POST.
590
-
591
- describe("macOS SSE bridge ingress (no extension registry)", () => {
592
- let server: RuntimeHttpServer;
593
- let port: number;
594
- let runtimeBaseUrl: string;
595
-
596
- beforeEach(async () => {
597
- const db = getDb();
598
- db.run("DELETE FROM contact_channels");
599
- db.run("DELETE FROM contacts");
600
- pendingInteractions.clear();
601
- __resetChromeExtensionRegistryForTests();
602
-
603
- port = 20200 + Math.floor(Math.random() * 200);
604
- runtimeBaseUrl = `http://127.0.0.1:${port}`;
605
- server = new RuntimeHttpServer({ port });
606
- await server.start();
607
- });
608
-
609
- afterEach(async () => {
610
- await server?.stop();
611
- pendingInteractions.clear();
612
- __resetChromeExtensionRegistryForTests();
613
- });
614
-
615
- /**
616
- * Create a HostBrowserProxy wired to a mock SSE sender. The sender
617
- * captures `host_browser_request` frames and simulates what the macOS
618
- * desktop client does: execute the CDP command locally and POST the
619
- * result back to `/v1/host-browser-result`.
620
- *
621
- * Unlike `createBoundProxy` (which routes through the extension
622
- * registry), this helper routes through a direct function call —
623
- * simulating the `onEvent` SSE hub publisher path.
624
- */
625
- function createSseBoundProxy(
626
- conversationId: string,
627
- token: string,
628
- ): {
629
- proxy: HostBrowserProxy;
630
- conversation: Conversation;
631
- sentFrames: Array<{ type: string; [key: string]: unknown }>;
632
- } {
633
- let proxyRef: HostBrowserProxy | null = null;
634
- const conversation = {
635
- resolveHostBrowser(
636
- requestId: string,
637
- response: { content: string; isError: boolean },
638
- ) {
639
- proxyRef?.resolve(requestId, response);
640
- },
641
- } as unknown as Conversation;
642
-
643
- const sentFrames: Array<{ type: string; [key: string]: unknown }> = [];
644
-
645
- // The SSE sender simulates what `assistantEventHub.publish` does in
646
- // production: it delivers the message to the connected SSE client.
647
- // Here we capture the frame and immediately simulate the macOS client
648
- // handling it — executing a mock CDP command and POSTing the result
649
- // back to the runtime.
650
- const sseSender = (msg: ServerMessage) => {
651
- const frame = msg as { type: string; [key: string]: unknown };
652
- sentFrames.push(frame);
653
-
654
- if (frame.type === "host_browser_request") {
655
- const requestId = frame.requestId as string;
656
-
657
- // Register the pending interaction (in production this happens
658
- // in makeHubPublisher inside conversation-routes.ts).
659
- pendingInteractions.register(requestId, {
660
- conversation,
661
- conversationId,
662
- kind: "host_browser",
663
- });
664
-
665
- // Simulate the macOS desktop client processing the CDP command
666
- // and POSTing the result back to the runtime.
667
- const cdpMethod = frame.cdpMethod as string;
668
- let content: string;
669
- let isError = false;
670
- if (cdpMethod === "Browser.getVersion") {
671
- content = JSON.stringify({
672
- product: "Chrome/macOS-SSE-Test",
673
- protocolVersion: "1.3",
674
- revision: "@macos-sse",
675
- userAgent: "Mozilla/5.0 (macOS SSE bridge e2e fixture)",
676
- jsVersion: "0.0.0-macos-sse",
677
- });
678
- } else if (cdpMethod === "Runtime.evaluate") {
679
- content = JSON.stringify({ result: { value: "complete" } });
680
- } else {
681
- content = `mock macOS client: unsupported cdpMethod "${cdpMethod}"`;
682
- isError = true;
683
- }
684
-
685
- // POST result asynchronously (simulating the real macOS client).
686
- void fetch(`${runtimeBaseUrl}/v1/host-browser-result`, {
687
- method: "POST",
688
- headers: {
689
- "Content-Type": "application/json",
690
- Authorization: `Bearer ${token}`,
691
- },
692
- body: JSON.stringify({ requestId, content, isError }),
693
- })
694
- .then((res) => res.body?.cancel())
695
- .catch(() => {});
696
- }
697
- };
698
-
699
- const proxy = new HostBrowserProxy(sseSender);
700
- proxyRef = proxy;
701
- return { proxy, conversation, sentFrames };
702
- }
703
-
704
- test("happy path: Browser.getVersion round-trips through the SSE bridge without extension registry", async () => {
705
- const guardianId = `test-guardian-macos-sse-${crypto.randomUUID()}`;
706
- const token = mintActorToken(guardianId);
707
-
708
- const { proxy, sentFrames } = createSseBoundProxy(
709
- "conv-macos-sse-happy",
710
- token,
711
- );
712
-
713
- const result = await proxy.request(
714
- { cdpMethod: "Browser.getVersion" },
715
- "conv-macos-sse-happy",
716
- );
717
-
718
- // The request completed via the SSE bridge path, not the extension registry.
719
- expect(result.isError).toBe(false);
720
- expect(result.content).toContain("Chrome/macOS-SSE-Test");
721
-
722
- // The SSE sender received exactly one host_browser_request frame.
723
- const requests = sentFrames.filter(
724
- (f) => f.type === "host_browser_request",
725
- );
726
- expect(requests).toHaveLength(1);
727
- expect(requests[0].cdpMethod).toBe("Browser.getVersion");
728
- expect(requests[0].conversationId).toBe("conv-macos-sse-happy");
729
-
730
- // The extension registry should NOT have been involved — no entries exist.
731
- expect(getChromeExtensionRegistry().get(guardianId)).toBeUndefined();
732
-
733
- proxy.dispose();
734
- });
735
-
736
- test("abort: SSE-bridged request resolves to 'Aborted' when signal fires", async () => {
737
- const guardianId = `test-guardian-macos-sse-abort-${crypto.randomUUID()}`;
738
- const _token = mintActorToken(guardianId);
739
-
740
- // Use a CDP handler that hangs forever so we can abort mid-flight.
741
- const sentFrames: Array<{ type: string; [key: string]: unknown }> = [];
742
- let proxyRef: HostBrowserProxy | null = null;
743
- const conversation = {
744
- resolveHostBrowser(
745
- requestId: string,
746
- response: { content: string; isError: boolean },
747
- ) {
748
- proxyRef?.resolve(requestId, response);
749
- },
750
- } as unknown as Conversation;
751
-
752
- const hangingSender = (msg: ServerMessage) => {
753
- const frame = msg as { type: string; [key: string]: unknown };
754
- sentFrames.push(frame);
755
- if (frame.type === "host_browser_request") {
756
- const requestId = frame.requestId as string;
757
- pendingInteractions.register(requestId, {
758
- conversation,
759
- conversationId: "conv-macos-sse-abort",
760
- kind: "host_browser",
761
- });
762
- // Simulate a macOS client that never responds (hangs).
763
- }
764
- };
765
-
766
- const proxy = new HostBrowserProxy(hangingSender);
767
- proxyRef = proxy;
768
-
769
- const controller = new AbortController();
770
- const resultPromise = proxy.request(
771
- { cdpMethod: "Browser.getVersion" },
772
- "conv-macos-sse-abort",
773
- controller.signal,
774
- );
775
-
776
- // Wait for the SSE sender to observe the request.
777
- await waitFor(() => sentFrames.length === 1);
778
-
779
- controller.abort();
780
- const result = await resultPromise;
781
-
782
- expect(result.content).toBe("Aborted");
783
- expect(result.isError).toBe(true);
784
-
785
- // The cancel frame should have been sent through the SSE sender.
786
- const cancels = sentFrames.filter((f) => f.type === "host_browser_cancel");
787
- expect(cancels).toHaveLength(1);
788
-
789
- proxy.dispose();
790
- });
791
-
792
- test("timeout: SSE-bridged request surfaces timeout when macOS client never responds", async () => {
793
- const guardianId = `test-guardian-macos-sse-timeout-${crypto.randomUUID()}`;
794
- const _token = mintActorToken(guardianId);
795
-
796
- const sentFrames: Array<{ type: string; [key: string]: unknown }> = [];
797
- let proxyRef: HostBrowserProxy | null = null;
798
- const conversation = {
799
- resolveHostBrowser(
800
- requestId: string,
801
- response: { content: string; isError: boolean },
802
- ) {
803
- proxyRef?.resolve(requestId, response);
804
- },
805
- } as unknown as Conversation;
806
-
807
- const hangingSender = (msg: ServerMessage) => {
808
- const frame = msg as { type: string; [key: string]: unknown };
809
- sentFrames.push(frame);
810
- if (frame.type === "host_browser_request") {
811
- const requestId = frame.requestId as string;
812
- pendingInteractions.register(requestId, {
813
- conversation,
814
- conversationId: "conv-macos-sse-timeout",
815
- kind: "host_browser",
816
- });
817
- // Never respond — simulate unresponsive macOS client.
818
- }
819
- };
820
-
821
- const proxy = new HostBrowserProxy(hangingSender);
822
- proxyRef = proxy;
823
-
824
- const result = await proxy.request(
825
- { cdpMethod: "Browser.getVersion", timeout_seconds: 0.05 },
826
- "conv-macos-sse-timeout",
827
- );
828
-
829
- expect(result.isError).toBe(true);
830
- expect(result.content).toContain("timed out");
831
-
832
- // The SSE sender received the request frame (confirming the timeout
833
- // is from the proxy timer, not a send failure).
834
- const requests = sentFrames.filter(
835
- (f) => f.type === "host_browser_request",
836
- );
837
- expect(requests).toHaveLength(1);
838
- expect(requests[0].cdpMethod).toBe("Browser.getVersion");
839
-
840
- proxy.dispose();
841
- });
842
-
843
- test("multiple sequential commands round-trip through the SSE bridge", async () => {
844
- const guardianId = `test-guardian-macos-sse-seq-${crypto.randomUUID()}`;
845
- const token = mintActorToken(guardianId);
846
-
847
- const { proxy, sentFrames } = createSseBoundProxy(
848
- "conv-macos-sse-seq",
849
- token,
850
- );
851
-
852
- // First command: Browser.getVersion
853
- const result1 = await proxy.request(
854
- { cdpMethod: "Browser.getVersion" },
855
- "conv-macos-sse-seq",
856
- );
857
- expect(result1.isError).toBe(false);
858
- expect(result1.content).toContain("Chrome/macOS-SSE-Test");
859
-
860
- // Second command: Runtime.evaluate
861
- const result2 = await proxy.request(
862
- { cdpMethod: "Runtime.evaluate", cdpParams: { expression: "1+1" } },
863
- "conv-macos-sse-seq",
864
- );
865
- expect(result2.isError).toBe(false);
866
-
867
- // Both requests went through the SSE sender.
868
- const requests = sentFrames.filter(
869
- (f) => f.type === "host_browser_request",
870
- );
871
- expect(requests).toHaveLength(2);
872
- expect(requests[0].cdpMethod).toBe("Browser.getVersion");
873
- expect(requests[1].cdpMethod).toBe("Runtime.evaluate");
874
-
875
- proxy.dispose();
876
- });
877
- });
878
-
879
417
  // ── Local wait helpers ──────────────────────────────────────────────
880
418
 
881
419
  async function waitFor(
@@ -894,11 +432,12 @@ async function waitFor(
894
432
  }
895
433
 
896
434
  async function waitForRegistryEntry(
897
- guardianId: string,
435
+ _guardianId: string,
898
436
  timeoutMs = 2000,
899
437
  ): Promise<void> {
900
438
  await waitFor(
901
- () => getChromeExtensionRegistry().get(guardianId) !== undefined,
439
+ () =>
440
+ assistantEventHub.getMostRecentClientByCapability("host_browser") != null,
902
441
  timeoutMs,
903
442
  );
904
443
  }