@vellumai/assistant 0.11.4-staging.1 → 0.11.4-staging.3

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 (205) hide show
  1. package/AGENTS.md +8 -2
  2. package/ARCHITECTURE.md +2 -0
  3. package/docs/architecture/memory.md +15 -0
  4. package/docs/browser-use-architecture-phase2.md +128 -56
  5. package/docs/flux-turn-detection-spike.md +11 -6
  6. package/knip.json +3 -0
  7. package/openapi.yaml +135 -75
  8. package/package.json +1 -1
  9. package/scripts/write-plugin-api-shim.ts +10 -0
  10. package/src/__tests__/app-control-flow.test.ts +1 -0
  11. package/src/__tests__/approval-routes-http.test.ts +2 -2
  12. package/src/__tests__/assistant-feature-flag-guard.test.ts +25 -3
  13. package/src/__tests__/byok-default-profile-ensure.test.ts +15 -0
  14. package/src/__tests__/channel-setup-panel-ack.test.ts +1 -1
  15. package/src/__tests__/compaction-events.test.ts +8 -10
  16. package/src/__tests__/conversation-agent-loop.test.ts +4 -1
  17. package/src/__tests__/conversation-confirmation-signals.test.ts +112 -0
  18. package/src/__tests__/conversation-notifiers-provenance.test.ts +1 -1
  19. package/src/__tests__/conversation-queue.test.ts +99 -0
  20. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
  21. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
  22. package/src/__tests__/conversation-routes-guardian-reply.test.ts +9 -9
  23. package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
  24. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
  25. package/src/__tests__/conversation-slash-queue.test.ts +3 -0
  26. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
  27. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -0
  28. package/src/__tests__/conversation-surfaces-app-control.test.ts +1 -0
  29. package/src/__tests__/conversation-surfaces-app-open.test.ts +1 -1
  30. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -1
  31. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +21 -14
  32. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +1 -0
  33. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1 -0
  34. package/src/__tests__/conversation-surfaces-standalone.test.ts +1 -0
  35. package/src/__tests__/conversation-surfaces-state-update.test.ts +1 -1
  36. package/src/__tests__/conversation-surfaces-table-action.test.ts +1 -1
  37. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -1
  38. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -1
  39. package/src/__tests__/conversation-tool-setup-attribution.test.ts +1 -1
  40. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  41. package/src/__tests__/document-sync-tags.test.ts +0 -75
  42. package/src/__tests__/gateway-only-guard.test.ts +2 -5
  43. package/src/__tests__/http-user-message-parity.test.ts +1 -1
  44. package/src/__tests__/init-feature-flag-overrides.test.ts +49 -0
  45. package/src/__tests__/managed-skill-lifecycle.test.ts +7 -0
  46. package/src/__tests__/media-generate-image.test.ts +131 -21
  47. package/src/__tests__/memory-retrieval-hook.test.ts +94 -2
  48. package/src/__tests__/plugin-api-webhook-url.test.ts +10 -7
  49. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +7 -3
  50. package/src/__tests__/proxy-approval-callback.test.ts +1 -0
  51. package/src/__tests__/qdrant-manager.test.ts +14 -1
  52. package/src/__tests__/run-due-schedules.test.ts +21 -0
  53. package/src/__tests__/scaffold-managed-skill-tool.test.ts +187 -18
  54. package/src/__tests__/schedule-routes.test.ts +23 -0
  55. package/src/__tests__/schedule-store.test.ts +17 -0
  56. package/src/__tests__/secret-ingress-http.test.ts +1 -1
  57. package/src/__tests__/send-endpoint-busy.test.ts +3 -3
  58. package/src/__tests__/starter-task-flow.test.ts +5 -4
  59. package/src/__tests__/subagent-fork-prompt-role.test.ts +1 -1
  60. package/src/__tests__/subagent-spawn-and-await.test.ts +4 -7
  61. package/src/__tests__/subagent-tool-gate-mode.test.ts +1 -1
  62. package/src/__tests__/subagent-tools.test.ts +81 -101
  63. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +1 -0
  64. package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
  65. package/src/__tests__/ui-shape-teaching.test.ts +33 -0
  66. package/src/__tests__/ui-visual-surface.test.ts +1 -1
  67. package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
  68. package/src/__tests__/ui-work-result-surface.test.ts +1 -1
  69. package/src/__tests__/voice-config-update.test.ts +40 -0
  70. package/src/__tests__/voice-scoped-grant-consumer.test.ts +5 -3
  71. package/src/__tests__/voice-session-bridge.test.ts +85 -29
  72. package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +235 -0
  73. package/src/acp/session-manager.ts +8 -1
  74. package/src/api/surfaces.ts +12 -3
  75. package/src/calls/__tests__/voice-session-bridge.test.ts +21 -10
  76. package/src/calls/__tests__/voice-triage-escalate.test.ts +8 -0
  77. package/src/calls/call-controller.ts +16 -7
  78. package/src/calls/voice-session-bridge.ts +44 -25
  79. package/src/calls/voice-triage-escalate.ts +1 -0
  80. package/src/cli/bundled-modules.ts +29 -0
  81. package/src/cli/commands/db/repair.ts +4 -8
  82. package/src/cli/commands/domain.ts +6 -3
  83. package/src/cli/commands/email.ts +6 -3
  84. package/src/cli/commands/keys.ts +8 -3
  85. package/src/cli/commands/plugins.ts +85 -36
  86. package/src/cli/commands/schedules.ts +35 -1
  87. package/src/cli/lib/bundled-marketplace.json +1 -1
  88. package/src/config/__tests__/balanced-model-experiment.test.ts +278 -0
  89. package/src/config/assistant-feature-flags.ts +36 -15
  90. package/src/config/balanced-model-experiment.ts +35 -0
  91. package/src/config/bundled-skills/image-studio/SKILL.md +5 -4
  92. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -1
  93. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +101 -0
  94. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.test.ts +65 -0
  95. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +7 -1
  96. package/src/config/bundled-skills/settings/tools/shared.ts +16 -0
  97. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -1
  98. package/src/config/bundled-skills/skill-management/TOOLS.json +9 -3
  99. package/src/config/bundled-skills/subagent/SKILL.md +17 -12
  100. package/src/config/bundled-skills/subagent/TOOLS.json +4 -4
  101. package/src/config/call-site-defaults.ts +7 -0
  102. package/src/config/default-profile-catalog.ts +97 -5
  103. package/src/config/feature-flag-registry.json +11 -11
  104. package/src/config/skills.ts +9 -2
  105. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
  106. package/src/daemon/conversation-agent-loop.ts +14 -13
  107. package/src/daemon/conversation-notifiers.ts +11 -9
  108. package/src/daemon/conversation-store.ts +4 -4
  109. package/src/daemon/conversation-surfaces.ts +54 -18
  110. package/src/daemon/conversation-tool-setup.ts +2 -4
  111. package/src/daemon/conversation.ts +81 -44
  112. package/src/daemon/doordash-steps.ts +2 -2
  113. package/src/daemon/lifecycle.ts +14 -1
  114. package/src/daemon/process-message.ts +0 -23
  115. package/src/daemon/windows-compiled-entry.ts +4 -0
  116. package/src/documents/document-store.ts +5 -235
  117. package/src/hooks/types.ts +5 -0
  118. package/src/ipc/gateway-flag-listener.ts +17 -3
  119. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +118 -0
  120. package/src/live-voice/live-voice-manager.ts +16 -3
  121. package/src/live-voice/live-voice-session.ts +63 -9
  122. package/src/live-voice/windows-compiled-live-voice.ts +4 -0
  123. package/src/monitoring/control.ts +1 -0
  124. package/src/monitoring/db-integrity-sample.ts +4 -5
  125. package/src/permissions/prompter.ts +1 -5
  126. package/src/persistence/conversation-queries.ts +66 -16
  127. package/src/persistence/embeddings/qdrant-manager.ts +84 -49
  128. package/src/persistence/migrations/360-add-document-workspace-path.ts +5 -14
  129. package/src/persistence/schema/documents.ts +4 -4
  130. package/src/plugin-api/constants.ts +8 -0
  131. package/src/plugin-api/index.ts +5 -1
  132. package/src/plugin-api/vision-support.test.ts +1 -1
  133. package/src/plugin-api/webhook-url.ts +13 -11
  134. package/src/plugins/defaults/main.ts +6 -7
  135. package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +77 -0
  136. package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +24 -6
  137. package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +53 -5
  138. package/src/plugins/defaults/memory/memory-retrospective-job.ts +4 -4
  139. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -0
  140. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +66 -1
  141. package/src/plugins/defaults/memory/v3/injector.ts +8 -0
  142. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +23 -9
  143. package/src/plugins/defaults/memory/worker-control.ts +1 -0
  144. package/src/plugins/defaults/worker-entrypoints.ts +3 -0
  145. package/src/plugins/mtime-cache.ts +17 -0
  146. package/src/prompts/templates/system-sections.ts +0 -7
  147. package/src/providers/__tests__/context-overflow-error.test.ts +24 -0
  148. package/src/providers/__tests__/retry-callsite.test.ts +20 -0
  149. package/src/providers/model-catalog.ts +3 -3
  150. package/src/providers/model-intents.ts +2 -2
  151. package/src/providers/openai/chat-completions-provider.ts +16 -7
  152. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +11 -6
  153. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +9 -49
  154. package/src/routes/control.ts +1 -0
  155. package/src/routes/route-host-client.ts +1 -0
  156. package/src/runtime/AGENTS.md +16 -17
  157. package/src/runtime/agent-wake.ts +15 -12
  158. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +170 -1
  159. package/src/runtime/routes/__tests__/schedule-routes-disarm-reason.test.ts +215 -0
  160. package/src/runtime/routes/conversation-list-routes.ts +54 -22
  161. package/src/runtime/routes/conversation-management-routes.ts +2 -3
  162. package/src/runtime/routes/conversation-routes.ts +11 -13
  163. package/src/runtime/routes/documents-routes.ts +3 -222
  164. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +2 -0
  165. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +3 -0
  166. package/src/runtime/routes/playground/inject-failures.ts +2 -2
  167. package/src/runtime/routes/playground/reset-circuit.ts +1 -1
  168. package/src/runtime/routes/schedule-routes.ts +94 -7
  169. package/src/runtime/routes/workspace-routes.ts +0 -9
  170. package/src/runtime/routes/workspace-utils.ts +3 -13
  171. package/src/runtime/services/conversation-serializer.ts +7 -2
  172. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +68 -5
  173. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +81 -0
  174. package/src/schedule/plugin-schedule-availability.ts +58 -0
  175. package/src/schedule/plugin-schedule-declarations.ts +23 -27
  176. package/src/schedule/plugin-schedule-reconciler.ts +12 -3
  177. package/src/schedule/schedule-store.ts +5 -1
  178. package/src/schedule/scheduler.ts +9 -4
  179. package/src/schedule/worker-control.ts +1 -0
  180. package/src/subagent/__tests__/consult-prompt.test.ts +26 -15
  181. package/src/subagent/consult-context.ts +11 -11
  182. package/src/subagent/consult-prompt.ts +26 -35
  183. package/src/subagent/manager.ts +20 -37
  184. package/src/subagent/notify.ts +7 -1
  185. package/src/subagent/types.ts +8 -6
  186. package/src/tools/acp/spawn.ts +6 -4
  187. package/src/tools/skills/scaffold-managed.ts +25 -7
  188. package/src/tools/subagent/spawn.ts +28 -88
  189. package/src/tools/ui-surface/surface-shape-docs.ts +12 -1
  190. package/src/tts/__tests__/reasoning-tag-filter.test.ts +15 -0
  191. package/src/tts/reasoning-tag-filter.ts +11 -32
  192. package/src/util/__tests__/worker-process-command.test.ts +37 -0
  193. package/src/util/logger.ts +16 -0
  194. package/src/util/think-tag-stream.ts +95 -0
  195. package/src/util/worker-process.ts +37 -4
  196. package/src/windows-compiled-cli.ts +32 -0
  197. package/src/windows-compiled-entry.ts +4 -0
  198. package/src/windows-compiled-logger.ts +6 -0
  199. package/src/windows-compiled-worker-entry.ts +29 -0
  200. package/src/workspace/byok-default-profile-ensure.ts +15 -6
  201. package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +195 -0
  202. package/src/workspace/migrations/registry.ts +2 -0
  203. package/src/__tests__/document-workspace-file.test.ts +0 -467
  204. package/src/subagent/__tests__/consult-transcript.test.ts +0 -184
  205. package/src/subagent/consult-transcript.ts +0 -90
