Closed feature request

CLI still carries bridge-specific code paths (pup/fusion/kicad wrappers, timeouts, hints); replace with the generic dynamic route + manifest budgets

John Lauer · 1mo ago ·closed by John Lauer

Follow-up to #211 (the hand-written bridge verb catalogs, removed in 2.1.144). The same rule, "ab stays generic; a bridge owns everything about its verbs", is still broken by CLI-side code in cli/src/commands.rs::run_inner:

  • pup: ~60 pup_* / browser_* match arms with per-verb timeouts, plus CLI-side wrappers (browser_open_window URL analysis and hints, browser_fetch_url, pup_lower_os_window fallback, pup_forward_first). relay::browser_command is a pup-specific route.
  • fusion360: fusion_start / fusion_dismiss_blocking_dialogs "forward first, fall back to ab's local implementation" (fusion_forward_first, ~400 lines of local Fusion launch orchestration, fusion_close_window), fusion_apply_instapcb_rules / fusion_load_design_rules, and a per-verb timeout table for fusion_* that "must match ADDIN_COMMAND_TIMEOUTS in bridges/fusion360/server.py" (a file that no longer exists).
  • kicad: a 140 s budget and post-hoc _hints for kicad_open_* / window_info / screenshot_all appended by the CLI.
  • help_printer still names pup/kicad/fusion in is_help_namespace (kept so adom-bridge pup prints the pointer rather than an unknown-verb error; fine, but the list should come from the box's bridge_list too).

None of this is wrong today, which is why it survived, but every line is a copy of a bridge's knowledge that will drift the way the catalog did. The generic replacement already exists: a bridge verb is relay::desktop_command("dynamic", verb, args, budget), where the budget comes from the bridge's manifest (timeouts.default / timeouts.verbs, which bridge.json already supports and ab already reads server-side), and any hint belongs in the bridge's OWN reply. Plan: (1) make the dynamic path read the manifest budget through bridge_info (cached per process) so the CLI needs no per-verb table; (2) delete the pup/fusion/kicad arms and the local Fusion fallback (fusion-bridge 1.10 answers every one of those verbs); (3) move the three CLI-appended kicad hints into kicad-bridge's replies (issue on adom/kicad-bridge). Needs a pup + fusion + kicad smoke run on a fleet box before shipping, so it is its own release rather than part of #211.

3 Replies

John Lauer · 1mo ago

Shipped in 2.1.146 (insiders; pkg 2.1.146 published). The CLI now has ONE route for every bridge verb and nothing bridge-specific left in it; 2,738 lines out, 170 in.

Removed from the CLI: the ~60 pup_* arms and their wrappers (relay::browser_command, the URL hints keyed by verb, the screenshot/fetch save paths, the native lower-window fallback, the bare credential_* names that pup's own contract calls pup_credentials), the kicad_ arm (140 s budget + three appended hints), the fusion_ passthrough with its timeout table, the fusion_start / fusion_dismiss_blocking_dialogs local fallbacks (~400 lines of Fusion launch orchestration), fusion_load_design_rules / fusion_apply_instapcb_rules (they read paths in the retired Python tree), the Fusion-only watch wrapper, and the timeouts verb's hand-written table. Removed from ab: timeout_meta::builtin_timeout_secs and default_status_verb (the fusion/kicad tables).

What replaced them: the generic arm sends ("dynamic", verb) with a wait sized from the BRIDGE's declared bridge.json timeouts (bridge_list carries timeouts + statusVerb now; exact bare verb, then prefix rule, then default, +20 s margin so ab's structured timed_out reaches the caller; a bridge with no block gets a 660 s ceiling; an unclaimed prefix 30 s). Three CLI-side conveniences are generic in run(): a base64 PNG in any reply is saved to the screenshot cache (_full_res skips the resize), a caller-side saveTo receives a reply's bodyBase64, a localhost/file url arg gets the cross-machine hint. timeouts lists each bridge's declared block live. The ab skill lost its 600-line Fusion body and the KiCad/Fusion troubleshooting blocks, VERBS.md its KiCad/Fusion/pup tables.

Proof from gallia's Linux CLI (pkg 2.1.146) against arav-rog (ab 2.1.146, real update path, exe signature Valid):

timeouts -> longVerbs table: absent; bridges (live, declared):
   altium          statusVerb altium_status        default 60   verbs 17
   fusion360       statusVerb fusion_get_app_state default 60   verbs 31
   kicad           statusVerb kicad_status         default 130  verbs 14
   native-browser  statusVerb nbrowser_status      default 60   verbs 4
   puppeteer       statusVerb pup_readiness        default 60   verbs 5
pup_wait {ms:40000}  -> ok, status ok, wall 40 s   (the old generic arm cut every unlisted bridge verb at 30 s; pup declares wait: 125 s)
pup_fetch_url {url, saveTo:"/tmp/p2145.html"} -> savedTo /tmp/p2145.html, 559 bytes on disk, bodyBase64 stripped
pup_navigate {url:"http://localhost:1234/"} -> the cross-machine hint, worded for any verb
kicad_list_versions -> 1 version;  fusion_readiness -> ok;  altium_readiness -> ok, installed;  nbrowser_readiness -> ok
credential_list -> unknown_verb (pup's own name is pup_credentials);  watch -> unknown_verb (use responseMode:"stream")
help pup -> "'pup' verbs belong to the puppeteer bridge, not to Adom Bridge." + the live calls

Also exercised through the same route: pup_open_window, pup_screenshot (pup returns its file on the desktop, filePath; the CLI saves a reply's base64 when a bridge sends one), pup_screenshot_full_res, pup_close_window.

Filed for the bridges: adom/kicad-bridge#116 (the three hints the CLI used to append, if still wanted) and adom/fusion-bridge#96 (the two CLI-only design-rule verbs, and bridge.json vs fusion_describe).

Known leftovers on the ab-core side, not in this ship: desktop_install_kicad / desktop_install_node and the kicad_bridge_request / fusion_bridge_request app paths (routing plumbing for the two bundled bridges). They are named in CLAUDE.md as the next targets of the same rule.

John Lauer closed this · 1mo ago
John Lauer · 1mo ago

Second half, the DESKTOP (John: "get rid of the desktop's special paths too"): shipped in 2.1.147 and 2.1.148 (insiders; pkg 2.1.148).

Deleted from the desktop app: kicad_bridge.rs, fusion_bridge.rs, browser_bridge.rs, kicad_ipc.rs (the dedicated spawn/port/lifecycle modules for the three originally-bundled bridges), the dedicated request handlers (handle_kicad_command, kicad_bridge_request, handle_kicad_ipc_command, fusion_bridge_request, handle_fusion_command, handle_browser_command), the boot-time by-name starts, the per-name LED emits in the health loop and get_bridge_statuses, restart_bridge's three arms, BUNDLED_SPECS in the duplicate-instance collapse, the kicad/fusion host-app detection fallbacks and the Chrome-for-Testing asset check in bridge_readiness, desktop_install_kicad. Deleted from the installer and the repo: the bridge payloads (bridges/kicad, fusion360, puppeteer, native-browser: labelled seed copies of repos that live elsewhere; 4,900 files, 76 MB of pup node_modules); the NSIS stages 45 files instead of 211 and is 1.8 MB smaller.

Generic now: a legacy app name (kicad, fusion360, browser, from a CLI older than 2.1.145) restores the prefixed verb and takes the same dynamic route as everything else; every registered bridge gets one LED rule, one restart path, one supervisor (persistent: true), one duplicate-instance collapse keyed by its own name; the runtime finders live in runtime_cache and look on PATH only. A fresh install gets its default bridges from bridges/seeds.json (name + manifest URL: the only bridge facts ab carries) through the same path as bridge_install; at boot ab removes any bridge dir an older installer left under its resources.

Proof on arav-rog (2.1.148 via the real update path, exe signature Valid):

bridge_list: altium/fusion360/kicad/native-browser/puppeteer all source=cache, running; hd = ab's own stub
kicad_list_versions, fusion_readiness, pup_readiness, nbrowser_readiness, altium_readiness -> all ok through the one route
direct API with the LEGACY app names: {app:kicad, command:list_versions} -> count 1; {app:fusion360, command:readiness} -> ok; {app:browser, command:pup_readiness} -> ok
boot prune: "removed stale bundled bridge dir 'fusion360' / 'kicad' / 'native-browser' / 'puppeteer'"; resource bridges dir now: hd, seeds.json
seed path: bridge_uninstall kicad -> gone from bridge_list, kicad_list_versions -> bridge_not_installed; restart -> 21 s later kicad 1.0.51 is back (source cache, running, installed from seeds.json's manifest URL); kicad_list_versions -> 1
GUI: bridge chips render from the registry, LEDs green

Also fixed on the way: the desktop's inline_commands_match_dispatcher test had been failing (seven dispatchable verbs, permission_history among them, were not advertised in /commands); they are now.

What ab still knows about bridges, in full: bridges/seeds.json (four names + four URLs) and bridges/hd/bridge.json (its own Hydrogen proxy). Adding a bridge to ab is one line in seeds.json.

John Lauer · 1mo ago

Fleet test of the #212 series, all three test boxes on 2.1.150 through the real update path (running process version checked, not just the exe on disk), 2026-09-22.

Boxes: arav-rog (6 bridges), ConfRoomROG (8, including the python-kind grok and codex), winvm (5). AdomLapper is John's own machine and was not driven.

per box: bridge_list all cache-installed + running; hd = ab's own stub
kicad_list_versions / fusion_readiness / pup_readiness / nbrowser_readiness / altium_readiness -> ok on every box that has the bridge
  (winvm has no altium: "Unknown verb 'altium_readiness' - no installed bridge claims it" = correct)
legacy app names through the direct API (what a CLI older than 2.1.145 sends):
  {app:"kicad", command:"list_versions"} -> count 1 | {app:"fusion360", command:"readiness"} -> ok | {app:"browser", command:"pup_readiness"} -> ok
real pup workload on crr: open_window, navigate, eval (document.title = "Home - Adom Wiki"), screenshot 1568x772, pup_wait 40 s (pup declares 125 s; the old generic arm cut at 30 s), close - all ok
boot prune on each box: removed stale bundled dirs; resource bridges dir is now exactly: hd, seeds.json

Verb discovery, end to end (measured on crr):

commands                      -> 8 bridges, each with version, status, prefixes, verb NAMES, describeVerb
commands {"bridge":"<name>"}  -> the bridge's own describe: kicad 118, fusion360 160, puppeteer 85,
                                 native-browser 87, altium 123, grok 28, codex 40
kicad_nonexistent_verb        -> unknown_verb + the bridge's own valid-command list
blender_render                -> bridge_not_installed + "belongs to the 'blender' bridge ... install it from <its wiki page>"
bridge_info {"name":"kicad"}  -> docs https://wiki.adom.inc/adom/kicad-bridge, skillPkg adom/kicad-bridge, manifestUrl

Two bugs this found, and what happened to them:

  1. A reinstalled bridge came back paused (winvm: native-browser returned from its seed 150 s after an uninstall, paused, refusing every verb). bridge_uninstall pauses a bridge before reaping it; the flag is in memory keyed by NAME and was only cleared when a bundled copy remained - which nothing does any more. Fixed in 2.1.150. Re-tested: uninstall native-browser on winvm -> back 257 s later, running, nbrowser_readiness ok. (2.1.149 claimed this fix but shipped without the code change: the patch script died on a later assertion before writing the file. Caught by re-testing, which is why the version numbers jump.)

  2. sync_skills installs nothing on Windows - the pkg's install.sh runs through bash with a mangled path (exit 127). Pre-existing (the verb is untouched by this series), filed as #213. It matters more now that ab carries no bridge documentation, so it is worth fixing; the discovery path that works everywhere is the commands / commands {bridge} / bridge_info chain above.

Behaviour worth knowing (not a bug, but new in effect): uninstalling a SEED bridge (kicad, fusion360, puppeteer, native-browser) is not permanent - it returns at the next launch, the next 4-hourly sync, or immediately on refresh_bridges {"name"}. That matches the old semantics (a bundled bridge kept serving from the installer copy after an uninstall), it just happens later and over the network now. If a user should be able to remove a seed for good, say so and I will add a remembered-removal marker.

Filed on the bridges' side: adom/pup-bridge#124 (its bridge.json lists 55 verbs while pup_describe has 85, so 30 verbs its skill teaches are invisible in the plain listing; and pup_screenshot answers with filePath instead of ab's fullPath/safePath, so the image never auto-pulls to a container AI). adom/kicad-bridge#116 and adom/fusion-bridge#96 were filed earlier in the series; fusion already fixed its manifest/describe mismatch in 1.10.36 (both now say 160).

Log in to reply.