app
Adom Bridge
Public Made by Adomby adom
Adom Bridge unleashes your AI onto your full PC: full power, full safety. The AI breaks out of the container it runs in and onto your real machine, managing and running your entire laptop as you: launch apps, move files, drive any window, control KiCad, Fusion 360 or your real signed-in browser. Works with any AI, cloud or local (Claude Desktop, Claude Code, Codex), no Hydrogen required. Bridge and its bridges are an operating system for AI, with managed Node and Python runtimes, and a human-onl
← Commit history
picker-targeted demos + live per-step proof + log dedup
1 file changed
+50−12
web-control-demo.html+50−12@@ -227,6 +227,7 @@ for (const w of mine) { function doVerb(el, command, args, target, summarize) { var body = { command: command }; if (args) body.args = args; if (target === undefined) target = chosenTarget(); if (target) body.target = target;+ if (target) { body.args = body.args || {}; if (!body.args.caller) body.args.caller = { aiThread: "web-control demo page", containerName: selfLabel() }; } var who = machineLabel(target || null); setRes(el, "pending", "asking permission...", "on " + esc(who) + " - approve the Adom Bridge toast"); logLine("POST /command " + JSON.stringify(body) + " [on " + who + "]");@@ -243,6 +244,9 @@ for (const w of mine) { // POST that returns the flattened JSON. Optional target. function postVerb(command, args, target) { var body = { command: command }; if (args) body.args = args; if (target) body.target = target;+ // A relayed verb arrives at the OTHER machine via_relay, where ab requires+ // caller identity - name this page as the thread so the run is attributed.+ if (target) { body.args = body.args || {}; if (!body.args.caller) body.args.caller = { aiThread: "web-control demo page", containerName: selfLabel() }; } logLine("POST /command " + JSON.stringify(body)); return fetch(AB + "/command", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }) .then(function (r) { return r.json().then(function (j) { return { s: r.status, j: flat(j) }; }); });@@ -407,15 +411,22 @@ for (const w of mine) { // Drive a bridge's demo verb: start (background) -> poll progress -> render the // returned window array. cfg = { startCmd, startArgs, pollArgs, stateCmd }. function runBridgeDemo(c, cfg) {+ // The demo HONORS the machine picker: pick ConfRoomROG and the tour runs+ // THERE (this page's ab relays the verbs to that machine). Captured ONCE+ // at click so changing the picker mid-run cannot split the run.+ var target = chosenTarget();+ if (target === "all") { setRes(c.res, "err", "pick ONE machine", "a demo streams per-machine progress - fan-out is not supported here"); return; }+ var who = machineLabel(target);+ c._who = who; c.btn.disabled = true; cardLog(c.card).textContent = ""; var sb = c.card.querySelector("div.shots"); if (sb) sb.innerHTML = ""; setProgress(c.card, 0, "starting ...");- setRes(c.res, "pending", "asking permission (choose “Allow 1 hr”) ...", "runs on " + esc(selfLabel()) + " - the demo runs many steps; a 1-hour grant keeps the progress silent");- clog(c, "Target machine: " + selfLabel() + " (demos always run on the machine serving this page)");+ setRes(c.res, "pending", "asking permission (choose “Allow 1 hr”) ...", "runs on " + esc(who) + " - the demo runs many steps; a 1-hour grant keeps the progress silent");+ clog(c, "Target machine: " + who + (target ? " (relayed through this machine's Adom Bridge)" : " (the machine serving this page)")); clog(c, "Starting the bridge's own demo verb: " + cfg.startCmd + " " + JSON.stringify(cfg.startArgs || {})); // 1) Start the background tour (cross-origin -> ONE consent prompt).- postVerb(cfg.startCmd, cfg.startArgs || {}).then(function (res) {+ postVerb(cfg.startCmd, cfg.startArgs || {}, target).then(function (res) { if (res.s !== 200 || !res.j || (res.j.started !== true && res.j.success !== true)) { if (res.j && res.j.errorCode === "web_control_disabled") { setRes(c.res, "err", "Refused: web control OFF", "re-enable in Settings → Web Control"); } else { setRes(c.res, "err", "could not start", (res.j && (res.j.error || res.j.errorCode)) || ("HTTP " + res.s)); }@@ -434,26 +445,51 @@ for (const w of mine) { clog(c, "Started (bridge estimates ~" + est + "s). Polling for live progress ..."); setProgress(c.card, res.j.percent || 2, "preparing ...", 0, 0, 0, est); setRes(c.res, "pending", "running the demo ...", "usually ~" + est + "s - watch the bar");+ // LIVE PROOF: at every step transition, snap the app's current state via+ // the bridge's state verb and pin it in the card - the user SEES each+ // stage of the tour as it happens, not just text + a final gallery+ // (John, 2026-08-16). One snap in flight at a time; a slow snap skips+ // steps rather than queueing behind the tour.+ var lastStep = null, snapBusy = false;+ function maybeSnap(label) {+ if (!cfg.snap || snapBusy) return;+ snapBusy = true;+ postVerb(cfg.snap.cmd, Object.assign({}, cfg.snap.args || {}), target).then(function (sr) {+ snapBusy = false;+ if (!(sr.s === 200 && sr.j)) return;+ var withImg = findShots(sr.j).filter(function (s) { return s && (s.image || s.base64 || s.png); });+ if (!withImg.length) return;+ var s = withImg[withImg.length - 1]; // the newest window is the step's surface+ addShot(c.card, s.image || s.base64 || s.png, "live: " + label);+ clog(c, " [proof] captured \"" + (s.title || label) + "\"");+ }).catch(function () { snapBusy = false; });+ } // 2) Poll progress every 2.5s (the Allow-1hr grant makes these silent). var polls = 0, maxPolls = 240; // ~10 min ceiling function poll() { polls++; if (polls > maxPolls) { setRes(c.res, "err", "timed out", "the demo did not finish in time"); c.btn.disabled = false; return; }- postVerb(cfg.pollCmd || cfg.startCmd, pollArgs).then(function (pr) {+ postVerb(cfg.pollCmd || cfg.startCmd, pollArgs, target).then(function (pr) { if (pr.s !== 200 || !pr.j) { return new Promise(function (r) { setTimeout(r, 2500); }).then(poll); } var d = pr.j; if (d.error && d.active === undefined && d.done === undefined) { // job already gone - treat as done, go fetch the final state clog(c, " progress job ended; fetching final state ...");- return finishDemo(c, cfg, est);+ return finishDemo(c, cfg, est, target); } setProgress(c.card, d.percent, d.stepLabel || d.step, d.beatIndex, d.beatCount, d.elapsedSec, d.estimatedSec || est);- if (d.stepLabel) clog(c, " " + (d.percent != null ? d.percent + "% " : "") + d.stepLabel + (d.beatCount ? " (step " + ((d.beatIndex || 0) + 1) + "/" + d.beatCount + ")" : ""));+ // Log + snap only on CHANGE - a 2.5s poll of a 30s step used to spam+ // the same line eight times.+ if (d.stepLabel && d.stepLabel !== lastStep) {+ lastStep = d.stepLabel;+ clog(c, " " + (d.percent != null ? d.percent + "% " : "") + d.stepLabel + (d.beatCount ? " (step " + ((d.beatIndex || 0) + 1) + "/" + d.beatCount + ")" : ""));+ maybeSnap(d.stepLabel);+ } if (d.done === true || d.active === false) { setProgress(c.card, 100, "done", d.beatCount, d.beatCount, d.elapsedSec, d.estimatedSec || est); // The bridge rides its final state on the last poll (result.state.shots). // Render it if present; otherwise ask the state verb.- if (!renderShots(c, d)) { return finishDemo(c, cfg, est); }+ if (!renderShots(c, d)) { return finishDemo(c, cfg, est, target); } return; } return new Promise(function (r) { setTimeout(r, 2500); }).then(poll);@@ -463,10 +499,10 @@ for (const w of mine) { }).catch(function (e) { setRes(c.res, "err", "blocked", String(e)); c.btn.disabled = false; }); } // Pull the bridge's full state (the window array) and render it.- function finishDemo(c, cfg, est) {+ function finishDemo(c, cfg, est, target) { if (!cfg.stateCmd) { setRes(c.res, "ok", "Demo finished", "the bridge closed its windows"); c.btn.disabled = false; return; } clog(c, "Fetching the bridge's full window state: " + cfg.stateCmd);- return postVerb(cfg.stateCmd, {}).then(function (sr) {+ return postVerb(cfg.stateCmd, {}, target).then(function (sr) { if (!(sr.s === 200 && sr.j && renderShots(c, sr.j))) { setRes(c.res, "ok", "Demo finished", "the bridge already closed its windows - nothing to show"); clog(c, "DONE - the demo completed; the bridge closed its windows before we captured state.");@@ -524,7 +560,7 @@ for (const w of mine) { } clog(c, "Rendering " + withImg.length + " window(s)."); withImg.forEach(function (s) { addShot(c.card, s.image || s.base64 || s.png, s.title || s.label || s.window || "window"); });- setRes(c.res, "ok", withImg.length === 1 ? "Done - the surface is below" : "Demo done - full state below", withImg.length + " window(s) on " + esc(selfLabel()));+ setRes(c.res, "ok", withImg.length === 1 ? "Done - the surface is below" : "Demo done - full state below", withImg.length + " window(s) on " + esc(c._who || selfLabel())); c.btn.disabled = false; return true; }@@ -691,7 +727,8 @@ for (const w of mine) { runBridgeDemo(cKicad, { startCmd: "kicad_demo", startArgs: { all: true, background: true }, pollCmd: "kicad_demo", pollArgs: { progress: true },- stateCmd: "kicad_state"+ stateCmd: "kicad_state",+ snap: { cmd: "kicad_state", args: { maxWidth: 640 } } }); }; var cFusion = card("grp-do", "verb", "Run the Fusion demo (bridge-driven, live progress)");@@ -705,7 +742,8 @@ for (const w of mine) { startCmd: "fusion_demo", startArgs: { mode: "tour" }, pollCmd: "fusion_demo_progress", pollArgs: {}, idFrom: "demoId", idAs: "demoId",- stateCmd: "fusion_state"+ stateCmd: "fusion_state",+ snap: { cmd: "fusion_state", args: { screenshot: true, imageAs: "base64", maxWidth: 640 } } }); }; // ---- The wiki part every surface button shows: Inolux IN-S42ATR ----