package/AGENTS.md CHANGED
@@ -41,6 +41,12 @@ Do not coordinate hook behaviour by re-parsing the tool's JSON response to infer
41
41
 
42
42
  Shared mutable resources written by more than one caller (e.g. `dist/` directories produced by `compileApp()`) must be serialised per-resource so concurrent callers cannot race on `rm -rf` + write sequences.
43
43
 
44
+ ## Conversation event delivery and turn presence
45
+
46
+ A `Conversation` has one event sink, fixed at construction and never rebound: top-level conversations are built with the SSE hub (`broadcastMessage`), subagents with the wrapper that re-envelopes their events under the parent. Emit conversation-level events (activity state, confirmation prompts, notifier output, out-of-turn pushes) through `conversation.emit`, which delivers to the sink and then to `addEventObserver` observers. Observers are for policy layered on delivery (the voice bridge auto-resolves approval prompts it has no UI for), never for delivery itself. Do not add a per-subsystem sender slot, a bind/restore step around a turn, or a manual `broadcastMessage` for something the conversation already emits: an emitter that runs outside a live turn (queue drain, ACP or subagent notification, summarize route, call notifiers) reaches every client because the sink is always live.
47
+
48
+ Presence (whether a human is present to see UI and answer prompts) is per-turn state, never derived from delivery. Every dispatch path declares it: `isInteractive` on `runAgentLoop` / `processMessage` / `enqueueMessage`, or a wake's `clientless` pin of `currentTurnIsNonInteractive`. A caller that omits it gets a non-interactive turn (approval-gated tools are denied rather than left waiting on a prompt nobody may answer). `hasNoClient` is a getter over the in-flight turn's presence with no setter, so a new dispatch path cannot inherit whatever the previous turn left behind; if you are reaching for a way to set it, declare interactivity on the turn instead.
49
+
44
50
  ## Route architecture: shared ROUTES array
