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

4.9 KiB

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.