Files
claw/docs/superpowers/specs/2026-04-03-ws-browser-welcome-frame-design.md
木炎 bdf8e12246 feat: align browser callback runtime and export flows
Consolidate the browser task runtime around the callback path, add safer artifact opening for Zhihu exports, and cover the new service/browser flows with focused tests and supporting docs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 21:44:53 +08:00

106 lines
4.9 KiB
Markdown

# WS Browser Welcome Frame Compatibility Design
## Background
Manual smoke verification after the ws-native browser backend auth replacement showed that real `sgBrowser` sends a banner text frame immediately after the websocket connection is established:
- `Welcome! You are client #1`
The current ws-native path treats the first received text frame as a protocol status frame. In `src/browser/ws_backend.rs`, `WsBrowserBackend::invoke(...)` reads one text frame and immediately parses it as an integer status code. That works for the existing deterministic tests, but it fails against the real browser because the first frame is a human-readable welcome banner rather than `0` or another numeric status.
This means the auth replacement is working — the old `invalid hmac seed: session key must not be empty` error no longer appears — but real smoke still fails on protocol parsing.
## Goal
Make the ws service path tolerate exactly one initial welcome/banner text frame from the real browser websocket, without weakening the general ws protocol semantics.
## Non-goals
This change must not:
- Relax parsing of arbitrary non-protocol text frames
- Change `WsBrowserBackend` into a browser-specific parser for banners
- Affect the legacy pipe path
- Add retry loops or broader reconnection logic
- Change callback handling semantics
## Chosen approach
Handle the welcome banner only in `ServiceBrowserWsClient`.
### Why this layer
`ServiceBrowserWsClient` is already the real-browser adapter used only by the ws service path in `src/service/server.rs`. The welcome frame is a quirk of the real browser endpoint rather than a property of the shared ws protocol abstraction. Keeping the compatibility behavior in the service-side client preserves the stricter semantics of `WsBrowserBackend` for all other callers and test doubles.
## Behavioral rules
1. Only the first received text frame after establishing a browser websocket connection may be treated as a welcome/banner candidate.
2. If that first text frame matches the real banner shape (currently observed as `Welcome! You are client #1`), the client discards it and continues waiting for the actual protocol frame.
3. The welcome skip is one-time only per connection, not per request. Because `ServiceBrowserWsClient` holds a persistent socket, this state must survive multiple `invoke(...)` calls on the same underlying websocket.
4. After the welcome skip:
- status frames must still be numeric strings
- callback frames must still match the existing JSON-array callback protocol
- any other malformed frame remains a protocol error
5. Timeout, close/reset, and connect-failure semantics remain unchanged.
## Matching strategy
Use a narrow string check in `ServiceBrowserWsClient` for a welcome/banner frame:
- starts with `Welcome! You are client #`
This is intentionally strict. We are adapting one known real-browser behavior, not introducing a generic “ignore garbage text” mode.
## Tests
### New red tests
Add focused unit tests under `src/service/server.rs` tests:
1. Positive case:
- fake websocket server sends:
1. `Welcome! You are client #1`
2. `0`
- then `WsBrowserBackend.invoke(Action::Navigate, ...)` succeeds
2. Negative case:
- fake websocket server sends a different first text frame that does **not** match the known welcome prefix
- assert the call still fails as a protocol error rather than silently skipping the frame
The positive test must fail before the implementation change and pass after it. The negative test guards the non-goal that we are not introducing a generic “ignore arbitrary text” mode.
### Regression coverage
Re-run:
- `cargo test service::server::tests -- --nocapture`
- `cargo test --test browser_ws_backend_test -- --nocapture`
- `cargo test --test service_task_flow_test -- --nocapture`
If those pass, re-run the earlier mixed ws+pipe sweep to confirm no unexpected regression escaped the targeted checks.
## Risks and controls
### Risk: swallowing a legitimate protocol error
Control:
- only allow the one-time skip on the first received text frame
- only skip frames matching the known welcome prefix
### Risk: broadening behavior beyond service ws path
Control:
- keep the change entirely inside `ServiceBrowserWsClient`
- do not modify `WsBrowserBackend` parsing rules
## Acceptance criteria
The fix is complete only if all of the following are true:
1. The positive welcome-banner test fails before the change and passes after it.
2. The negative malformed-first-frame test proves that non-matching first text frames still fail as protocol errors.
3. Real ws service smoke no longer fails with `invalid browser status frame: Welcome! You are client #1` when using the configured real sgBrowser endpoint.
4. Existing ws backend tests remain green.
5. Existing service task-flow regression remains green.
6. Pipe behavior remains unchanged, verified by the mixed ws+pipe regression suite.