45
51
 
46
52
  Routes in `src/runtime/routes/` are being migrated to a **shared `ROUTES` array** that serves as the single source of truth for both the HTTP server and the IPC server. Each route module exports `ROUTES: RouteDefinition[]` (from `routes/types.ts`), and the aggregator `routes/index.ts` collects them.
@@ -61,7 +67,7 @@ Three response shapes are supported:
61
67
  - **Binary**: a JSON envelope with `headers: { "content-length": "<n>" }` followed by one binary frame of exactly `n` bytes.
62
68
  - **Chunked streaming**: a JSON envelope with `headers: { "transfer-encoding": "chunked" }` followed by one or more binary frames, terminated by a zero-length frame.
63
69
 
64
- The server auto-detects legacy newline-delimited JSON from old CLI clients and handles it transparently. New code must use length-prefixed framing via `writeMessage()` / `IpcFrameReader` in `src/ipc/ipc-framing.ts`.
70
+ The server auto-detects legacy newline-delimited JSON from old CLI clients and handles it transparently. New code must use length-prefixed framing via `writeMessage()` / `IpcFrameReader` from `@vellumai/ipc-server-utils` (`packages/ipc-server-utils/src/ipc-framing.ts`).
65
71
 
66
72
  ### CLI ↔ daemon version skew
67
73
 
@@ -69,7 +75,7 @@ The CLI and daemon are always shipped and upgraded together — there is no vers
69
75
 
70
76
  ### IPC-only routes
71
77
 
72
- Some routes are IPC-only (defined in `src/ipc/routes/`, not in the shared array). These are tool/CLI-specific methods (e.g. `wake_conversation`, `upsert_contact`) that have no HTTP counterpart. They follow the existing pattern: define in `src/ipc/routes/`, register in `src/ipc/routes/index.ts`.
78
+ Some routes are IPC-only (defined in `src/ipc/routes/`, not in the shared array). These are tool/CLI-specific methods (e.g. `wake_conversation`, `upsert_contact`) that have no HTTP counterpart. They follow the existing pattern: define a `*_IPC_METHODS` map in `src/ipc/routes/` and add it to the list `AssistantIpcServer` iterates in `src/ipc/assistant-server.ts` (there is no index file; each map is imported by hand).
73
79
 
74
80
  The module-level dependency-injection pattern (`registerFooDeps()`) used by some IPC routes is a known antipattern. New IPC-only routes should avoid it.
75
81
 
package/ARCHITECTURE.md CHANGED
@@ -709,6 +709,8 @@ Live voice STT uses the same `resolveStreamingTranscriber()` path as conversatio
709
709
 
710
710
  Live voice TTS uses `streamLiveVoiceTtsAudio()` and the configured `services.tts.provider`. The selected provider must be registered, catalog-compatible, and expose `capabilities.supportsStreaming` plus `synthesizeStream()`. Providers whose catalog entry advertises `supportsStreaming` (currently all four catalog providers: ElevenLabs, Fish Audio, Deepgram, and xAI) satisfy this requirement; a buffered-only provider would remain available for buffered message playback or other supported surfaces, but live voice reports a TTS error instead of silently falling back to buffered playback.
711
711
 
