← Commit history

docs: no em-dashes anywhere; dev and publish skills in step with 0.16.22

John Lauer ·79f9a54e68 ·1mo ago ·parent cc90282
5 files changed +97−100
AB-REQUESTS.md+7−8
@@ -3,21 +3,20 @@ Running tracker of ab fixes/features surfaced while building this bridge. Status as of 2026-06-19.  ## Shipped-- **1.8.148** — WGC per-window recorder (`desktop_record_window_*`); foreground-steal fix;+- **1.8.148** - WGC per-window recorder (`desktop_record_window_*`); foreground-steal fix;   spawn-storm backoff; `send_files` honors `dest`; `pull_file` path resolution.-- **1.8.149** — control-API discovery contract (`ADOM_DIRECT_API_URL` env injected into spawned+- **1.8.149** - control-API discovery contract (`ADOM_DIRECT_API_URL` env injected into spawned   bridges; `~/.adom/direct-api-port` = `host:port`); `bridge_install` cache-lock retry. - Containment **Phase 1** green-lit (kill-switch + `--safe-mode` + crash-loop guard + headroom gate). -## Open — first-class verbs to kill `shell_execute` inline-quoting friction+## Open - first-class verbs to kill `shell_execute` inline-quoting friction The #1 time-sink integrating this bridge is shell-quoting through JSON→bash→cmd→PowerShell. Each verb below replaces a class of quoting pain hit repeatedly this session: 1. **`run_script`** {interpreter: powershell|cmd|node|python, body, args?, cwd?} → {stdout,stderr,exitCode}.-   ab writes `body` to a temp file + runs it. THE big one — I currently `send_files` a `.ps1` then+   ab writes `body` to a temp file + runs it. THE big one - I currently `send_files` a `.ps1` then    `shell_execute -File` it as a workaround, every time.-2. **`read_file` / `write_file`** (text or base64, absolute path, resolved in ab's own context —-   fixes intermittent `%USERPROFILE%` non-expansion in `shell_execute`).-3. **`registry_get/set/delete`** (HKCU/HKLM) — replaces `reg query/add/delete` backslash-escaping+2. **`read_file` / `write_file`** (text or base64, absolute path, resolved in ab's own context - fixes intermittent `%USERPROFILE%` non-expansion in `shell_execute`).+3. **`registry_get/set/delete`** (HKCU/HKLM) - replaces `reg query/add/delete` backslash-escaping    (native-messaging host registration was painful + error-prone). 4. **`process_list`** {nameFilter?, pidFilter?} → [{pid,name,commandLine,parentPid,handles,memory}].    Replaces `tasklist`/`wmic`/`Get-Process`; `wmic` CommandLine returned empty (privilege). Needed@@ -28,7 +27,7 @@ Each verb below replaces a class of quoting pain hit repeatedly this session: 7. **`launch_process`** {path, args:[], env?, cwd?, window?} → {pid}. Args as an ARRAY (no quoted-string    parsing); replaces `Start-Process` arg-array pain. -## Open — bridge lifecycle question+## Open - bridge lifecycle question - Does ab keep an on-demand-spawned bridge **alive**, or tear it down when idle with no connected   client? The `native-browser` bridge must **persist** so the Chrome-launched native host can dial it   (host.json port). Observed: after an `nbrowser_*` call the bridge process is gone (node=0), which
PRIVACY.md+16−17
@@ -1,4 +1,4 @@-# Privacy Policy — Adom for Chrome & Edge+# Privacy Policy - Adom for Chrome & Edge  **Effective date:** June 25, 2026 **Publisher:** Adom Industries Inc. ("Adom", "we", "us")@@ -12,40 +12,40 @@ information, in plain terms. ## Who we are, and why this extension needs broad browser capabilities  **Adom Industries is an AI electronics-prototyping company.** We build a full, AI-native development-environment for the entire electronics industry — and we let developers everywhere build *on top of us*.+environment for the entire electronics industry - and we let developers everywhere build *on top of us*. Our ecosystem and our users are continuously writing and running code: **web-based oscilloscopes, web-based electronic design automation (EDA) and CAD software, and applications that drive and control-real circuit boards** — all in the browser, all day, every day.+real circuit boards** - all in the browser, all day, every day. -Because Adom is a **complete "vibe coding" development environment** — where an AI assistant and the-developer build software together in real time — the Extension is designed to **operate the browser the+Because Adom is a **complete "vibe coding" development environment** - where an AI assistant and the+developer build software together in real time - the Extension is designed to **operate the browser the same way a human developer does**: open and navigate pages, read and screenshot what's on screen, fill forms, click controls, inspect the DOM, run and debug JavaScript, and manage tabs, windows, downloads, and the developer's own signed-in sessions. It is the **same category of capability that AI coding-assistants such as the Claude browser extension use to drive a browser for the user** — the AI agent's+assistants such as the Claude browser extension use to drive a browser for the user** - the AI agent's hands inside the developer's own browser. The Extension's powerful permissions exist to deliver that functionality on your command.  ## What the Extension does -The Extension runs **inside your own browser** and acts on the commands you — or your Adom AI session,-working on your behalf — give it. To carry out a task, it can read and screenshot the pages you're+The Extension runs **inside your own browser** and acts on the commands you - or your Adom AI session,+working on your behalf - give it. To carry out a task, it can read and screenshot the pages you're working on, fill and click and type, run and debug JavaScript on the live page, manage your tabs, windows, and downloads, and use your existing signed-in sessions so your tools and vendor sites just-work. It acts on a page as part of a command you issue — it's your AI's hands, working where you point+work. It acts on a page as part of a command you issue - it's your AI's hands, working where you point them.  ## Where information goes, and how it's used  The Extension connects, through a Chrome **native-messaging host on your own computer**, to your local-**Bridge** application. Commands flow to the Extension and results — including screenshots and page-data — flow back to **your own active Adom AI session** so it can complete the task you asked for.+**Bridge** application. Commands flow to the Extension and results - including screenshots and page+data - flow back to **your own active Adom AI session** so it can complete the task you asked for. Depending on how you run Adom, that session may execute in **Adom cloud infrastructure you control**, in-which case the data needed for your task transits to that session to fulfill your request — the same way+which case the data needed for your task transits to that session to fulfill your request - the same way any cloud-based AI assistant receives the page it is helping you with.  That data is used for one thing: **carrying out the automation you request.** Adom's business is selling-electronics-prototyping and development tools — your browsing and the contents of your work stay yours,+electronics-prototyping and development tools - your browsing and the contents of your work stay yours, and are not used for advertising. Your saved passwords stay with the browser; the browser's own autofill enters them when a login is needed. @@ -53,8 +53,7 @@ enters them when a login is needed.  The screenshots and page data captured to complete a request live for as long as the request needs them and as part of your Adom session transcript, under that session's normal retention. The Extension itself-keeps only a small amount of local state in your browser — a map of your sessions to their windows —-which is cleared whenever you remove the Extension.+keeps only a small amount of local state in your browser - a map of your sessions to their windows - which is cleared whenever you remove the Extension.  ## Security @@ -65,7 +64,7 @@ drive it. Connections out to your Adom session use authenticated, encrypted tran ## Your controls  - Remove the Extension at any time (Chrome/Edge → Extensions → Remove) to end all access.-- The Extension only acts while your Bridge and an Adom AI session are connected — with them+- The Extension only acts while your Bridge and an Adom AI session are connected - with them   closed, it's idle. - For any question about your data, email **[email protected]**. @@ -80,4 +79,4 @@ updates will appear on this page.  ## Contact -**Adom Industries Inc.** — [email protected] — https://adom.inc+**Adom Industries Inc.** - [email protected] - https://adom.inc
dev-skills/developing-the-bridge/SKILL.md+42−43
@@ -19,18 +19,18 @@ top-level `_hint`/`related`/`_next` onto EVERY dispatch response. Add a verb → in the same commit, or it ships hint-less.  ## The 3 components (know which one you're touching)-1. **The bridge** (`bridge/server.js`) — ab spawns it (`spawn.kind:"node"`), so **ab provisions Node**+1. **The bridge** (`bridge/server.js`) - ab spawns it (`spawn.kind:"node"`), so **ab provisions Node**    (SDK runtime contract; you never install Node for it). Binds `127.0.0.1:0` on    `process.env.ADOM_BIND_HOST || '127.0.0.1'` (never `0.0.0.0` → no firewall prompt). Writes a    discovery file `~/.adom/bridges/native-browser/host.json` + `node-path.txt`. Serves `/status`    (health chip: led/summary/tooltip, AND a top-level `version` ab reads as `liveVersion`) + `/command`    for ab, plus a loopback TCP listener the host dials.-2. **The native-messaging host** (`host/native-host.js` via `native-host.bat`) — **Chrome launches it**,+2. **The native-messaging host** (`host/native-host.js` via `native-host.bat`) - **Chrome launches it**,    NOT ab, so it does NOT inherit ab's managed-runtime PATH. It reuses ab's Node via    `node-path.txt` (published by the bridge = `process.execPath`, backslash-normalized) and falls back-   to PATH `node`. This is the ONLY place system-Node dependence could bite a fresh machine — keep the+   to PATH `node`. This is the ONLY place system-Node dependence could bite a fresh machine - keep the    `.bat`'s two-branch resolution intact.-3. **The MV3 extension** (`extension/`) — runs inside Chrome/Edge, no runtime. SW `connectNative` →+3. **The MV3 extension** (`extension/`) - runs inside Chrome/Edge, no runtime. SW `connectNative` →    host → bridge loopback. Deterministic ID (`manifest.json` pins `key`) so the host manifest's    `allowed_origins` is known ahead of time. @@ -52,7 +52,7 @@ Data path: container → relay → ab → bridge `/command` → loopback → hos  ## Verb contract Request `{command, args}`; response `{success, output(JSON string), error?, errorCode?}` + the hints.-`nbrowser_readiness` is READ-ONLY (never spawns/installs) — mirror `bridge_readiness`. Long ops: return+`nbrowser_readiness` is READ-ONLY (never spawns/installs) - mirror `bridge_readiness`. Long ops: return a job id + `statusVerb`, don't block. Declare per-verb budgets >60s in `bridge.json` `timeouts` (v1.9.79) so ab doesn't cut them at 60s. `nbrowser_describe` returns the full catalog. @@ -61,8 +61,7 @@ a job id + `statusVerb`, don't block. Declare per-verb budgets >60s in `bridge.j discovery file; `GET 127.0.0.1:<port>/status` → led/summary/tooltip + `version`. End-to-end: `nbrowser_readiness` through ab (`--target <laptop>`), then open bg window → screenshot → close. -**Testing on John's VMs (winvm / ADOMBASELINE):** John ALWAYS keeps an RDP session open to his VMs —-**assume it; don't pause to confirm his RDP view before driving a VM over the `--target <vm>` ab path.**+**Testing on John's VMs (winvm / ADOMBASELINE):** John ALWAYS keeps an RDP session open to his VMs - **assume it; don't pause to confirm his RDP view before driving a VM over the `--target <vm>` ab path.** Full fresh-VM onboarding = install bridge (`bridge_install {manifestUrl}`), deploy `extension/`+`host/`, write `host/host-manifest.json` (path→`native-host.bat`, `allowed_origins`→`chrome-extension://<keyed-id>/`), register the native host in HKCU (`…\Microsoft\Edge\NativeMessagingHosts` / `…\Google\Chrome\…`), then load@@ -88,7 +87,7 @@ classification, the GUI card, `liveVersion`/`staleProcess` reporting. YOU own: v missing capability (new lifecycle verb, relay field) → file against ab; a misbehaving `nbrowser_*` verb → fix here. -## Safety invariants (never regress — one wiped a user's other AI threads)+## Safety invariants (never regress - one wiped a user's other AI threads) Only ever drive a window YOU opened; NEVER navigate/reload/close the user's existing window/tab (incl. OS `desktop_navigate`). Background by default; foreground is the user's choice. `nbrowser_credentials` read-only, never returns passwords; auto-login gated on `confirm:true`+`host:`. Honor the blocklist.@@ -159,43 +158,43 @@ Post-launch minimize (ShowWindow SW_MINIMIZE returns focus to the user automatic is the pattern - the user opens the window when THEY are ready. foreground:true stays gated behind foregroundReason + notify, exactly like the extension path. -## ⭐ Opening native browser windows without nbe — the hard lessons (John, 2026-07-19)+## ⭐ Opening native browser windows without nbe - the hard lessons (John, 2026-07-19) Spent a whole session flailing at "open a background window in the live profile" and CRASHED the user's Edge (and the AI thread running in it) FOUR times. The final root cause was small and the user named it before I did. Read this before writing any browser-launch code. -**THE ACTUAL ROOT CAUSE: opening was always fine — the BACKGROUNDING call crashed the browser.** I was+**THE ACTUAL ROOT CAUSE: opening was always fine - the BACKGROUNDING call crashed the browser.** I was minimizing the freshly-launched window with `ShowWindow(hwnd, SW_MINIMIZE)` **inside a 30ms poll loop,-the instant the window first appeared** — i.e. hammering ShowWindow on a browser window while Chromium+the instant the window first appeared** - i.e. hammering ShowWindow on a browser window while Chromium was still constructing it. That race crashed the whole browser PROCESS (all its windows, including the-workspace), then Edge session-restored (windows come back with NEW hwnds — which looks like "you closed+workspace), then Edge session-restored (windows come back with NEW hwnds - which looks like "you closed it"). The launch itself never restarted anything. I wasted the session on exotic theories (singleton takeover, session isolation, "Edge is fundamentally unsafe") instead of looking at my own two-line backgrounding code and the ONE working reference implementation.  **READ THE WORKING IMPLEMENTATION FIRST.** Hydrogen Desktop's browser picker already opens native-browser windows reliably — `hydrogen-desktop/src-tauri/src/control.rs`, the `/open-in-profile` handler:-for Edge it just does `cmd /c start msedge <url>` — **no `--profile-directory`, no `--new-window`, and+browser windows reliably - `hydrogen-desktop/src-tauri/src/control.rs`, the `/open-in-profile` handler:+for Edge it just does `cmd /c start msedge <url>` - **no `--profile-directory`, no `--new-window`, and it never minimizes.** When a working impl exists in the codebase, copy it before hand-rolling P/Invoke. -**The correct recipe (shipped 0.1.41, verified live on the user's single-profile Edge — window opened+**The correct recipe (shipped 0.1.41, verified live on the user's single-profile Edge - window opened minimized/background, workspace hwnd unchanged, no crash):** 1. **Launch clean, like Hydrogen:** don't pass `--profile-directory=Default` (only pass a profile dir when it    is NOT the default). Args as a PowerShell ARRAY, never an embedded-quote string (`'--profile-directory=    "Default"'` passes LITERAL quotes to Chromium). e.g. `Start-Process $exe -ArgumentList @('--new-window',$url)`. 2. **Detect the new window READ-ONLY.** Poll `EnumWindows` for a genuinely-new `Chrome_WidgetWin` hwnd-   (exclude a pre-snapshot + a PROTECTED set: the workspace/editor window — titles `- Editor -|Hydrogen|-   code-server|claude.ai|galliaApril` for the right brand — and other threads' live-session hwnds).+   (exclude a pre-snapshot + a PROTECTED set: the workspace/editor window - titles `- Editor -|Hydrogen|+   code-server|claude.ai|galliaApril` for the right brand - and other threads' live-session hwnds).    **Do NOT touch any window inside this loop.** 3. **Background with ONE settled, gentle call.** After the window is found, `Start-Sleep 700ms` to let    Chromium FULLY build it, then ONE `ShowWindow(hwnd, SW_SHOWMINNOACTIVE=7)` (minimize WITHOUT stealing-   focus). One call, on a settled window, no loop. This is the crash-safe part — the whole bug was here.+   focus). One call, on a settled window, no loop. This is the crash-safe part - the whole bug was here.  **Do NOT build a footgun, and do NOT wall off a feature you can't root-cause.** I twice over-reacted: (a) added a `confirmLiveProfile:true` override to a guard, then passed it myself on the live profile; (b) when crashes continued I re-added a HARD refuse that blocked the exact thing the user asked for. Both were me guessing instead of fixing the real (tiny) bug. If you're refusing a whole capability, you don't-understand the failure yet — keep debugging.+understand the failure yet - keep debugging.  **Verify `node --check server.js` PASSES before EVERY release.** I shipped 0.1.34 and 0.1.36 with syntax errors because the release commands ran after a failed check in the same `&&` chain wasn't actually@@ -204,47 +203,47 @@ verification a hard, separate gate: `node --check server.js || exit 1` on its ow  **Never demo a destructive-capable op against the user's LIVE working profile.** John's workspace runs inside Edge's only profile; every Edge open I did was aimed at the very session he was using. If there is-no safe surface to prove something on, say "I can't safely prove this on your machine" — do not gamble.+no safe surface to prove something on, say "I can't safely prove this on your machine" - do not gamble.  **Isolated `--user-data-dir` on a work machine is NOT clean.** A fresh Edge profile auto-signs-in the Windows work account and pops Microsoft's "we're syncing your data across devices" first-run dialog. It's confined to that throwaway user-data-dir (delete it after), and it does NOT flip sync on the real-Default profile — but it looks alarming to the user. Prefer not launching extra browser instances on the+Default profile - but it looks alarming to the user. Prefer not launching extra browser instances on the user's machine at all when you can avoid it.  **Diagnostics gotchas:** `Get-Process msedge | MainWindowTitle` is unreliable for a multi-window browser-(one process, one MainWindow) — use `EnumWindows` + `GetWindowText` to truly enumerate windows. And-`$pid` is a READ-ONLY PowerShell automatic variable (the shell's own PID) — never assign to it; your+(one process, one MainWindow) - use `EnumWindows` + `GetWindowText` to truly enumerate windows. And+`$pid` is a READ-ONLY PowerShell automatic variable (the shell's own PID) - never assign to it; your "pid" readouts will be garbage. When checking "did I just kill a window", enumerate windows directly; `desktop_list_windows` returning 0 during ab churn is "can't see", not "gone".  **The honest product truth to keep surfacing:** for the user's REAL signed-in session, opening a window-via **nbe** (`chrome.windows.create` FROM INSIDE the running browser — no process launch at all) is+via **nbe** (`chrome.windows.create` FROM INSIDE the running browser - no process launch at all) is strictly safer than any external launch. Extension-free `open_os_window` is the graceful-degradation wedge; nbe is the clean path. Say so.  ## ⭐ The badge MUST land on the driven window, never a same-profile PWA (John, 2026-07-21)-The taskbar badge is a SECURITY signal — the user has to know which exact browser window an AI is+The taskbar badge is a SECURITY signal - the user has to know which exact browser window an AI is driving. It broke because `badgeSpawnsIn` re-correlated a driven session to "the first OS window whose title contains the profile email" (`profileHwnd`). On a work profile that matches the **"Inbox - [email protected] - Adom Mail"/Gmail PWA** window, so the badge landed on the PWA's taskbar button instead of the Chrome window the user was watching = the driven window showed NO badge. This is the SAME wrong-PWA bug that `touchBadge` warns about, reintroduced in the correlation helper. Rules: - **Correlate each session to its OWN window by the page title, and ONLY match real browser windows**-  (title ends `- Google Chrome` / `- Microsoft Edge`). Never badge by profile-email-in-title — PWAs+  (title ends `- Google Chrome` / `- Microsoft Edge`). Never badge by profile-email-in-title - PWAs   (Adom Mail, Google Messages, Gmail-as-app) carry the email too and are NOT the browser window. - **Prefer an exact title PREFIX** over a substring: the OS title is `<page title> - <Browser>`, so the   session's page title is a prefix. Two windows on the same site (e.g. two Gusto pages) share a-  substring but only one is the prefix — prefix-match disambiguates; `matchHwnd` does this now.+  substring but only one is the prefix - prefix-match disambiguates; `matchHwnd` does this now. - After a bridge RESTART, active sessions lose their captured hwnd; the badge self-heals on the   session's next drive verb (`touchBadge` → `extQuery(list_windows)` → `badgeSpawnsIn` → `matchHwnd`).   I restarted the bridge ~20× shipping other fixes that day, so every active thread was un-badged until-  its next verb — expected, but verify the self-heal actually re-correlates to the RIGHT window.+  its next verb - expected, but verify the self-heal actually re-correlates to the RIGHT window. -## ⭐ A non-2xx from ab is a FAILURE — never treat it as success (2026-07-21, fusion-bridge report)+## ⭐ A non-2xx from ab is a FAILURE - never treat it as success (2026-07-21, fusion-bridge report) `adCommand` only treated a *connection* failure (`status === 0`) as an error. Any HTTP error (403, 500) fell through as "success" with an error body, so `chromeWindowsDetailed()` parsed no windows and returned-`[]`. Result: `nbrowser_os_windows` reported **0 windows while ab saw 21**, and — far worse — the taskbar+`[]`. Result: `nbrowser_os_windows` reported **0 windows while ab saw 21**, and - far worse - the taskbar BADGE and FLASH silently stopped working, because they go through the same call. Nothing logged. - **Root cause: the bridge token.** ab injects `ADOM_BRIDGE_TOKEN` at spawn and **rotates it when ab   self-updates**. A long-running bridge keeps sending the stale one and ab answers **403 to every call**@@ -256,9 +255,9 @@ BADGE and FLASH silently stopped working, because they go through the same call.  ## ⭐ Log every badge paint + flash (John, 2026-07-21) The taskbar badge is a security signal; a silent failure is indistinguishable from "no AI is driving."-`taskbar()` now logs an `_taskbar` activity entry for EVERY call — `hwnd`, `ops`+`taskbar()` now logs an `_taskbar` activity entry for EVERY call - `hwnd`, `ops` (`overlay:composite|overlay:adom|overlay:none|flash:until_focused|progress:x`), `ok`, and the HTTP-status on failure — and `nbrowser_activity` carries `hwnd`/`ops` through so you can query+status on failure - and `nbrowser_activity` carries `hwnd`/`ops` through so you can query `{verb:"_taskbar"}` and see exactly what was painted where and whether ab accepted it.  ## ⭐ A load-unpacked extension has an EMPTY manifest.name on disk (2026-07-21)@@ -272,20 +271,20 @@ two verbs disagree about whether the extension is installed.  ## ⭐ Report "did it launch" separately from "did I get an hwnd" (2026-07-21) `nbrowser_open_os_window` returned `ok:false` when the browser GROUPED the page into an existing window-(no new hwnd within 8s) even though the page opened — for a consent flow that IS success, and a caller+(no new hwnd within 8s) even though the page opened - for a consent flow that IS success, and a caller was forced to regex our `_hint` to detect it. Contract now: **`ok:true` + `launched:true`** whenever the launch fired, with **`hwndResolved:bool` + `hwndReason`** reported separately. Never make a caller parse-prose to learn an outcome — give them a stable boolean.+prose to learn an outcome - give them a stable boolean.  ## ⭐ Two badges, and SHOW the human the foreground reason (John, 2026-07-22) The overlay badge must distinguish the two capability layers, because conflating them is a security ambiguity ("is an AI reading this window, or did it just open it?"):-- **LAYER 2 — solid teal Adom favicon** (`adom-favicon.png`) = the extension is installed and the AI has+- **LAYER 2 - solid teal Adom favicon** (`adom-favicon.png`) = the extension is installed and the AI has   LIVE CONTROL (read/click/type). Painted at `open_window`, re-asserted by every drive verb.-- **LAYER 1 — HOLLOW teal outline** (`adom-favicon-hollow.png`) = extension-free `open_os_window`: Adom+- **LAYER 1 - HOLLOW teal outline** (`adom-favicon-hollow.png`) = extension-free `open_os_window`: Adom   OPENED this window but cannot see or drive it; the USER is in control. Filled-vs-outline is the   convention everyone already reads, and it survives at the true ~24px overlay size (a merely *muted*-  variant did not — it was indistinguishable from layer 2, which defeats the whole point).+  variant did not - it was indistinguishable from layer 2, which defeats the whole point). Both use the SAME lifecycle: `applyBadge` arms the 3-min idle expiry, so they self-clear back to the profile's plain avatar and never linger. @@ -299,17 +298,17 @@ erodes trust in an agent that can touch their desktop.  ## ⭐ Announce EVERY user-visible open, at the BOTTOM of the screen (John, 2026-07-22) (Duration: **3s** - 2s was tried live and read too fast to actually absorb.)-**The rule:** if a native-browser open is going to be visible to the user — because that is simply how-the browser behaves — **allow it**, do not fight it, but put up a **3-second desktop caption naming the+**The rule:** if a native-browser open is going to be visible to the user - because that is simply how+the browser behaves - **allow it**, do not fight it, but put up a **3-second desktop caption naming the reason**. Fighting the browser produced worse bugs than the foregrounding ever did; being unannounced is the actual failure. Three cases must announce (`announceVisibleOpen`): 1. an explicit foreground grant (`foreground:true` / `userRequestedForeground` + reason),-2. **a page the browser GROUPED into an existing window** — no new window exists to background, so it+2. **a page the browser GROUPED into an existing window** - no new window exists to background, so it    surfaces regardless (this is what made a Fusion sign-in page appear unannounced),-3. `open_tab` with `active:true` — the tab lands in front of the user inside an existing window.+3. `open_tab` with `active:true` - the tab lands in front of the user inside an existing window. -**Position: `bottom`, never `top`.** The user's eye is already at the taskbar — that is where the overlay-badge and the orange flash appear — so a bottom caption sits beside the signals it explains instead of+**Position: `bottom`, never `top`.** The user's eye is already at the taskbar - that is where the overlay+badge and the orange flash appear - so a bottom caption sits beside the signals it explains instead of competing with them from the far edge of the screen.  **The reason is never optional and never blank:** it falls back to the session's `purpose`, which
install/README.md+5−5
@@ -2,19 +2,19 @@  Three registrations on a managed Windows machine (golden image / ah / winvm): -1. **Bridge → ab `bridges-cache`** — `bridge/` copied to `%LOCALAPPDATA%\Adom Bridge\bridges-cache\+1. **Bridge → ab `bridges-cache`** - `bridge/` copied to `%LOCALAPPDATA%\Adom Bridge\bridges-cache\    native-browser\`; Bridge's registry auto-discovers it and routes `nbrowser_*` to it.-2. **Native-messaging host (HKCU, no UAC)** — render `host-manifest.json` from the template (absolute+2. **Native-messaging host (HKCU, no UAC)** - render `host-manifest.json` from the template (absolute    `native-host.bat` path + the extension ID in `allowed_origins`) and register it under    `HKCU\Software\<vendor>\NativeMessagingHosts\inc.adom.native_browser` for Chrome and/or Edge.-3. **Extension force-install (HKLM, managed only)** — `ExtensionInstallForcelist` → `<id>;<update.xml>`.+3. **Extension force-install (HKLM, managed only)** - `ExtensionInstallForcelist` → `<id>;<update.xml>`.  `install.ps1` does all three. **Run it OUTSIDE Claude Code.**  ## ⚠ The wcifs / AppContainer trap (read adom-bridge/CLAUDE.md)  For Claude-spawned tooling, `%LOCALAPPDATA%\Adom Bridge\...` is silently redirected to a per-package-shadow — PowerShell, `Start-Process`, and the Write/Edit tool all hit the shadow, not the real path.+shadow - PowerShell, `Start-Process`, and the Write/Edit tool all hit the shadow, not the real path. So **from a Claude session, do the bridges-cache copy via WSL2 `cp`** (bypasses wcifs):  ```bash@@ -27,7 +27,7 @@ cp -r bridge/* "/c/Users/<user>/AppData/Local/Adom Bridge/bridges-cache/native-b ## Better long-term: wiki-streamed bridge  Bridge already streams bridges from the wiki (`bridge_cache.rs` / `WIKI_STATIC_BASE`). Publishing-`bridge/` as a wiki package lets ab pull + cache it with no manual copy — the preferred delivery once+`bridge/` as a wiki package lets ab pull + cache it with no manual copy - the preferred delivery once this stabilizes. See the plan's distribution section.  ## update.xml
publish-skills/publishing-the-bridge/SKILL.md+27−27
@@ -8,13 +8,13 @@ https://wiki.adom.inc/adom/adom-bridge-sdk # Publishing the native-browser bridge  ## What "the bridge" actually is (3 artifacts, 3 places)-1. **Runtime** — `bridge/` (server.js + bridge.json) zipped and published as a **Release asset**+1. **Runtime** - `bridge/` (server.js + bridge.json) zipped and published as a **Release asset**    (`release create` + `release upload --platform any`), with a manifest on `/files` whose `url` points    at the Release `/download/` path. ab streams it to `bridges-cache/native-browser/` and spawns it.-   (Node runtime is source-only; ab provisions Node — see the SDK "Runtime contract".)-2. **Skills pkg** — the `skills/<name>/SKILL.md`s, published via `adom-wiki pkg publish --org adom`,+   (Node runtime is source-only; ab provisions Node - see the SDK "Runtime contract".)+2. **Skills pkg** - the `skills/<name>/SKILL.md`s, published via `adom-wiki pkg publish --org adom`,    installed into the container's `~/.claude/skills` + `~/.codex/skills`.-3. **Discovery** — `discovery_triggers` + `discovery_pitch` on `page.json`.+3. **Discovery** - `discovery_triggers` + `discovery_pitch` on `page.json`.  ## bridge.json contracts we declare (and one we deliberately don't) - **`timeouts`** (ab v1.9.79+): ab hard-caps every bridge verb at 60s unless you declare per-verb HTTP@@ -23,22 +23,22 @@ https://wiki.adom.inc/adom/adom-bridge-sdk   + `prefixes [{screenshot:120},{record:120}]`, `default:60`, and `statusVerb:"nbrowser_status"` (ab   returns `{stillRunning:true,statusVerb}` on its own timeout → caller polls). Keep the describe   `timeoutSeconds` in `server.js` aligned. Note: Bridge's **CLI has its own ~30s client-  timeout**, separate from ab's bridge budget — you can't observe a >30s verb via a bare CLI call.-- **`detect` — intentionally OMITTED.** `detect` is for a HOST APP ab detects/gates. Native-browser+  timeout**, separate from ab's bridge budget - you can't observe a >30s verb via a bare CLI call.+- **`detect` - intentionally OMITTED.** `detect` is for a HOST APP ab detects/gates. Native-browser   drives the user's REAL browser via the extension, so the authoritative "is it usable" signal is-  **extension-connected** (`nbrowser_readiness`), not a browser binary on disk — and our read-only+  **extension-connected** (`nbrowser_readiness`), not a browser binary on disk - and our read-only   verbs are always forwarded regardless. A single `hostApp:"Google Chrome"` would MISREPORT the common   Edge-only machine (verified real: ADOMBASELINE runs fine on Edge with no Chrome), and Edge is   universal on Windows so "browser installed" is near-zero signal. So we do NOT declare `detect`. -## Release the RUNTIME — the exact, proven sequence+## Release the RUNTIME - the exact, proven sequence Deps: none (core `http`/`net`), so the payload is source-only (no `node_modules`).  1. **Bump the version in THREE places, together** (they drift if you don't):    - `bridge/bridge.json` `version` (what ab reads + reports),    - `bridge/package.json` `version`,    - `bridge/BRIDGE_VERSION`.-   `server.js` reads `VERSION` from `bridge.json` (authoritative) so it can't drift — keep it that way.+   `server.js` reads `VERSION` from `bridge.json` (authoritative) so it can't drift - keep it that way.    ab reads that same `version` off your `/status` JSON as `liveVersion`, so keep `/status` accurate. 1b. **Regenerate `bridge.json` `verbs[]` from `VERB_META`. NEVER hand-edit it.** `verbs[]` is what ab    can see; a verb missing from it is invisible to every caller even though the code works, with no@@ -54,61 +54,61 @@ Deps: none (core `http`/`net`), so the payload is source-only (no `node_modules` 1c. **Run the gates before anything else.** `node --check bridge/server.js` (a syntax error has shipped    twice and takes the bridge down) and `node tools/check-guard-coverage.mjs`, which now fails on verb    drift, a dead declared verb, an out-of-order `verbs[]`, or `bridge.json` version != `BRIDGE_VERSION`.-2. **Build the zip with files at the ROOT** (there is no `zip` binary — use Python `zipfile`):+2. **Build the zip with files at the ROOT** (there is no `zip` binary - use Python `zipfile`):    members = `bridge.json`, `package.json`, `server.js`, `BRIDGE_VERSION`, `adom-favicon.png`. Name it-   `native-browser-bridge-v<ver>.zip` (as of 0.1.29 — it always WAS a zip; the old `.bin` naming was a+   `native-browser-bridge-v<ver>.zip` (as of 0.1.29 - it always WAS a zip; the old `.bin` naming was a    scar from the retired `/files` base64 API which rejected `.zip`. Release assets are extension-agnostic    and ab only follows the manifest `url`, so no ab-side change was needed for the rename; old `.bin`    releases stay valid at their existing URLs).-3. **The runtime is a RELEASE ASSET, not a `/files` blob** (SDK "two-artifact" rule — fusion + pup do+3. **The runtime is a RELEASE ASSET, not a `/files` blob** (SDK "two-artifact" rule - fusion + pup do    exactly this). Cut a Release for the bridge version + upload the zip:    - `adom-wiki release create adom/nb-bridge <ver> --title "native-browser bridge runtime v<ver>"`    - `adom-wiki release upload adom/nb-bridge <ver> native-browser-bridge-v<ver>.zip --platform any`-   The download then serves anon at `https://wiki.adom.inc/download/adom/nb-bridge/<ver>/native-browser-bridge-v<ver>.zip` (verify: `curl -sI` → 200). (The pkg's own release stays 0.3.x; the bridge release is 0.0.x — separate version streams on the same page, that's expected.)+   The download then serves anon at `https://wiki.adom.inc/download/adom/nb-bridge/<ver>/native-browser-bridge-v<ver>.zip` (verify: `curl -sI` → 200). (The pkg's own release stays 0.3.x; the bridge release is 0.0.x - separate version streams on the same page, that's expected.) 4. **sha256 + byte size** → write `native-browser-bridge-manifest.json` with the `url` pointing at that    **`/download/` path** (NOT a `/files` blob):    `{manifest_version:1, version, url:"https://wiki.adom.inc/download/adom/nb-bridge/<ver>/native-browser-bridge-v<ver>.zip", sha256, size, released_at}`. 5. **Push manifest + source as TEXT** with `adom-wiki repo push adom-native-browser --files native-browser-bridge-manifest.json bridge/bridge.json bridge/package.json bridge/server.js bridge/BRIDGE_VERSION ...` (the manifest lives on `/files`; only the binary is off it). 6. **Land it:** `refresh_bridges {name:"native-browser"}` → verify `nbrowser_readiness` routes and    `bridge_check_updates`/`bridge_list` show `liveVersion==<ver>` (no `staleProcess`). *(bridge_check_updates-   intermittently omits native-browser after a Bridge self-update — trust readiness + bridge_list.)*+   intermittently omits native-browser after a Bridge self-update - trust readiness + bridge_list.)* 7b. **RUN THE VERB BATTERY before announcing** (testing-the-bridge): `node tools/verb-battery.mjs --target <box>` must exit 0 - it catches what the static gates cannot (a verb that parses, declares, describes, and then fails against a real browser). 7. **NEVER `repo rm` a runtime zip while the manifest still points at it.** The old `/files` `.bin` uploads-   are the dead anti-pattern (they piled up to 22 stale versions once) — do NOT re-add them. If any linger,+   are the dead anti-pattern (they piled up to 22 stale versions once) - do NOT re-add them. If any linger,    migrate as a SEQUENCE: release-upload → repoint manifest → verify `/download/` serves → THEN delete the    `/files` copies. The **pkg's** `package.json` (the skills tarball, not the zip) declares    `dependencies:{"adom/adom-bridge":"^2.x"}` (so `pkg install` pulls the ab CLI + core skills the-   container needs) — NOT the server's npm deps (the bridge has none anyway). The **zip's**-   `bridge/package.json` holds the server's runtime deps (none for us — core `http`/`net`).+   container needs) - NOT the server's npm deps (the bridge has none anyway). The **zip's**+   `bridge/package.json` holds the server's runtime deps (none for us - core `http`/`net`). -## Reaping is fixed (ab v1.9.76) — the old orphan trap is gone+## Reaping is fixed (ab v1.9.76) - the old orphan trap is gone Earlier, `refresh_bridges` could leave a stale PID serving old code (a node bridge doesn't lock the cache dir, so a clean swap didn't reap). **ab v1.9.76 fixed this:** `refresh_bridges` now force-restarts a running bridge after an update, `restart_bridge {name}` is a real verb (kill tracked+port-orphan + respawn on the stable port), and `bridge_check_updates` surfaces `liveVersion` + `staleProcess` directly. So: after a release, just `refresh_bridges` (or `restart_bridge` for an explicit restart) and-confirm `liveVersion` matches — no more digging in `host.json` or manual `taskkill`.+confirm `liveVersion` matches - no more digging in `host.json` or manual `taskkill`. (`bridge_pause`/`bridge_resume` are a ROUTING gate only; they never kill the process.) -## Publish the SKILLS pkg — USER skills ONLY (dev/publish live in the repo)-`adom-wiki pkg publish --org adom`. **The pkg ships ONLY your USER skills** — the ones a general Adom+## Publish the SKILLS pkg - USER skills ONLY (dev/publish live in the repo)+`adom-wiki pkg publish --org adom`. **The pkg ships ONLY your USER skills** - the ones a general Adom user's agent needs to DRIVE the bridge. That's the pkg's whole job: a container `pkg install`s it to USE you. Each is a real `skills/<name>/SKILL.md` with a `Parent skill:` first line; list **every user-skill path EXPLICITLY** (no globs) in `package.json` `files[]` AND `install.sh`/`uninstall.sh`.-- **DEV + PUBLISH skills do NOT go in the pkg — they live in your source REPO** (this skill, and+- **DEV + PUBLISH skills do NOT go in the pkg - they live in your source REPO** (this skill, and   `developing-the-bridge`). A developer who clones the source to EDIT the bridge gets them as repo files;   the normal user never needs them. So: **not in `files[]`, not in `install.sh`, and do NOT scope them-  with `user-invocable:false`** — that convention still SHIPS them and bloats every container install.+  with `user-invocable:false`** - that convention still SHIPS them and bloats every container install.   Keep them under `dev-skills/` + `publish-skills/` in the repo (the wiki Skills page reads those folders-  for its Dev/Publish tabs). **Open-vs-closed source is irrelevant** — it only sets whether the repo is+  for its Dev/Publish tabs). **Open-vs-closed source is irrelevant** - it only sets whether the repo is   publicly readable; either way dev/publish stay in the repo, never the pkg. (Bridge core does   exactly this: its RELEASE/WIKI_PUBLISH/SIGNING skills live only in source, never in the published pkg.)-- **The pkg's `package.json` declares `dependencies:{"adom/adom-bridge":"^2.x"}`** — `pkg install`+- **The pkg's `package.json` declares `dependencies:{"adom/adom-bridge":"^2.x"}`** - `pkg install`   resolves it npm-style, so installing our skill pkg also pulls the ab CLI + core skills the container-  needs to drive us. (Do NOT put the SERVER's npm deps here — those ride in the zip's `bridge/package.json`.)+  needs to drive us. (Do NOT put the SERVER's npm deps here - those ride in the zip's `bridge/package.json`.)  ## Discovery-Edit `page.json` `discovery_triggers` (30+ user-task phrasings, login/forms niche — pup can't do+Edit `page.json` `discovery_triggers` (30+ user-task phrasings, login/forms niche - pup can't do logins) + `discovery_pitch` (an INSTRUCTION). Push `page.json` **LAST** (a pkg publish clobbers it and drops the hero). Verify: `adom-wiki discover preview --json "<7 real user queries>"` → land top-3.