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],