712
+ The `voiceFrontDoor` prompt skips current-turn legacy and v3 memory retrieval, while prior frozen memory cards and static memory context remain available. The front-door rule escalates rather than guessing when required personal context is absent. The escalated leg runs the ordinary memory pipeline against the latest visible caller prompt before the quality model, with low selector effort. This keeps memory work off the front-door TTFT path without cross-leg speculative state.
713
+
712
714
  V1 is local/gateway-scoped. Managed/cloud WebSocket proxy support, cross-region routing, and p50/p95 latency guarantees are out of scope for this version. Metrics frames expose timing data for measurement, but the architecture does not promise a hard latency SLO.
713
715
 
714
716
  **Client service-first boundary:**
@@ -172,6 +172,21 @@ Ingested pages carry provenance frontmatter with distinct consumers:
172
172
  machinery (graph extraction, summarization, PKB indexing/filing, PKB
173
173
  injection) is suppressed while the substrate is active.
174
174
 
175
+ #### Live voice front-door memory
176
+
177
+ The live voice front door does not await current-turn memory retrieval. Its
178
+ prompt hook skips legacy graph retrieval, and both v3 injectors skip
179
+ orchestration for `voiceFrontDoor`. Frozen cards from prior turns and the static
180
+ substrate context remain available. When the answer depends on a saved personal
181
+ fact that is absent from that context, the front-door rule escalates instead of
182
+ guessing.
183
+
184
+ The escalated leg runs the ordinary memory pipeline before the quality model.
185
+ V3 retrieval routes on the latest visible caller message, ignoring the hidden
186
+ continuation message used to start that leg. The selector uses low effort to
187
+ keep retrieval latency bounded. This keeps current-turn memory work off the
188
+ front-door TTFT path without maintaining speculative cross-leg state.
189
+
175
190
  ### Boot-time maintenance
176
191
 
177
192
  `substrate/boot-maintenance.ts`, invoked from the memory plugin's
@@ -1,40 +1,42 @@
1
- # Browser Use Architecture — Phase 2
1
+ # Browser Use Architecture
2
2
 
3
3
  ## Overview
4
4
 
5
- Phase 2 of browser automation introduced a three-tier backend selection chain for macOS-originated turns, enabling the assistant to prefer the user's real Chrome session over a sandboxed Playwright instance. Two transport paths exist for the top-priority "extension" backend:
5
+ macOS-originated turns choose a browser backend from a three-tier chain, so the assistant prefers the user's real Chrome session over a sandboxed Playwright instance. The top tier, the host browser proxy, has two transports, both riding the SSE event hub:
6
6
 
7
- 1. **Chrome Extension Registry (WebSocket)**: When the user has the Vellum Chrome Extension installed and paired, `host_browser_request` frames route through the `ChromeExtensionRegistry` singleton over a dedicated `/v1/browser-relay` WebSocket.
8
- 2. **macOS Host Browser Proxy (SSE)**: When the macOS desktop client is connected but the Chrome extension is absent, `host_browser_request` frames travel through `assistantEventHub` (SSE). The desktop client receives the frames via its SSE connection, executes CDP commands against the local Chrome, and POSTs results back to `/v1/host-browser-result`.
7
+ 1. **Chrome Extension**: when the Vellum Chrome Extension is installed and paired, `HostBrowserProxy` publishes `host_browser_request` frames to `assistantEventHub` targeted at the extension's SSE subscription; the extension executes CDP commands through `chrome.debugger` and POSTs results to `/v1/host-browser-result`.
8
+ 2. **macOS desktop bridge**: when the macOS desktop client is connected but no extension is, the same publish targets the desktop client's SSE subscription; it executes CDP commands against the local Chrome and POSTs results the same way.
9
9
 
10
- When neither transport is available, the system falls back through cdp-inspect (direct Chrome DevTools Protocol attach) before resorting to the local Playwright browser.
10
+ When neither is available, the chain falls through to cdp-inspect (direct Chrome DevTools Protocol attach) before resorting to the local Playwright browser.
11
11
 
12
12
  This document describes the runtime architecture, backend precedence rules, transport matrix, and the manual QA playbook for verifying correct backend selection.
13
13
 
14
14
  ## Component Inventory
15
15
 
