← Commit history
bridge/BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.1.17\ No newline at end of file+0.1.18\ No newline at end of file
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" }