wsl-chrome-bridge 0.2.0 → 0.3.0

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.
@@ -0,0 +1,198 @@
1
+ # Bridge Connection Lifecycle (Draft)
2
+
3
+ ## Purpose
4
+
5
+ This document defines how `wsl-chrome-bridge` manages lifecycle and recovery with Windows Chrome, especially for:
6
+
7
+ - shared `--user-data-dir` reuse behavior
8
+ - different behavior between `pipe` (`chrome-devtools-mcp`) and `ws` (`playwright-mcp`) transport modes
9
+ - recovery behavior after Chrome is manually closed and a new upstream request arrives
10
+
11
+ ## Core Principles
12
+
13
+ 1. Use `windowsUserDataDir` as the primary instance identity key and prefer reusing an existing Chrome instance.
14
+ 2. If reuse is possible, keep the existing `remote-debugging-port` and do not force a restart.
15
+ 3. Only launch a new Chrome instance when no reusable instance is available.
16
+ 4. In `pipe` mode, bridge is allowed to exit when downstream Chrome disconnects (so upstream can restart on next operation).
17
+ 5. In `ws` mode, bridge should not exit on Chrome disconnect; it enters a recoverable state and retries on the next upstream request.
18
+ 6. `pipe + headless` requires explicit exit handling:
19
+ - normal exit: bridge cleanup actively stops headless Chrome
20
+ - abnormal exit (for example `SIGKILL`): a detached Node watchdog observes bridge PID death and then stops Chrome
21
+
22
+ ## Terms
23
+
24
+ - `TransportMode`
25
+ - `pipe`: upstream uses fd3/fd4 CDP pipe
26
+ - `ws`: upstream uses local websocket proxy
27
+ - `Ownership`
28
+ - `attached`: bridge attached to an existing Chrome (not launched in this run)
29
+ - `launched`: bridge launched Chrome in this run
30
+ - `BridgeRuntimeState`
31
+ - `starting`
32
+ - `connected`
33
+ - `degraded` (Chrome unavailable but bridge still alive)
34
+ - `closing`
35
+
36
+ ## Startup Lifecycle
37
+
38
+ ### Phase 1: Plan and argument resolution
39
+
40
+ 1. Parse args/env and determine:
41
+ - `windowsUserDataDir`
42
+ - `requestedLocalDebugPort` (commonly provided by Playwright)
43
+ - `windowsDebugPort` (candidate port)
44
+ - transport mode (`pipe` / `ws` / mixed)
45
+ 2. Determine effective `TransportMode`:
46
+ - fd3/fd4 present and pipe requested -> `pipe`
47
+ - localProxyPort present -> `ws` (or mixed)
48
+
49
+ ### Phase 2: Detect and reuse
50
+
51
+ 1. Query all Windows `chrome.exe` processes.
52
+ 2. Filter instances whose `--user-data-dir` is equivalent to `windowsUserDataDir`.
53
+ 3. Extract `--remote-debugging-port` from matched process:
54
+ - if valid, use it as `resolvedWindowsDebugPort` and mark `ownership = attached`
55
+ - otherwise continue to Phase 3
56
+
57
+ ### Phase 3: Launch fallback
58
+
59
+ 1. Resolve launch port using current precedence:
60
+ - bridge arg / env / upstream port / auto-random
61
+ 2. Launch Windows Chrome and mark `ownership = launched`.
62
+ 3. Poll `/json/version` until `webSocketDebuggerUrl` is available.
63
+
64
+ ### Phase 4: Relay and upstream channel setup
65
+
66
+ 1. Start CDP relay to `remoteBrowserWsUrl`.
67
+ 2. In `ws` mode, start local debug proxy (for Playwright).
68
+ 3. If `pipe + headless + ownership=launched + chromePid available`, start detached Node watchdog:
69
+ - pass `bridgePid`, `chromePid`, `powershellPath`, and `expectedExecutablePath`
70
+ - watchdog only monitors process lifetime; it does not relay CDP traffic
71
+ 4. Transition state to `connected`.
72
+
73
+ ## Runtime Event Handling
74
+
75
+ ### Event A: User manually closes Chrome (relay disconnect)
76
+
77
+ #### A1. `pipe` mode
78
+
79
+ 1. Bridge logs disconnect reason.
80
+ 2. Bridge runs cleanup and exits (non-zero or current policy).
81
+ 3. If this run is `pipe + headless`, cleanup stops the matching browser root process using `user-data-dir + active remote-debugging-port` (PowerShell `Stop-Process`).
82
+ 4. Upstream (`chrome-devtools-mcp`) restarts on next operation.
83
+
84
+ #### A2. `ws` mode
85
+
86
+ 1. Bridge stays alive and keeps local proxy listener open.
87
+ 2. When strong signal is observed (for example `ConnectionClosedPrematurely` / `READER_CLOSE`), bridge enters `degraded` immediately (without waiting relay process exit).
88
+ 3. Forwarding to Chrome is paused.
89
+ 4. Recovery is triggered by the next upstream CDP request (Event B).
90
+
91
+ ### Chrome-close detection rules (runtime heuristics)
92
+
93
+ To avoid false positives from a single event (for example `Inspector.detached`), runtime uses a signal combination:
94
+
95
+ 1. Weak signals (not enough by themselves to conclude browser shutdown):
96
+ - `Inspector.detached` (any reason)
97
+ - `Target.detachedFromTarget`
98
+ 2. Strong signals (browser-level connection failure):
99
+ - relay receives `READER_ERROR` with `webSocketErrorCode=ConnectionClosedPrematurely`
100
+ - relay receives `READER_CLOSE` and then can no longer exchange CDP traffic
101
+ 3. Recommended decision rule:
102
+ - cache weak events from `chrome -> relay` with full JSON (ring buffer up to 10)
103
+ - on websocket interruption (`relayFatal` / `relayError` / `relayExit`), compare interruption timestamp with cached weak events
104
+ - if weak signal exists within 2 seconds and strong signal is also present, treat as browser disconnected and enter `degraded` (`ws`) or `closing` (`pipe`)
105
+ - weak-only signals are treated as target/session-level issues, not full browser shutdown
106
+ 4. Notes:
107
+ - reason strings (for example `Render process gone.`) are observational only and not a single source of truth
108
+ - distinguishing user-close vs crash/system-kill requires extra process/port probes; recovery flow does not depend on this distinction
109
+
110
+ ### Event B: upstream request arrives during `ws` `degraded`
111
+
112
+ 0. Special short-circuit (avoid relaunch-on-exit loop):
113
+ - if bridge has `chromeDisconnectedLikely=true` and current request is `Browser.close`
114
+ - return synthetic success response (`{ id: <same>, result: {} }`) immediately
115
+ - do not trigger recovery and do not queue this request
116
+ 1. Bridge runs `recoverChromeForCurrentUserDataDir()`:
117
+ - first try attach to existing same-profile Chrome
118
+ - if attach succeeds: refresh `remoteBrowserWsUrl`, rebuild relay, transition to `connected`
119
+ - if attach fails: relaunch Chrome using normal launch policy, wait for `/json/version`, rebuild relay, transition to `connected`
120
+ 2. On recovery relay rebuild, run bootstrap before queue flush:
121
+ - send internal root-session request:
122
+ - `Target.setAutoAttach { autoAttach: true, waitForDebuggerOnStart: true, flatten: true }`
123
+ - do not flush queued requests before bootstrap response success
124
+ - flush queue only after bootstrap success
125
+ 3. Internal request protocol boundaries:
126
+ - internal requests use bridge-owned id space, separate from upstream ids
127
+ - internal responses are consumed inside bridge and never forwarded upstream
128
+ - `Target.attachedToTarget` / `Target.detachedFromTarget` events are still forwarded so upstream gets new `sessionId`
129
+ 4. On success:
130
+ - forward current request (queue -> bootstrap -> flush)
131
+ 5. On failure:
132
+ - throw and terminate bridge process
133
+
134
+ #### Why bootstrap is required
135
+
136
+ - `Target.createTarget` returns `targetId`, not `sessionId`
137
+ - `sessionId` comes from attach flow (`Target.attachToTarget` or auto-attach events)
138
+ - flushing `Target.createTarget` too early (before auto-attach restore) can produce upstream page/session mismatches (for example `undefined (_page)` style failures)
139
+
140
+ ## Cleanup Rules
141
+
142
+ 1. Default case (not `pipe + headless`):
143
+ - bridge does not actively stop Windows Chrome
144
+ 2. `pipe + headless`:
145
+ - bridge cleanup performs stop with `windowsUserDataDir + activeWindowsDebugPort` filtering
146
+ - this prevents orphaned background headless processes
147
+ 3. In `pipe + headless + ownership=launched + chromePid available`, detached Node watchdog is started as abnormal-exit safety:
148
+ - watchdog polls `kill -0 <bridgePid>` in Linux/WSL
149
+ - after bridge PID disappears, watchdog invokes PowerShell stop for target `chromePid`
150
+ - pre-stop validation:
151
+ - target PID command line must contain `--headless`
152
+ - if `expectedExecutablePath` is provided, executable path must match
153
+ 4. `ws + degraded` should not auto-destroy process; terminate only when:
154
+ - upstream connection is intentionally closed and service is no longer needed
155
+ - bridge receives SIGINT/SIGTERM
156
+ - fatal error (for example proxy cannot listen)
157
+
158
+ ### Definition of `stop-process`
159
+
160
+ - `stop-process` means bridge uses PowerShell `Stop-Process -Id <pid> -Force` to terminate Windows Chrome.
161
+ - in `pipe + headless`, this is used in cleanup after profile+port filtering
162
+ - on abnormal bridge exit, detached Node watchdog can trigger equivalent stop via PID validation flow
163
+
164
+ ## State Machine Summary
165
+
166
+ - `starting` -> `connected`
167
+ - condition: successful attach or launch and relay connection
168
+ - `connected` -> `degraded`
169
+ - condition: relay disconnect in `ws` mode
170
+ - `degraded` -> `connected`
171
+ - condition: next upstream request triggers successful recovery
172
+ - `connected` -> `closing`
173
+ - condition: downstream disconnect in `pipe`, signal, or fatal error
174
+ - `degraded` -> `closing`
175
+ - condition: signal, fatal error, or policy-driven termination
176
+
177
+ ## Observability Recommendations
178
+
179
+ Debug log fields should include:
180
+
181
+ - `transportMode=pipe|ws`
182
+ - `runtimeState=starting|connected|degraded|closing`
183
+ - `ownership=attached|launched`
184
+ - `windowsUserDataDir=...`
185
+ - `resolvedWindowsDebugPort=...`
186
+ - `recoveryAttempt=<n>`
187
+ - `recoveryResult=attached|relaunched|failed`
188
+ - `recoveryBootstrap=started|acknowledged|failed`
189
+ - `shortCircuitBrowserClose=true|false`
190
+ - `disconnectSignals=...` (for example `Inspector.detached,Target.detachedFromTarget,ConnectionClosedPrematurely`)
191
+
192
+ ## Confirmed Policies
193
+
194
+ 1. On `ws` recovery failure: throw and terminate bridge process.
195
+ 2. `ws` degraded waiting window: no timeout.
196
+ 3. `pipe + headless` termination policy:
197
+ - normal cleanup: actively stop matching headless Chrome
198
+ - abnormal exit (including `SIGKILL`): detached Node watchdog performs fallback stop
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wsl-chrome-bridge",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Bridge Chrome DevTools MCP/Playwright MCP in WSL2 to Windows Chrome",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -17,6 +17,7 @@
17
17
  },
18
18
  "files": [
19
19
  "dist",
20
+ "docs",
20
21
  "README*.md",
21
22
  "UPGRADE*.md"
22
23
  ],
@@ -38,7 +39,7 @@
38
39
  "@types/node": "^20.19.39",
39
40
  "@types/ws": "^8.18.1",
40
41
  "tsx": "^4.21.0",
41
- "typescript": "^6.0.2",
42
- "vitest": "^4.1.3"
42
+ "typescript": "^6.0.3",
43
+ "vitest": "^4.1.5"
43
44
  }
44
45
  }