16
- | Component | Location | Role |
17
- | --------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
18
- | **ChromeExtensionRegistry** | `runtime/chrome-extension-registry.ts` | Tracks active extension WebSocket connections keyed by `(guardianId, clientInstanceId)`. Populated on WS `open`, drained on WS `close`. |
19
- | **HostBrowserProxy** | `daemon/host-browser-proxy.ts` | Per-conversation proxy that dispatches `host_browser_request` frames and awaits `host_browser_result` responses. Wired to either the registry sender or the SSE hub sender. |
20
- | **CDP Factory** | `tools/browser/cdp-client/factory.ts` | Builds the ordered candidate list and returns a `ScopedCdpClient` with per-invocation failover. |
21
- | **BrowserSessionManager** | `browser-session/manager.ts` | Routes CDP commands through the selected backend with session tracking. |
22
- | **CdpInspectClient** | `tools/browser/cdp-client/cdp-inspect-client.ts` | Connects to a host Chrome instance via its remote-debugging WebSocket endpoint. |
23
- | **LocalCdpClient** | `tools/browser/cdp-client/local-cdp-client.ts` | Drives Playwright's CDPSession against the sacrificial-profile browser. |
24
- | **ExtensionCdpClient** | `tools/browser/cdp-client/extension-cdp-client.ts` | Routes CDP commands through the HostBrowserProxy to the user's real Chrome. |
25
- | **conversation-routes.ts** | `runtime/routes/conversation-routes.ts` | Wires `resolveHostBrowserSender()` to set `hostBrowserSenderOverride` when the extension is connected. For macOS without extension, provisions the proxy with the SSE hub sender. |
26
- | **Desktop-auto config** | `config/schemas/host-browser.ts` | `desktopAuto.enabled` (default `true`) and `desktopAuto.cooldownMs` (default 30s) control automatic cdp-inspect on macOS. |
16
+ | Component | Location | Role |
17
+ | ------------------------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
18
+ | **HostBrowserProxy** | `daemon/host-browser-proxy.ts` | Lazily-created singleton. Publishes `host_browser_request` frames to the hub with `targetCapability: "host_browser"` and an explicit target client chosen at send time (`resolveTargetClient`), then awaits the matching `host_browser_result`. `Vellum.*` pseudo-methods go only to a chrome-extension client; raw CDP prefers an extension client over the macOS bridge; `targetClientId` pins one; `sourceActorPrincipalId` restricts to that actor's clients. |
19
+ | **events-routes.ts** | `runtime/routes/events-routes.ts` | `GET /v1/events`: registers each SSE client on the hub with the capabilities its `X-Vellum-Interface-Id` supports (`host_browser` for `chrome-extension`; every host-proxy capability for `macos`). |
20
+ | **pair.ts** | `gateway/src/http/routes/pair.ts` | `POST /v1/pair`: loopback-only, rate-limited pairing that mints the extension's `actor_client_v1` JWT for self-hosted deployments. Cloud deployments issue the guardian-bound JWT through the gateway's WorkOS-backed flow. |
21
+ | **CDP Factory** | `tools/browser/cdp-client/factory.ts` | Builds the ordered candidate list and returns a `ScopedCdpClient` with per-invocation failover. The macOS bridge is its internal `"host-bridge"` candidate kind. |
22
+ | **BrowserSessionManager** | `browser-session/manager.ts` | Routes CDP commands through the selected backend with session tracking. |
23
+ | **CdpInspectClient** | `tools/browser/cdp-client/cdp-inspect-client.ts` | Connects to a host Chrome instance via its remote-debugging WebSocket endpoint. |
24
+ | **LocalCdpClient** | `tools/browser/cdp-client/local-cdp-client.ts` | Drives Playwright's CDPSession against the sacrificial-profile browser. |
25
+ | **ExtensionCdpClient** | `tools/browser/cdp-client/extension-cdp-client.ts` | Routes CDP commands through the HostBrowserProxy to the user's real Chrome. |
26
+ | **Desktop-auto config** | `config/schemas/host-browser.ts` | `desktopAuto.enabled` (default `true`) and `desktopAuto.cooldownMs` (default 30s) control automatic cdp-inspect on macOS. |
27
+
28
+ Nothing in the turn layer wires the proxy: it reads the hub's live roster on every send, and a conversation's own event sink is fixed for its life (see `assistant/AGENTS.md`, "Conversation event delivery and turn presence"), so a queued or drained turn reaches the extension exactly like a live one.
27
29
 
28
30
  ## Transport Matrix
29
31
 
30
- The following table shows how `host_browser_request` frames are delivered to the client based on the originating interface and extension connectivity:
32
+ How `host_browser_request` frames reach a client, by originating interface and extension connectivity:
31
33
 
32
- | Interface | Extension Connected | Transport | Sender | Notes |
33
- | ------------------ | ------------------- | ------------------------------- | ----------------------------------------------- | ---------------------------------------------------------------------- |
34
- | `chrome-extension` | Yes (always) | WebSocket (`/v1/browser-relay`) | `ChromeExtensionRegistry.send(guardianId, msg)` | The only transport for chrome-extension turns; SSE fallback is invalid |
35
- | `macos` | Yes | WebSocket (`/v1/browser-relay`) | `ChromeExtensionRegistry.send(guardianId, msg)` | Extension takes priority; browser tools route through user's Chrome |
36
- | `macos` | No | SSE (`assistantEventHub`) | `onEvent` hub publisher | macOS host browser proxy mode; desktop client handles CDP locally |
37
- | Other | Any | N/A | No browser proxy provisioned | Falls through to cdp-inspect or local Playwright |
34
+ | Interface | Extension Connected | Transport | Target | Notes |
35
+ | ------------------ | ------------------- | ------------------------- | --------------------------------------- | --------------------------------------------------------------------------- |
36
+ | `chrome-extension` | Yes (always) | SSE (`assistantEventHub`) | The chrome-extension client | The only transport for chrome-extension turns |
37
+ | `macos` | Yes | SSE (`assistantEventHub`) | The chrome-extension client (preferred) | Browser tools route through the user's real Chrome session |
38
+ | `macos` | No | SSE (`assistantEventHub`) | The macOS desktop client (bridge) | Desktop client executes CDP locally against the user's Chrome |
39
+ | Other | Any | SSE (`assistantEventHub`) | Any connected `host_browser` client | Falls through to cdp-inspect or local Playwright when no client is eligible |
38
40
 
39
41
  ## Wire Diagram
40
42
 
@@ -45,22 +47,6 @@ macOS app (user message)
45
47
  POST /v1/messages { interface: "macos", ... }
46
48
  |
47
49
  v
48
- conversation-routes.ts
49
- |-- setTurnInterfaceContext({ userMessageInterface: "macos", ... })
50
- |-- resolveHostBrowserSender()
51
- | |
52
- | +-- ChromeExtensionRegistry.get(guardianId)
53
- | |
54
- | +-- entry found? --> registrySender (WS to extension)
55
- | | hostBrowserSenderOverride = registrySender
56
- | | provision HostBrowserProxy(registrySender)
57
- | |
58
- | +-- entry not found? --> SSE hub sender (default)
59
- | hostBrowserSenderOverride = undefined
60
- | provision HostBrowserProxy(onEvent)
61
- | [macOS natively supports host_browser]
62
- |
63
- v
64
50
  Agent loop invokes browser tool
65
51
  |
66
52
  v
@@ -68,8 +54,9 @@ getCdpClient(toolContext)
68
54
  |-- toolContext.hostBrowserProxy set?
69
55
  | AND hostBrowserProxy.isAvailable()?
70
56
  | --> candidate: extension (priority 1)
71
- | [Transport: WS via registry when extension present,
72
- | SSE via hub when macOS host proxy only]
57
+ | [HostBrowserProxy publishes to the hub with
58
+ | targetCapability "host_browser"; the target client is
59
+ | the extension when one is connected, else the macOS bridge]
73
60
  |
74
61
  |-- transportInterface === "macos"
75
62
  | AND desktopAuto.enabled?
