← Commit history
Update 6 file(s)
6 files changed
+68−20
bridge/BRIDGE_VERSION+1−1@@ -1 +1 @@-0.1.17+0.1.18
bridge/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "native-browser", "displayName": "Adom Native Browser (Chrome + Edge)",- "version": "0.1.17",+ "version": "0.1.18", "expectedExtensionVersion": "0.1.2", "description": "Drives the user's REAL signed-in Chrome/Edge via an installed extension (native-messaging transport). Mirrors the puppeteer bridge's verb surface as nbrowser_* but runs inside the actual logged-in profile, so cookies/SSO/saved-logins/captcha human-trust all work. The bridge binds an ephemeral loopback port (PORTS.md compliant) and writes a discovery file; the native-messaging host pumps stdio <-> this bridge; the extension never touches a TCP port.", "homepage": "https://wiki.adom.inc/adom/adom-browser-extension",
bridge/package.json+1−1@@ -1,6 +1,6 @@ { "name": "adom-native-browser-bridge",- "version": "0.1.17",+ "version": "0.1.18", "private": true, "description": "adom-desktop bridge for the Adom native-browser extension (Chrome + Edge)", "main": "server.js",
bridge/server.js+49−12@@ -306,25 +306,28 @@ const HELP = { // grabs it directly - no WSL distro). NEVER call HD here: AD must run standalone without HD. // AD 1.8.149 control-API discovery contract: env var first (AD injects it into every spawned bridge - // no file race, no stale port), then the host:port discovery file, then AD's fixed 47200..47209 range.-function discoverAdBase() {- if (process.env.ADOM_DIRECT_API_URL) return process.env.ADOM_DIRECT_API_URL.replace(/\/+$/, '');- if (process.env.ADOM_DIRECT_API_PORT) return `http://127.0.0.1:${process.env.ADOM_DIRECT_API_PORT}`;+// ⚠️ The spawn-time env values FREEZE the port AD had when it spawned us. AD SELF-UPDATES rotate the+// control port while we keep running (real incident, 2026-07-18: a Jul-16 bridge process hammered a dead+// port for hours - hwnd capture, badge painting, and repair ALL failed SILENTLY). So: env/override is+// only the first try; on any connection failure adCommand re-discovers from the FILE (which AD keeps+// current), retries once, caches the working base, and LOGS the outage + recovery.+let adBaseOverride = null; // set when file-rediscovery finds a LIVE base that differs from the env one+let adDownSince = 0;+function discoverAdBase(fileOnly) {+ if (!fileOnly) {+ if (adBaseOverride) return adBaseOverride;+ if (process.env.ADOM_DIRECT_API_URL) return process.env.ADOM_DIRECT_API_URL.replace(/\/+$/, '');+ if (process.env.ADOM_DIRECT_API_PORT) return `http://127.0.0.1:${process.env.ADOM_DIRECT_API_PORT}`;+ } try { const addr = fs.readFileSync(path.join(os.homedir(), '.adom', 'direct-api-port'), 'utf8').trim(); if (addr) return /^\d+$/.test(addr) ? `http://127.0.0.1:${addr}` : `http://${addr}`; // bare port (old) or host:port } catch {} return 'http://127.0.0.1:47200'; }-function adCommand(app, command, args, target) {+function adRequest(base, payload) { return new Promise((resolve) => {- let u; try { u = new URL('/command', discoverAdBase()); } catch { u = new URL('http://127.0.0.1:47200/command'); }- // app is OPTIONAL on AD 1.9.84+ (inferred) - omit it for top-level verbs like notify_user, keep it- // when a caller passes one (e.g. desktop_* recording delegation).- const payload = { command, args: args || {} };- if (app) payload.app = app;- // CROSS-AD routing: `target` (a peer clientName from `targets`, or "all") routes this call to a PEER- // AD via the relay - so a bridge running on a VM reaches the user on their LAPTOP. Omitted = local AD.- if (target) payload.target = target;+ let u; try { u = new URL('/command', base); } catch { u = new URL('http://127.0.0.1:47200/command'); } const data = Buffer.from(JSON.stringify(payload)); // X-Adom-Bridge-Token is ATTRIBUTION only (AD 1.9.84) - AD injects ADOM_BRIDGE_TOKEN into our spawn // env; sending it badges our outbound calls as "bridge" in the Activity Log. It is NOT an approval@@ -339,6 +342,40 @@ function adCommand(app, command, args, target) { req.write(data); req.end(); }); }+function adCommand(app, command, args, target) {+ // app is OPTIONAL on AD 1.9.84+ (inferred) - omit it for top-level verbs like notify_user, keep it+ // when a caller passes one (e.g. desktop_* recording delegation).+ const payload = { command, args: args || {} };+ if (app) payload.app = app;+ // CROSS-AD routing: `target` (a peer clientName from `targets`, or "all") routes this call to a PEER+ // AD via the relay - so a bridge running on a VM reaches the user on their LAPTOP. Omitted = local AD.+ if (target) payload.target = target;+ const first = discoverAdBase();+ return adRequest(first, payload).then((r) => {+ if (r.status !== 0) {+ if (adDownSince) { console.error(`[native-browser] AD link RECOVERED after ${Math.round((Date.now() - adDownSince) / 1000)}s`); logActivity({ verb: '_ad_link', ok: true, internal: true }); adDownSince = 0; }+ return r;+ }+ // Connection-level failure -> AD may have restarted on a new port. Re-discover from the FILE and retry.+ const fromFile = discoverAdBase(true);+ const retry = fromFile && fromFile !== first ? adRequest(fromFile, payload) : Promise.resolve(r);+ return retry.then((r2) => {+ if (r2.status !== 0) {+ adBaseOverride = fromFile;+ console.error(`[native-browser] AD port rotated (${first} dead) - rediscovered ${fromFile} from file, link restored`);+ logActivity({ verb: '_ad_link', ok: true, internal: true });+ adDownSince = 0;+ return r2;+ }+ if (!adDownSince) {+ adDownSince = Date.now();+ console.error(`[native-browser] AD UNREACHABLE at ${first}${fromFile !== first ? ` and ${fromFile}` : ''} (${(r2.body && r2.body.error) || 'connect failed'}) - taskbar badge/flash + window correlation degraded until the link recovers`);+ logActivity({ verb: '_ad_link', ok: false, errorCode: 'ad_unreachable', internal: true });+ }+ return r2;+ });+ });+} // ⭐ The hint we hand back whenever an AI tries to record WITHOUT choosing WGC. The AI is not assumed // to know the core adom-desktop window recorder exists, so we spell it out and explain - in detail - // why the extension's own screen-capture is a heinous last resort. (Lesson from a real thread that
dev-skills/developing-the-bridge/SKILL.md+11@@ -116,3 +116,14 @@ calls). The janitor keeps it bounded FOREVER: startup + every 6h, trim to newest days when >8MB; it also sweeps stale composited overlay icons (>30 days) from the icon temp dir. If you add a new persistent artifact (log, cache, icon, temp file), you MUST add it to the janitor in the same PR - unbounded growth on the user's machine is a regression, not a nice-to-have.++## ⭐ AD self-updates rotate the control port under you (2026-07-18 incident)+The env vars AD injects at spawn (ADOM_DIRECT_API_URL/_PORT) freeze the port AD had THEN. AD+self-updates restart it on a new port while your process keeps running - and every adCommand+(desktop_list_windows, desktop_taskbar, notify) starts failing. If those failures are swallowed, whole+features (badge, flash, hwnd correlation) die SILENTLY for hours; a Jul-16 bridge process did exactly+that until the user noticed a missing badge. Rules: (1) adCommand must re-discover from the+~/.adom/direct-api-port FILE on connection failure, retry, and cache the live base; (2) NEVER swallow an+AD-link failure silently - log the outage + recovery to the bridge log AND the activity trail (_ad_link+entries) so a missing badge is diagnosable in one query; (3) suspect stale strays: multiple node.exe+bridge processes days old = AD lost track of them - kill by PID, never by image name.
native-browser-bridge-manifest.json+5−5@@ -1,8 +1,8 @@ { "manifest_version": 1,- "version": "0.1.17",- "url": "https://wiki.adom.inc/download/adom/adom-browser-extension/0.1.17/native-browser-bridge-v0.1.17.bin",- "sha256": "6faae072cef04f7042669f1f9f99c72d70ae0768da5c31bdba122ad2c1999059",- "size": 70919,- "released_at": "2026-07-15T00:00:00Z"+ "version": "0.1.18",+ "url": "https://wiki.adom.inc/download/adom/adom-browser-extension/0.1.18/native-browser-bridge-v0.1.18.bin",+ "sha256": "9f21c9e3693ffc40f9decc282f7e187246b7f8a5285b27baf4ff01b1c7f35f56",+ "size": 71636,+ "released_at": "2026-07-18T00:00:00Z" }