From 9864fb1a495f8b1ae2e378b2012a2467d533d7a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Thu, 13 Aug 2026 00:32:29 +0000 Subject: [PATCH] switch off the browser relay, pending extraction into a plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BROWSER_RELAY_PORT is gone and the second listener no longer starts. The Chrome extension, api/browser/ and the /browser screen all stay on disk — this is going to be extracted, and deleting it means writing it again. Three things had to move together, and the middle one would have failed the boot on its own: server.tsx the listener, commented out with the variable name recorded hono.ts the /api/browser mount, closed registry.ts the 'browser' capability's claim on /browser, dropped assertCapabilityTotality checks both directions: check 2 refuses to start on a capability claiming a prefix nothing serves. Unmounting the router alone would have left the registry describing it, and the server would not have come up. The capability itself survives because it also claims /scrape, which shares nothing with the relay — it launches its own headless chromium through playwright and never speaks to the extension. The comment in server.tsx carries the two facts that are not recoverable by reading the remaining code. First, the port is an INPUT TO A CREDENTIAL: relay-auth.ts derives each extension's token as HMAC(JWT_SECRET, 'officer-browser-relay-v1:${port}:${userId}:${salt}'), so bringing the relay back on a different number silently invalidates every paired browser — reported by the extension as "Relay not reachable", which SETUP.md blames on a wrong address, port or token. Second, it cannot come back as a kernel-assigned port:0 like the other sidecars: the extension is configured by hand and stores the value, so a port that moves each restart breaks the pairing each restart. Left alone deliberately: the /browser route in App.tsx, its Dock entry, and the Settings → Browser Relay panel. They will not work against a closed endpoint. Removing them is frontend work for the extraction, not part of switching the listener off. .env is down to PORT and POSTGRES_URL. Not typechecked (empty node_modules, frozen installs). Every changed file parses; the setup section was run and writes two variables. Co-Authored-By: Claude Opus 5 (1M context) --- .env.example | 1 - CLAUDE.md | 3 ++- scripts/setup/officer-setup.sh | 4 +-- scripts/setup/officer-setup/lib/env.sh | 3 --- src/server.tsx | 37 ++++++++++++++++++++------ src/servers/capabilities/registry.ts | 8 +++++- src/servers/hono.ts | 6 +++-- 7 files changed, 43 insertions(+), 19 deletions(-) diff --git a/.env.example b/.env.example index 3257fb9f..4d7bbadc 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,5 @@ # What officer-setup writes. Everything below this block is optional, or is on its way out. PORT=9000 -BROWSER_RELAY_PORT=18792 POSTGRES_URL="postgres://postgres:password@localhost:5432/officer" # ── Moving to the secret store ───────────────────────────────────────────────────────────────── diff --git a/CLAUDE.md b/CLAUDE.md index 9d423ff6..ccce1bca 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -42,7 +42,8 @@ One Bun process (`src/server.tsx`) serves everything: - eight WebSocket providers — terminal, chat, task-runner, pipeline, cliamp, cliamp-audio, desktop, vault — plus a sidecar registration socket. `terminal` is a byte relay onto the pty sidecar's own listener, not a translating bridge; `vault` is the same shape onto Vaultwarden's notifications hub. -- a browser relay on its own port (`BROWSER_RELAY_PORT`, default 18792) +- ~~a browser relay on its own port~~ — switched off 2026-08-13, awaiting extraction into a plugin. + The extension and `api/browser/` stay on disk; the listener and the `/api/browser` mount do not. Long-running and privileged work lives in **sidecars**: separate processes that dial back in over `/api/sidecar/register` and are tracked in `src/servers/sidecar-registry.ts`. PM2 runs them diff --git a/scripts/setup/officer-setup.sh b/scripts/setup/officer-setup.sh index 905c2563..df768686 100755 --- a/scripts/setup/officer-setup.sh +++ b/scripts/setup/officer-setup.sh @@ -460,7 +460,6 @@ if ! skip; then # Read back before anything is asked; existing values become the defaults. ENV_PORT="$(env_get PORT)" - ENV_BROWSER_RELAY_PORT="$(env_get BROWSER_RELAY_PORT)" if env_exists; then echo " exists — its values are the defaults below" @@ -471,11 +470,10 @@ if ! skip; then # ── what is asked ── echo "" ask_required ENV_PORT "Port Officer listens on" "${ENV_PORT:-9000}" - ENV_BROWSER_RELAY_PORT="${ENV_BROWSER_RELAY_PORT:-18792}" echo "" echo " to write:" - echo " PORT=${ENV_PORT} BROWSER_RELAY_PORT=${ENV_BROWSER_RELAY_PORT}" + echo " PORT=${ENV_PORT}" echo " POSTGRES_URL=${POSTGRES_URL%%:*}://…" echo "" echo " the install root is not written here — the platform derives it as the" diff --git a/scripts/setup/officer-setup/lib/env.sh b/scripts/setup/officer-setup/lib/env.sh index 077b6d38..8523c0a5 100644 --- a/scripts/setup/officer-setup/lib/env.sh +++ b/scripts/setup/officer-setup/lib/env.sh @@ -65,9 +65,6 @@ write_env() { PORT="${ENV_PORT}" -# The browser relay listens on its own port, separate from the app. -BROWSER_RELAY_PORT="${ENV_BROWSER_RELAY_PORT}" - POSTGRES_URL="${POSTGRES_URL}" ENVF diff --git a/src/server.tsx b/src/server.tsx index 9833f2ba..d4e756b1 100644 --- a/src/server.tsx +++ b/src/server.tsx @@ -18,7 +18,7 @@ import { cliampWebsocket, cliampAudioWebsocket } from './servers/api/cliamp/rela import { desktopWebsocket } from './servers/api/desktop/websocket'; import { vaultWebsocket, upgradeVaultWs } from './servers/api/vault/websocket'; import officerWeb from './apps/officer-web/index.gen.html'; -import { startBrowserRelay } from './servers/api/browser/relay'; +// import { startBrowserRelay } from './servers/api/browser/relay'; // switched off — see below import { registerSidecar, unregisterSidecar, handleSidecarMessage } from './servers/sidecar-registry'; import './servers/api/chat/opencode/sidecar-server'; // subscribe to the opencode sidecar's port report import type { SidecarRegistration } from './servers/sidecar/registration-protocol'; @@ -345,13 +345,34 @@ const server = serve({ console.log(`🚀 Server running at ${server.url}`); -const BROWSER_RELAY_PORT = Number(process.env.BROWSER_RELAY_PORT ?? '18792'); -try { - await startBrowserRelay(BROWSER_RELAY_PORT); - console.log(`[browser-relay] listening on port ${BROWSER_RELAY_PORT}`); -} catch (err) { - console.error('[browser-relay] failed to start:', err instanceof Error ? err.message : err); -} +// ── The browser relay is not started. Deliberate, 2026-08-13. ── +// +// The Chrome extension, its CDP bridge (api/browser/) and the /browser screen all remain on disk: this +// is going to be extracted into a plugin, and deleting it would mean writing it again. What is switched +// off is the second listener and the /api/browser mount (see hono.ts) — the platform serves one port. +// +// It used to read BROWSER_RELAY_PORT, default 18792. The name is recorded here because there is nothing +// left to grep for, and whoever does the extraction needs to know what it was called. +// +// ── Read this before turning it back on ── +// +// The port is not only configuration. relay-auth.ts derives each extension's token as +// HMAC(JWT_SECRET, `officer-browser-relay-v1:${port}:${userId}:${salt}`), so the port is an INPUT TO A +// CREDENTIAL. Bringing the relay back on a different number silently invalidates every paired browser, +// and the extension reports it as "Relay not reachable" — which SETUP.md attributes to a wrong address, +// port or token. Re-pairing means re-copying from Settings → Browser Relay. +// +// Whatever it comes back as, it cannot be a kernel-assigned port:0 the way the other sidecars are. The +// extension is configured by hand and stores the value, so a port that changes each restart breaks the +// pairing every restart. It has to be predictable — PORT + 2 is the shape the Anthropic proxy already +// uses for the same reason. +// +// try { +// await startBrowserRelay(BROWSER_RELAY_PORT); +// console.log(`[browser-relay] listening on port ${BROWSER_RELAY_PORT}`); +// } catch (err) { +// console.error('[browser-relay] failed to start:', err instanceof Error ? err.message : err); +// } // Initialize queue engine in API server process import { diff --git a/src/servers/capabilities/registry.ts b/src/servers/capabilities/registry.ts index e9d2508e..5f9c19bd 100644 --- a/src/servers/capabilities/registry.ts +++ b/src/servers/capabilities/registry.ts @@ -327,12 +327,18 @@ export const CAPABILITIES: Capability[] = [ ws: ['desktop'], routes: ['/desktop'], }, + // `/browser` — the Chrome-extension relay — is unmounted as of 2026-08-13 and dropped from this claim, + // because check 2 of assertCapabilityTotality refuses to boot on a capability claiming a prefix nothing + // serves. Put both back together or neither. + // + // `/scrape` is untouched and is what this capability still covers. It shares nothing with the relay: it + // launches its own headless chromium through playwright and never speaks to the extension. { key: 'browser', label: 'Browser', description: 'Drives a real browser on the host', kind: 'execution', - api: ['/browser', '/scrape'], + api: ['/scrape'], routes: ['/browser'], }, diff --git a/src/servers/hono.ts b/src/servers/hono.ts index de1f833d..8521c143 100644 --- a/src/servers/hono.ts +++ b/src/servers/hono.ts @@ -50,7 +50,9 @@ import { dockRouter } from './api/dock/dock'; import { integrationsRouter, googleCallbackHandler } from './api/integrations/integrations'; import { queueRouter } from './api/queue/queue'; import { emailRouter } from './api/email/router'; -import { browserRouter } from './api/browser/router'; +// The browser relay is switched off — see server.tsx. Restoring this mount means restoring the +// registry's claim on '/browser' in the same commit, or assertCapabilityTotality refuses to boot. +// import { browserRouter } from './api/browser/router'; import { desktopRouter } from './api/desktop/rest'; import { bugReportRouter } from './api/bug-report/bug-report'; import { agentStatusRouter } from './api/agent-status/router'; @@ -221,7 +223,7 @@ const PROTECTED_MOUNTS: [prefix: string, router: ReturnType ['/integrations', integrationsRouter], ['/queue', queueRouter], ['/email', emailRouter], - ['/browser', browserRouter], + // ['/browser', browserRouter], // switched off — see server.tsx ['/bug-report', bugReportRouter], ['/agent-status', agentStatusRouter], ['/chat', chatRouter],