@@ -81,7 +68,7 @@ getCdpClient(toolContext)
81
68
  v
82
69
  ScopedCdpClient.send(method, params)
83
70
  |
84
- +-- Try candidate 1 (extension / macOS host proxy)
71
+ +-- Try candidate 1 (extension / macOS bridge)
85
72
  | transport_error? --> failover to candidate 2
86
73
  | cdp_error? --> propagate immediately (no failover)
87
74
  | success? --> sticky for remainder of invocation
@@ -96,14 +83,18 @@ ScopedCdpClient.send(method, params)
96
83
 
97
84
  ## Backend Precedence (macOS)
98
85
 
99
- | Priority | Backend | When selected | Transport | Failover trigger |
100
- | -------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | ---------------------------------------------------------------------------------------- |
101
- | 1 | Extension / macOS host proxy | `hostBrowserProxy` present and `isAvailable()` is `true`. On macOS, the proxy is always provisioned (SSE sender when no extension, registry-routed when extension is connected) | WS (registry) or SSE (hub) | Transport error (WebSocket disconnected, SSE send failed) |
102
- | 2 | cdp-inspect | Config `enabled: true`, OR macOS + `desktopAuto.enabled` (default) + cooldown not active | Direct CDP WebSocket | Transport error (endpoint unreachable, WS connect failure). Records cooldown on failure. |
103
- | 3 | Local (Playwright) | Always present as final fallback | In-process CDP | Errors propagate to the tool |
86
+ | Priority | Backend | When selected | Transport | Failover trigger |
87
+ | -------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
88
+ | 1 | Extension / macOS host proxy | `extension` candidate when `hasExtensionClient(actor)` finds a chrome-extension client; otherwise `host-bridge` candidate when `isAvailable(actor)` finds a `host_browser` client and the actor's host-bridge cooldown is not active | SSE (hub) | Transport error (client disconnected, no eligible client, publish failed). A `host-bridge` failure records the per-actor cooldown. |
89
+ | 2 | cdp-inspect | Config `enabled: true`, OR macOS + `desktopAuto.enabled` (default) + cooldown not active | Direct CDP WebSocket | Transport error (endpoint unreachable, WS connect failure). Records cooldown on failure. |
90
+ | 3 | Local (Playwright) | Always present as final fallback | In-process CDP | Errors propagate to the tool |
104
91
 
105
92
  After the first successful CDP command on any backend, that backend becomes **sticky** for the remainder of the tool invocation.
106
93
 
94
+ ## Host-bridge Cooldown
95
+
96
+ When the `host-bridge` candidate fails with a transport error, the factory records a per-actor cooldown (`recordHostBridgeCooldown`, keyed by `sourceActorPrincipalId`, `__default__` when unresolved) for the same `desktopAuto.cooldownMs` window. While it is active, `buildCandidateList` skips the bridge (log `CDP factory: host-bridge skipped (cooldown active)`) and the turn goes straight to cdp-inspect/local. Per-actor rather than process-global because on a multi-actor cloud daemon the bridge reaches a different desktop per actor. The `extension` candidate is never cooled down.
97
+
107
98
  ## Desktop-auto cdp-inspect Cooldown
108
99
 
109
100
  When cdp-inspect fails with a transport error during a desktop-auto attempt:
@@ -119,8 +110,8 @@ When cdp-inspect fails with a transport error during a desktop-auto attempt:
119
110
 
120
111
  **Setup:**
121
112
 
122
- 1. Pair the browser extension (chrome extension installed, `assistant pair browser-extension` completed or cloud pairing via platform).
123
- 2. Open the macOS app and verify the extension WebSocket is connected (check runtime logs for `browser-relay: registered connection`).
113
+ 1. Pair the browser extension (self-hosted: the extension pairs itself through `POST /v1/pair`; cloud: sign in through the platform).
114
+ 2. Verify the extension's SSE subscription is registered (runtime log `subscriber registered (client)` with `interfaceId: "chrome-extension"` and `capabilities: ["host_browser"]`).
124
115
 
125
116
  **Test:**
126
117
 
@@ -132,7 +123,7 @@ When cdp-inspect fails with a transport error during a desktop-auto attempt:
132
123
  - `cdp-factory` log: `CDP factory: built candidate list` with `candidates: [{kind: "extension", ...}, {kind: "cdp-inspect", ...}, {kind: "local", ...}]`
133
124
  - `cdp-factory` log: `CDP factory: candidate succeeded, backend is now sticky` with `candidateKind: "extension"`
134
125
  - No `browserManager` launch log (Playwright not started).
135
- - Extension WebSocket receives `host_browser_request` frames.
126
+ - The extension's SSE stream receives `host_browser_request` frames; results arrive as `POST /v1/host-browser-result`.
136
127
 
137
128
  ### Scenario 1b: macOS Host Browser Proxy (No Extension)
138
129
 
@@ -149,13 +140,13 @@ When cdp-inspect fails with a transport error during a desktop-auto attempt:
149
140
 
150
141
  **Expected telemetry/log signals:**
151
142
 
152
- - `cdp-factory` log: `CDP factory: built candidate list` with `candidates: [{kind: "extension", ...}, {kind: "cdp-inspect", ...}, {kind: "local", ...}]`
153
- - `cdp-factory` log: `CDP factory: candidate succeeded, backend is now sticky` with `candidateKind: "extension"`
143
+ - `cdp-factory` log: `CDP factory: built candidate list` with `candidates: [{kind: "host-bridge", ...}, {kind: "cdp-inspect", ...}, {kind: "local", ...}]`
144
+ - `cdp-factory` log: `CDP factory: candidate succeeded, backend is now sticky` with `candidateKind: "host-bridge"`
154
145
  - No `browserManager` launch log (Playwright not started).
155
- - SSE event stream delivers `host_browser_request` frames (not WebSocket relay).
156
- - `browser_status` output shows `transport: "macos-sse"` in the extension mode details.
146
+ - The macOS client's SSE stream delivers the `host_browser_request` frames.
147
+ - No failover: the bridge is the first candidate and succeeds.
157
148
 
