switch off the browser relay, pending extraction into a plugin

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) <noreply@anthropic.com>
This commit is contained in:
2026-08-13 00:32:29 +00:00
co-authored by Claude Opus 5
parent 571d0a62ff
commit 9864fb1a49
7 changed files with 43 additions and 19 deletions
+29 -8
View File
@@ -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 {
+7 -1
View File
@@ -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'],
},
+4 -2
View File
@@ -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<typeof createRouter>
['/integrations', integrationsRouter],
['/queue', queueRouter],
['/email', emailRouter],
['/browser', browserRouter],
// ['/browser', browserRouter], // switched off — see server.tsx
['/bug-report', bugReportRouter],
['/agent-status', agentStatusRouter],
['/chat', chatRouter],