diff options
| author | Tianren Dong <tianrendong.dtr@gmail.com> | 2026-05-16 13:13:32 -0400 |
|---|---|---|
| committer | Tianren Dong <tianrendong.dtr@gmail.com> | 2026-05-16 13:13:48 -0400 |
| commit | b2ca8f8c343281c0263bda4c043cbe7f0d7d442e (patch) | |
| tree | c37f274c112a202af86144fd6034a8a54d7933eb /docs | |
| parent | ea567819d835d4704d9fb38cd114e0e5b231759e (diff) | |
Revert 0.16.x and 0.17.x: restore tree to v0.15.20 state
Reverts the pairing + signed-envelopes work (0.16.0-0.16.1), the
standalone daemon refactor (0.17.0-0.17.3), and assorted patches.
These were unpublished from npm; this commit restores the working
tree to match the v0.15.20 release so the branch tip and the npm
'latest' dist-tag are consistent again.
Tags v0.16.0 through v0.17.3 are preserved as recovery anchors;
the work is reachable via 'git checkout v0.17.3' if ever needed.
Tree-equivalent to v0.15.20.
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/DESIGN-0.17-daemon.md | 106 |
1 files changed, 0 insertions, 106 deletions
diff --git a/docs/DESIGN-0.17-daemon.md b/docs/DESIGN-0.17-daemon.md deleted file mode 100644 index 2e7f115..0000000 --- a/docs/DESIGN-0.17-daemon.md +++ /dev/null @@ -1,106 +0,0 @@ -# pi-chrome 0.17 — standalone versioned daemon - -**Status:** design draft. Implementing in this same release branch. - -## Goals - -1. **Decouple the bridge from any Pi session lifecycle.** Killing a Pi session must never break Chrome control for other sessions. -2. **Eliminate the "owner vs client" topology.** Every Pi session should be a uniform client. -3. **Make upgrades self-healing.** When `pi update` lands a new pi-chrome version, the next session that starts must Just Work without any manual `kill` or `/chrome onboard` ritual. -4. **No new runtime deps.** Keep the zero-deps promise. - -## Non-goals (deferred to 0.18) - -- Transport change. Stays HTTP + long-poll for 0.17. WebSocket lands separately on top of the daemon. -- Native messaging. Not pursued; see SECURITY.md threat model rationale. - -## Architecture - -``` - ┌─────────────┐ ┌────────────────────────────┐ - │ Chrome ext │ HTTPS │ pi-chrome daemon │ ─────┐ - │ (SW polls ├────────►│ ~/.cache/pi-chrome/ │ │ - │ /next) │ │ v0.17.0/daemon.cjs │ │ stat() - └─────────────┘ │ Listens :17318 │ │ on each - │ Owns BridgeAuth + state │ │ accept - └─────────────┬──────────────┘ │ - │ │ - signed /command (HTTP) │ - │ │ - ┌─────────────┴──────────────┐ │ - │ Pi session N (any version) │ ──────┘ - │ extension/index.ts as │ - │ daemon supervisor + client │ - └────────────────────────────┘ -``` - -### Lifecycle - -- **First Pi session in a pi-chrome version family** spawns the daemon if no compatible one is running. Spawn is detached (`spawn(node, [daemon.cjs], { detached: true, stdio: 'ignore' })`), the parent Pi doesn't own the daemon's process group, and `unref()`'s it so Pi can exit independently. -- **Daemon stays alive** as long as it has work to do (active extension poll, or pending peer connections). After `IDLE_SHUTDOWN_MS` (default: 10 min) of zero activity, daemon exits cleanly. -- **Subsequent Pi sessions** at `session_start` probe `/status`: - - Healthy + compatible version → use as-is. - - Unresponsive or incompatible version → kill it, spawn the right-version daemon. -- **`pi update`** changes the on-disk pi-chrome version. Next Pi session sees mismatch, kills old daemon, spawns new. **No in-process hot reload** — the old daemon's in-flight commands are allowed to drain (or time out normally) and the next request to the new daemon retries fresh. - -### Versioned daemon path - -Daemon is copied to `~/.cache/pi-chrome/<version>/daemon.cjs` on Pi session_start (idempotent, only writes if absent or shasum mismatches). Pi spawns the daemon from that path, never directly from the npm install location: - -- Survives `nvm use` switches. -- Multiple pi-chrome versions can coexist on disk (the active daemon corresponds to the most recent Pi session that started). Old version dirs are garbage-collected when the daemon detects them. -- The daemon executable is a self-contained CommonJS bundle (built at publish time, or generated by a postinstall hook). No `require()` from anywhere except `node:` builtins. - -### Why not ESM? - -CommonJS keeps the daemon a single file with no entry-point ceremony, no `package.json` next to it, no resolver surprises across Node versions. The cost is irrelevant — the daemon has no third-party deps. - -## Protocol (unchanged from 0.16.1) - -The HTTP endpoints and signed-envelope contract are byte-identical to 0.16.1. The daemon is structurally a refactor of the existing `ChromeProfileBridge` class: - -- `/status` (now adds `daemonPid`, `daemonStartedAt`) -- `/next` (long-poll, signed `bridge->ext` response) -- `/result` (signed `ext->bridge` body) -- `/command` (signed `peer->owner` / `owner->peer`) -- `/pair` (invite window, signed response with invite secret) - -The only addition is a new `/heartbeat` endpoint (cheap GET, returns `{ok:true,ts}`) used by Pi clients to keep the daemon's idle timer alive and to detect daemon health without consuming a full command round-trip. - -### Configuration file unchanged - -`~/.config/pi/chrome-bridge.json` keeps the same shape (`{protocol, bridgeId, extensionId, extensionPairKey, brokerKey}`). The daemon owns the read/write of that file; Pi clients only consult it to derive `brokerKey` for signing peer→owner requests. - -The cross-process `fs.watch` + `refreshIfStale` machinery becomes daemon-local — `chrome-bridge.json` is now exclusively the daemon's data file. Pi clients receive pairing state via `/status` instead of reading the file directly. - -## Extension changes - -**None.** The extension still polls `127.0.0.1:17318`. From its perspective the only thing that changed is "the process behind that port restarts itself occasionally during pi-chrome upgrades." - -The extension's existing version-mismatch reload path (`x-pi-chrome-version` header → `chrome.runtime.reload()`) handles its own upgrade story independently. - -## Pi-side client surface - -`extensions/chrome-profile-bridge/index.ts` is split into: - -1. **`bridge-client.ts`** — Pi-side talker. `BridgeClient.send(action, params, timeoutMs)` signs and posts to `/command` of the local daemon, verifies the response signature, returns the result. No long-poll loop, no HTTP server. ~200 LOC. -2. **`daemon-supervisor.ts`** — at `session_start`: ensures daemon is running, spawns if not, registers a graceful keepalive. ~100 LOC. -3. **`daemon.cjs`** (built artifact) — the bridge process. Internally identical to today's `ChromeProfileBridge` minus the "client mode" branch. Single self-contained file shipped in npm tarball + copied to versioned cache dir on first use. -4. **`index.ts`** — pi-extension entry. Imports `bridge-client` and `daemon-supervisor`, registers the chrome_* tools, wires `/chrome` commands. ~500 LOC (everything else moved out). - -## Open questions - -1. **Daemon log location.** Default: `~/.cache/pi-chrome/<version>/daemon.log` (rotated at 1 MiB). Exposed via `/chrome doctor --logs`. -2. **What happens during a takeover with in-flight Pi commands?** Old daemon dies → its in-flight `/command` returns a TCP RST to the Pi client → Pi client retries against the new daemon. Implement explicit retry-once on connection-refused/RST inside `BridgeClient.send` with a budget (5 s) before surfacing the error. -3. **Pairing across daemon restarts.** `~/.config/pi/chrome-bridge.json` persists. New daemon `load()`s it, picks up the same `extensionPairKey`/`brokerKey`. Extension never has to re-pair across a daemon restart. -4. **Concurrent daemon spawn race.** Two Pi sessions starting simultaneously could each try to spawn a daemon. Mitigation: spawn-then-bind, second one hits EADDRINUSE inside the daemon itself and exits cleanly; supervisor probes `/status` after spawn to confirm one is up. -5. **Garbage-collecting old versioned dirs.** Daemon at startup deletes `~/.cache/pi-chrome/v*/` directories older than 30 days. - -## Migration - -0.16.1 → 0.17: -- First Pi session on 0.17 detects an existing 0.16.x bridge on `:17318`, version-mismatches, kills it, spawns the 0.17 daemon. Same as the existing auto-takeover code in 0.16.1. -- Extension picks up new bridge on next poll, sees same `expectedExtensionVersion`, no reload. -- Pairing state in `~/.config/pi/chrome-bridge.json` is preserved across the transition. - -User-visible: nothing. `pi update` → next Pi session → done. |