158
- **Difference from Scenario 1:** The transport is SSE-based (`assistantEventHub`), not the `/v1/browser-relay` WebSocket. The `hostBrowserSenderOverride` is `undefined` because no registry entry exists. The proxy is provisioned because macOS natively supports `host_browser`.
149
+ **Difference from Scenario 1:** Same transport, different target: with no chrome-extension client on the hub roster, `resolveTargetClient` selects the macOS desktop client, which registers every host-proxy capability including `host_browser`.
159
150
 
160
151
  ### Scenario 2: Extension Absent + cdp-inspect Enabled
161
152
 
@@ -205,7 +196,7 @@ In all scenarios, the definitive signal is the `cdp-factory` structured log:
205
196
 
206
197
  ```
207
198
  CDP factory: candidate succeeded, backend is now sticky
208
- candidateKind: "extension" | "cdp-inspect" | "local"
199
+ candidateKind: "extension" | "host-bridge" | "cdp-inspect" | "local"
209
200
  conversationId: "<id>"
210
201
  method: "<first CDP method called>"
211
202
  ```
@@ -215,3 +206,84 @@ Filter runtime logs with:
215
206
  ```bash
216
207
  grep -h "cdp-factory" "$VELLUM_WORKSPACE_DIR"/data/logs/assistant-*.log
217
208
  ```
209
+
210
+ ## Steady-state contract
211
+
212
+ After the first successful Connect, the extension operates as a
213
+ background service with no further user interaction required:
214
+
215
+ 1. **Install once**: Load the extension.
216
+ 2. **Connect once**: Click Connect in the popup. The worker
217
+ auto-bootstraps credentials (local pair token or cloud JWT) as part
218
+ of the single-click flow.
219
+ 3. **Forget it**: The extension keeps its SSE subscription up indefinitely.
220
+ A `chrome.alarms` alarm (`vellum-relay-keepalive`, every 30 s) wakes the
221
+ MV3 service worker and reconnects the stream if it is closed; transient
222
+ drops reconnect with exponential backoff (`sse-connection.ts`). The
223
+ `autoConnect` flag persists across browser sessions so reopening Chrome
224
+ automatically reconnects. An authentication failure is not retried
225
+ silently: it surfaces as the `auth_required` health state below.
226
+
227
+ Users should only interact with the extension again when:
228
+
229
+ - They want to **Pause** (intentionally disconnect and disable
230
+ auto-reconnect).
231
+ - The popup shows **Action required** (`auth_required` or `error` health
232
+ state), meaning automatic recovery has been exhausted.
233
+
234
+ A transient extension disconnect does change backend selection. In auto
235
+ mode `buildCandidateList` reads the hub roster at the start of each
236
+ browser operation: with no chrome-extension client connected it skips the
237
+ `extension` candidate and the chain proceeds to `host-bridge` (macOS),
238
+ cdp-inspect (opt-in, or desktop-auto on macOS), then local; and an
239
+ extension that drops mid-command surfaces a `transport_error`, which
240
+ advances the chained client to the next candidate. Only a dispatch pinned
241
+ with `browser_mode: "extension"` waits through the proxy's reconnect grace
242
+ (`EXTENSION_RECONNECT_GRACE_MS`, 3 s) before failing. `cdp-inspect` is an
243
+ advanced backend for users who cannot install the extension or who need
244
+ broad session-level CDP access; see
245
+ [the `cdp-inspect` backend doc](../../docs/browser-use-cdp-inspect-backend.md).
246
+
247
+ ## Known UX considerations
248
+
249
+ ### `chrome.debugger` infobar
250
+
251
+ When the Chrome extension calls
252
+ `chrome.debugger.attach(target, requiredVersion)`, Chrome displays a
253
+ persistent yellow infobar at the top of the affected tab saying "Vellum
254
+ started debugging this browser." This is an intentional security
255
+ mitigation; it cannot be suppressed via the public MV3 API.
256
+
257
+ Chrome API notes:
258
+
259
+ - `chrome.debugger.attach(target, requiredVersion, callback)`: three-
260
+ argument form, no options parameter. Chrome 120+.
261
+ (https://developer.chrome.com/docs/extensions/reference/api/debugger)
262
+ - There is no `{ silent: true }` option on attach.
263
+ - The `--silent-debugger-extension-api` command-line flag exists for
264
+ Chromium but (a) requires the user to launch Chrome with the flag,
265
+ (b) is not enabled by default in stable channels, and (c) is not
266
+ something we can enforce on end users.
267
+ - Chrome 126+ added `chrome.debugger.attach` acceptance via `targetId`
268
+ / `tabId` but did not add a silent-mode option.
269
+ - Closing the infobar does not detach the debugger; it is purely
270
+ informational.
271
+
272
+ Decision: accept the infobar; no public API exists to suppress it. End-user messaging in the
273
+ Mac app popup should explain that the banner is expected and normal
274
+ when Vellum is driving the browser.
275
+
276
+ Alternatives considered:
277
+
278
+ - Playwright / `chrome --remote-debugging-port` in a sacrificial profile
279
+ avoids the infobar but requires installing Chromium and is out-of-
280
+ scope.
281
+ - The assistant-local `cdp-inspect` backend attaches to an existing
282
+ Chrome instance via `chrome://inspect` / `--remote-debugging-port`
283
+ and avoids the per-tab debugger infobar entirely. It is implemented
284
+ and opt-in via `hostBrowser.cdpInspect.enabled`; see
285
+ [the `cdp-inspect` backend doc](../../docs/browser-use-cdp-inspect-backend.md)
286
+ for setup, security trade-offs, and troubleshooting. Note that in auto
287
+ mode it is also the next candidate after `host-bridge` when the
288
+ extension is disconnected at the start of an operation (see the
289
+ steady-state contract above).
@@ -12,7 +12,7 @@ Flux is a spike. `liveVoice.flux.turnEnd.enabled` defaults to `false`, the exist
12
12
 
13
13
  It is stamped in `releaseUtterance` (`live-voice-session.ts`), which every committed turn passes through whichever decider released it, from the same speech-stop anchor on both. So the two arms are one population measured over one span. It is absent only on a turn that never committed, and in push-to-talk, which this spike does not run.
14
14
 
15
- It is also the only endpoint number that the Flux socket teardown stays out of: `releaseUtterance` stamps it before it stops the transcriber. `roundTripMs`, `llmFirstDeltaMs`, and `totalMs` all carry that teardown on a Flux arm, and so does what the caller hears. Read "The per-turn socket teardown, and why it is load-bearing" in section 3 before you compare any of those three against a `deepgram` run.
15
+ It is also the only endpoint number that a Flux socket teardown stays out of: `releaseUtterance` stamps it before any stop. On a turn Flux itself closed there is no teardown to carry; on a caller-side release `roundTripMs`, `llmFirstDeltaMs`, and `totalMs` still carry one, and so does what the caller hears. Read "The socket teardown, and when it is load-bearing" in section 3 before you compare any of those three against a `deepgram` run.
16
16
 
17
17
  ### Why the other endpoint fields are not the comparison
18
18
 
@@ -105,18 +105,23 @@ The rest of `liveVoice.flux` is optional and defaulted (`config/schemas/live-voi
105
105
 
106
106
  Say the same scripted set of utterances in both arms. Include at least a few deliberate mid-sentence thinking pauses, because that is the case the hold path exists for and the case Flux has to not regress.
107
107
 
108
- ### The per-turn socket teardown, and why it is load-bearing
108
+ ### The socket teardown, and when it is load-bearing
109
109
 
110
110
  The Flux adapter implements no `finalizeUtterance`. That is forced by Flux's wire protocol, not a statement that Flux owns the turn boundary, and adding the method as a no-op would break transcript correctness.
111
111
 
112
112
  `parseFluxFrame` emits `final` only on `EndOfTurn`, and that is the adapter's sole source of `final`. Flux offers no mid-stream flush, so `CloseStream` is the only message that makes it answer for a turn still in progress, which is exactly what `stop()` sends. A no-op `finalizeUtterance` would report `finalized` without flushing anything, so a turn released on a caller-side boundary would dispatch on an empty transcript while its real text arrived afterwards and was dropped as a late final segment. With `turnEnd.enabled` at its default `false` that is every turn, because the release always comes from the local silence path. With the latch on it is still every fail-open fallback, every max-duration force-end, and every barge-in, all of which release with a Flux turn open.
113
113
 
114
- So the release path is: `stop()` sends `CloseStream`, the adapter waits for Deepgram's close frame under a `CLOSE_GRACE_MS` ceiling (**5000ms**, `deepgram-flux-realtime.ts`), and then emits `closed`. The cycle only reaches `transcriber_closed` on that `closed` event, and `startAssistantTurnIfReady` refuses to dispatch before it. Two consequences for the numbers:
114
+ That reasoning holds for a turn the **caller** releases, and only for those. A turn Flux itself closes needs no flush at all: `EndOfTurn` emits the `final` immediately before `turn-end`, so the transcript is already complete when the release runs, and the stream can stay open (JARVIS-1538). The session reflects that split:
115
115
 
116
- - **`endpointCommitLatencyMs` does not contain the teardown.** `releaseUtterance` stamps the commit latency and `utteranceEndAtMs` before it calls `stop()`, so the headline comparison is clean on both arms.
117
- - **`roundTripMs`, `llmFirstDeltaMs`, and `totalMs` do contain it,** and so does the silence the caller sits through. On a Flux arm the assistant turn cannot start until the socket has closed. A `deepgram` run pays none of that: the shared stream stays open and the cycle reaches `transcriber_closed` on the `finalized` event instead.
116
+ - **Provider-closed turns seal in place.** `handleProviderTurnEnd` marks the cycle `providerClosedTurn`, and the release moves it straight to `transcriber_closed` without stopping the transcriber. The stream serves the whole session, and `rearmAfterTurn` re-arms onto it synchronously.
117
+ - **Caller-side releases still close it.** The fail-open deadline, a max-duration force-end, and barge-in all release with a Flux turn potentially still open, and `CloseStream` remains the only message that makes Flux answer for one. Those releases retire the shared stream and tear it down; the next arm dials a fresh one.
118
118
 
119
- The re-dial is off the end-of-turn path but is not free either. `rearmAfterTurn` opens the next `/v2/listen` socket once the assistant turn finishes, and speech arriving during that handshake is held in the VAD pre-roll buffer, so its cost lands on the next utterance's first partial rather than on its commit.
119
+ Consequences for the numbers, all now specific to the caller-side path:
120
+
121
+ - **`endpointCommitLatencyMs` never contains a teardown.** `releaseUtterance` stamps the commit latency and `utteranceEndAtMs` before any stop, so the headline comparison is clean on both arms.
122
+ - **`roundTripMs`, `llmFirstDeltaMs`, and `totalMs` contain it only when the caller released the turn,** which with the latch on means the exceptional paths rather than every turn.
123
+
124
+ Before JARVIS-1538 every utterance dialed its own socket, and the audio arriving during that handshake was lost rather than replayed from the VAD pre-roll buffer. The opening words of each turn after the first went missing ("How many days are in February?" transcribed as "many days are in February?"). A persistent stream removes the handshake, and with it the gap.
120
125
 
121
126
  ### What this A/B still does not hold constant
122
127
 
package/knip.json CHANGED
@@ -4,6 +4,9 @@
4
4
  "src/**/__tests__/**/*.ts",
5
5
  "scripts/**/*.ts",
6
6
  "src/daemon/main.ts!",
7
+ "src/daemon/windows-compiled-entry.ts!",
8
+ "src/windows-compiled-entry.ts!",
9
+ "src/windows-compiled-worker-entry.ts!",
7
10
  "src/plugin-api/index.ts!",
8
11
  "src/api/index.ts!",
9
12
  "src/config/bundled-skills/**/tools/**/*.ts!"