app
Pup - the AI's own browser
Public Made by Adomby adom
pup gives your AI a real browser on your desktop that it drives completely, in the background, signed in as you: windows that never jump in front of your work, one shared profile that learns your logins, and every window labeled on the taskbar with its AI thread's name.
← Commit history
Close out wiki discussion 557 (startup hang after an ab cycle with orphaned Chrome windows): timeout-cap every hangable await in session recovery (CDP version probe 5s, puppeteer.connect 15s, browser.pages 10s, window.name evaluate 3s) so one wedged or orphaned Chrome window costs seconds instead of wedging the whole recovery pass, and sweep the remaining Adom Desktop and AD naming out of user-facing errors, hints, tooltips, dashboard copy, welcome and recorder pages to the canonical ab, Bridge, adom-bridge-cli names, leaving frozen code identifiers untouched
6 files changed
+64−49
adom-bridge-puppeteer-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "2.0.14",- "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.14/adom-bridge-puppeteer-v2.0.14.zip",- "sha256": "59851ffe2ea81b34c16a2957d21f1700c4daefd7f39bcf5bdf2453614a1f1555",- "size": 641264,+ "version": "2.0.15",+ "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.15/adom-bridge-puppeteer-v2.0.15.zip",+ "sha256": "35cf2ce8e3e741001ed67b9b1955d3e44ed02798689b9737656737a354dc3deb",+ "size": 641803, "verbPrefixes": [ "browser_", "desktop_recorder_",
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-2.0.14+2.0.15
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "2.0.14",+ "version": "2.0.15", "description": "Drives a browser ALREADY on the machine \u2014 installed Chrome, else Microsoft Edge (every Windows PC ships with Edge) \u2014 launched with a FRESH isolated profile, so the common case has NO ~150 MB Chrome-for-Testing download and it never touches the user's real tabs/logins. Binds its local server to LOOPBACK (127.0.0.1) only, so no Windows Firewall 'allow Node through the firewall' prompt ever pops. Chrome for Testing is fetched only as a LAST RESORT if the machine has no Chromium browser at all. SPAWN-VERIFIED fallback: pup tries each detected browser (Chrome \u2192 Edge \u2192 CfT) and actually launches it, falling through to the next if one won't start (and handling browsers like Edge that relaunch onto another PID), so a corrupt/partial install never hard-fails an open. The browser that launches is cached as the default (persisted across restarts) so it isn't re-probed each time. On spawn the bridge SELF-WARMS: detects browsers + disk, and only if the box has no Chrome/Edge and no cached CfT does it background-fetch CfT so the first open is instant. pup_use pins a specific browser (chrome | edge | cft | auto); {browser:'chrome',install:true} downloads & installs REAL Google Chrome (user-scoped, no admin) on an Edge-only box. pup_readiness (READ-ONLY) reports which browser will be used + the full candidate list + the default + free disk. Every failure returns a specific errorCode + _hint (chrome_install_no_disk, chrome_download_network, chrome_install_permission, chrome_stable_not_detected, \u2026). Launches with FULL browser permissions by default for full debug (strictPermissions:true opts out). Headful automation: open/close windows+tabs, navigate, screenshot, eval JS, record. pup is a GENERIC, non-profile browser only \u2014 every session is a fresh isolated Chrome/Edge process, never the user's real signed-in browser; profile defaults to sessionId. To drive the user's REAL Chrome/Edge (their profile, cookies, SSO), use the Adom browser EXTENSION's nbrowser_* verbs \u2014 a separate bridge, not pup's job. macOS (v1.9.74+): full parity with mac-native mechanics \u2014 background-by-default is the app-level hide (windows stay drivable/screenshottable while hidden), raise/lower map to unhide+activate/hide, pup_alert_window posts a native toast whose click focuses the window, pup_use {browser:'chrome',install:true} installs Chrome from the official dmg, and taskbar identity/badging remains Windows-only.", "homepage": "https://wiki.adom.inc/adom/adom-bridge", "author": "Adom Inc.",
src/recorder.html+4−4@@ -2,7 +2,7 @@ <html lang="en"> <head> <meta charset="UTF-8">- <title>Adom Desktop Recorder</title>+ <title>Adom Bridge Recorder</title> <!-- Brand: Adom. Sources reconciled: • gallia/skills/brand/SKILL.md — operational rules + canonical font URLs@@ -18,7 +18,7 @@ App icon: recorder-icon.svg — a red variant of favicon-color.svg, used instead of the regular Adom teal favicon so the recorder window is- visually distinct from the main Adom Desktop app in the Windows taskbar.+ visually distinct from the main Adom Bridge app in the Windows taskbar. --> <!-- v1.8.131: crisp raster favicon. The recorder runs as a Chrome `--app`@@ -286,8 +286,8 @@ <!-- ================ STATE 1 — READY ================ --> <div id="state-ready" class="state active"> <div class="header">- <img class="logo" src="recorder-icon.svg" alt="Adom Desktop Recorder" />- <span>Adom Desktop Recorder</span>+ <img class="logo" src="recorder-icon.svg" alt="Adom Bridge Recorder" />+ <span>Adom Bridge Recorder</span> </div> <div class="reason"><b>Reason:</b> <span id="reason-ready">—</span></div> <div class="divider"></div>
src/server.js+52−37@@ -1781,6 +1781,18 @@ async function rescanAllProfiles({ adoptOrphans = false } = {}) { const RECOVERY_SENTINEL = path.join(SESSIONS_DIR, '.recovery-in-flight'); let _poisonSessionId = null; +// v2.0.15 (#557 ask 2): TIMEOUT every await in recovery that can hang on a wedged/orphaned Chrome+// (fetch /json/version, puppeteer.connect, browser.pages(), page.evaluate). Since v2.0.13 recovery+// runs AFTER the port binds (fire-and-forget), so a hang can no longer block startup or the health+// probe — but it could still wedge the recovery pass itself forever: sessions half-recovered, the+// RECOVERY_SENTINEL left naming an innocent session, and the next boot quarantining it. Every risky+// await now races a deadline and falls back, so one wedged window costs seconds, never the pass.+// Rejections resolve to the fallback too (callers treated them as fallbacks already).+const _recoverTimeout = (p, ms, fallback) => Promise.race([+ Promise.resolve(p).catch(() => fallback),+ new Promise(r => setTimeout(() => r(fallback), ms)),+]);+ async function recoverSessions() { const files = listSessionFiles(); try {@@ -1855,8 +1867,8 @@ async function recoverSessions() { } try {- // Check if Chrome is actually listening- const resp = await fetch(`http://127.0.0.1:${port}/json/version`).catch(() => null);+ // Check if Chrome is actually listening (5s cap — a wedged Chrome must not hang the pass)+ const resp = await _recoverTimeout(fetch(`http://127.0.0.1:${port}/json/version`), 5000, null); if (!resp || !resp.ok) { console.log(`Session "${sessionId}" CDP port ${port} not responding — killing orphaned Chrome PID ${pid}`); try { process.kill(pid, 'SIGKILL'); } catch (e) { console.log(` kill failed: ${e.message}`); }@@ -1866,7 +1878,9 @@ async function recoverSessions() { // Reconnect browser if needed if (!browsers.has(profileName) || !browsers.get(profileName).browser.isConnected()) {- const browser = await puppeteer.connect({ browserURL: `http://127.0.0.1:${port}`, defaultViewport: null });+ // 15s cap — puppeteer.connect against a wedged Chrome otherwise hangs the whole pass (#557)+ const browser = await _recoverTimeout(puppeteer.connect({ browserURL: `http://127.0.0.1:${port}`, defaultViewport: null }), 15000, null);+ if (!browser) throw new Error(`CDP connect to port ${port} hung or failed (15s cap) — wedged or orphaned Chrome`); browser._detachedPid = pid; // Preserve PID so killBrowserProcess can find it browser._cdpPort = port; browsers.set(profileName, { browser, refCount: 0 });@@ -1877,9 +1891,10 @@ async function recoverSessions() { console.log(`Reconnected to Chrome on port ${port} for profile "${profileName}"`); } - // Find the page that matches this session's URL+ // Find the page that matches this session's URL (10s cap on pages() — same wedge risk) const browser = browsers.get(profileName).browser;- const pages = await browser.pages();+ const pages = await _recoverTimeout(browser.pages(), 10000, null);+ if (!pages) throw new Error('browser.pages() hung (10s cap) — wedged Chrome'); let page = null; // v1.9.154 — THE fourth matcher, fixed (two failed binding proofs cornered it here):@@ -1889,7 +1904,7 @@ async function recoverSessions() { for (const p of pages) { if (_recoveryClaimed.has(p)) continue; try {- const wn = await p.evaluate(() => window.name);+ const wn = await _recoverTimeout(p.evaluate(() => window.name), 3000, ''); if (wn === 'pup:' + sessionId) { page = p; break; } } catch (e) {} }@@ -1900,7 +1915,7 @@ async function recoverSessions() { if (_recoveryClaimed.has(p)) continue; try { if (!p.url().startsWith(url.split('?')[0])) continue;- const wn = await p.evaluate(() => window.name).catch(() => '');+ const wn = await _recoverTimeout(p.evaluate(() => window.name), 3000, ''); if (wn && wn.startsWith('pup:') && wn !== 'pup:' + sessionId) continue; page = p; break; } catch (e) {}@@ -1913,7 +1928,7 @@ async function recoverSessions() { if (_recoveryClaimed.has(p)) continue; try { if (p.url() === 'about:blank') continue;- const wn = await p.evaluate(() => window.name).catch(() => '');+ const wn = await _recoverTimeout(p.evaluate(() => window.name), 3000, ''); if (wn && wn.startsWith('pup:') && wn !== 'pup:' + sessionId) continue; cands.push(p); } catch (e) {}@@ -2997,13 +3012,13 @@ async function probeAdVersion() { if (v) { _adVersion = v; _adRaiseGateBypassed = _verLt(v, '1.9.124');- console.log(`[adver] AD ${v}${_adRaiseGateBypassed ? ' — WARNING: <1.9.124 intercepts raise, the foreground gate is BYPASSED on this box; recommend updating Adom Desktop' : ''}`);+ console.log(`[adver] AD ${v}${_adRaiseGateBypassed ? ' — WARNING: <1.9.124 intercepts raise, the foreground gate is BYPASSED on this box; recommend updating Adom Bridge' : ''}`); } } catch (e) {} } function adUpgradeHint() { return _adRaiseGateBypassed- ? ` ⚠ AD ${_adVersion} on this desktop is older than 1.9.124: it intercepts pup_raise_os_window natively, so pup's foreground gate (foregroundReason + audit trail) does NOT apply to raises here. Recommend the user update Adom Desktop (update_check / the footer banner) — everything else degrades gracefully, this is the one policy gap.`+ ? ` ⚠ Bridge ${_adVersion} on this desktop is older than 1.9.124: it intercepts pup_raise_os_window natively, so pup's foreground gate (foregroundReason + audit trail) does NOT apply to raises here. Recommend the user update Adom Bridge (update_check / the footer banner) — everything else degrades gracefully, this is the one policy gap.` : ''; } // v1.8.82: PUP SETTINGS — user-configurable behavior, persisted OUTSIDE the bridge dir (the@@ -6760,7 +6775,7 @@ async function parkSessionWindowDirect(page, sessionId) { if (!pid) { console.log(`[park] "${sessionId}": no pid to hide (mac) — park-failed`); return 'park-failed'; } const hidden = await macSetAppHiddenByPid(pid, true); if (!hidden) {- console.log(`[park] "${sessionId}": mac hide failed (System Events denied? grant Automation to Adom Desktop) — park-failed`);+ console.log(`[park] "${sessionId}": mac hide failed (System Events denied? grant Automation to Adom Bridge) — park-failed`); return 'park-failed'; } return 'backgrounded';@@ -8771,7 +8786,7 @@ const VERB_META = { desktop_record_stop: { hint: 'Stops the desktop recording and returns the file.', related: ['desktop_record_start', 'desktop_record_list'], pitfalls: [] }, pup_readiness: { hint: 'Cold-start readiness probe: {ready, chromeForTestingInstalled, installing, installProgressPct}. On a fresh PC, poll this until ready:true before pup_open_window — the bridge auto-installs Chrome for Testing on its own.', related: ['pup_prewarm', 'pup_open_window'], pitfalls: ['ready:false with installing:true just means Chrome for Testing is still downloading — wait + poll, do NOT treat it as an error', 'installPhase:"failed" → surface lastError to the user (usually network/proxy blocking the download, or low disk)'] }, pup_prewarm: { hint: 'Installs Chrome for Testing (the browser pup drives) WITHOUT opening a window — the warm-before-first-use path. Returns fast; poll pup_readiness for completion.', related: ['pup_readiness', 'pup_open_window'], pitfalls: ['the ~150 MB download needs network access to storage.googleapis.com', 'does not open a window — call pup_open_window once readiness reports ready:true'] },- pup_import_scan: { hint: 'READ-ONLY discovery for the comprehensive credential import (adom-you seed): enumerates every profile of every Chromium browser (Chrome/Edge/Brave) with its source account email(s), saved-password count, and ABE status. Decrypts NOTHING — actual decryption needs the AD-core elevated SYSTEM helper (adom/adom-desktop #51).', related: ['pup_open_window'], pitfalls: ['Windows-only', 'passwordCount is null if python is not on PATH', 'ABE (v20) passwords cannot be decrypted by this user process — that is the AD SYSTEM helper’s job'] },+ pup_import_scan: { hint: 'READ-ONLY discovery for the comprehensive credential import (adom-you seed): enumerates every profile of every Chromium browser (Chrome/Edge/Brave) with its source account email(s), saved-password count, and ABE status. Decrypts NOTHING — actual decryption needs the Bridge-core elevated SYSTEM helper (adom/adom-bridge #51).', related: ['pup_open_window'], pitfalls: ['Windows-only', 'passwordCount is null if python is not on PATH', 'ABE (v20) passwords cannot be decrypted by this user process — that is the Bridge SYSTEM helper’s job'] }, pup_describe: { hint: 'This list — the bridge\'s verbs + schemas + hints, for AD\'s Verbs tab. Read it once to learn the surface.', related: ['pup_status'], pitfalls: [] }, }; @@ -8842,7 +8857,7 @@ function urlLint(u, callerContainer) { + `ERR_FILE_NOT_FOUND page. Do ONE of these instead: (1) SERVE it and open the public proxy URL — run a ` + `static server in your container (e.g. \`python3 -m http.server <port>\` in that directory) and open ` + `https://<your-container>.adom.cloud/proxy/<port>/<file> ; or (2) PUSH it to the user's machine first `- + `with adom-desktop push_file, then open the file:// path it actually landed at on THEIR disk. Never `+ + `with adom-bridge-cli push_file, then open the file:// path it actually landed at on THEIR disk. Never ` + `assume your container's paths exist on the user's computer — they are different machines.`, }; }@@ -8854,7 +8869,7 @@ function urlLint(u, callerContainer) { code: 'url_file_not_found', message: `FILE DOES NOT EXIST ON THE USER'S MACHINE. pup pre-flighted "${winPath}" on the desktop before ` + `opening it and there is no such file, so this would have rendered Chrome's ERR_FILE_NOT_FOUND page. `- + `Check the path, or push the file to the desktop first with adom-desktop push_file and open the path it `+ + `Check the path, or push the file to the desktop first with adom-bridge-cli push_file and open the path it ` + `reports back. (Remember: pup's Chrome runs on the USER'S computer, so file:// is THEIR filesystem, ` + `not your container's.)`, };@@ -8997,7 +9012,7 @@ function renderVerifyHint(sessionId, url, rc) { const lint = wikiUrlLint(url); if (lint) { return 'THIS PAGE DID NOT RENDER CORRECTLY (' + sig + '). ' + lint- + ' See it yourself: `adom-desktop --ai-thread "<your thread>" pup_screenshot {"sessionId":"'+ + ' See it yourself: `adom-bridge-cli --ai-thread "<your thread>" pup_screenshot {"sessionId":"' + sid + '"}`. Do NOT report success, and do NOT report a conclusion about permissions or login' + ' until the URL is owner-qualified and you have re-checked.'; }@@ -9005,7 +9020,7 @@ function renderVerifyHint(sessionId, url, rc) { const ex = rc.excerpt ? ' The page says: "' + String(rc.excerpt).replace(/\s+/g, ' ').slice(0, 140) + '".' : ''; return 'THIS PAGE DID NOT RENDER CORRECTLY — pup checked it, so do NOT report success. Detected: ' + sig + st + '.' + ex- + ' See it yourself: `adom-desktop --ai-thread "<your thread>" pup_screenshot {"sessionId":"' + sid + '"}`.'+ + ' See it yourself: `adom-bridge-cli --ai-thread "<your thread>" pup_screenshot {"sessionId":"' + sid + '"}`.' + ' Then FIX THE CAUSE: a 404 or "Cannot GET" usually means the dev server is not running, is on a' + ' different port, or the path is wrong; unstyled HTML means the CSS 404d; a stack trace means the' + ' app threw. After fixing, `pup_reload` and re-check until this response comes back clean.'@@ -9015,7 +9030,7 @@ function renderVerifyHint(sessionId, url, rc) { ? 'THIS LOOKS LIKE CONTENT YOU JUST BUILT OR EDITED — SCREENSHOT IT AND LOOK BEFORE YOU CLAIM IT WORKS.' : 'VERIFY WHAT YOU JUST PUT ON SCREEN — do not assume it rendered.'; return `${lead} ok:true means the NAVIGATION succeeded; it says NOTHING about whether the page actually painted. `- + `Take a screenshot and READ the image: \`adom-desktop --ai-thread "<your thread>" pup_screenshot {"sessionId":"${sid}"}\` — it returns a real PNG path you can open. `+ + `Take a screenshot and READ the image: \`adom-bridge-cli --ai-thread "<your thread>" pup_screenshot {"sessionId":"${sid}"}\` — it returns a real PNG path you can open. ` + `Look for: a blank or white page, an unstyled flash of plain HTML (CSS never loaded), a stack trace or framework error overlay, ` + `missing images, a broken or overlapping layout, content cut off past the fold, a dev-server "Cannot GET /" page, or the wrong build entirely. ` + `If it is wrong: fix the source, \`pup_reload\`, and screenshot AGAIN — ralph-test that loop until the render is genuinely correct, `@@ -9029,7 +9044,7 @@ function bugReportInvite(isError) { const lead = isError ? 'THIS MAY BE A PUP BUG — PLEASE REPORT IT.' : 'pup is actively developed and bug reports are genuinely wanted.';- return `${lead} If a pup verb misbehaves (wrong result, a hang, a window that never appears, a hint that turned out to be false, a process left running), file it on pup's own wiki repo so the maintainers see it: \`adom-wiki issue list ${PUP_ISSUES_REF}\` to check for a duplicate first, then \`adom-wiki issue create ${PUP_ISSUES_REF} --title "<one-line symptom>" --category bug-report --body "<what you ran, what you expected, what happened, the VERBATIM response JSON, and the bridge version from bridge_list>"\`. Include the machine's OS and whether it is Windows or macOS. Reports from real machines are the only way this bridge gets correct — the maintainers cannot reproduce every environment, browser install, or concurrency pattern locally. (An AD-core problem — the relay, bridge lifecycle, caller identity — belongs on adom/adom-desktop instead.)`;+ return `${lead} If a pup verb misbehaves (wrong result, a hang, a window that never appears, a hint that turned out to be false, a process left running), file it on pup's own wiki repo so the maintainers see it: \`adom-wiki issue list ${PUP_ISSUES_REF}\` to check for a duplicate first, then \`adom-wiki issue create ${PUP_ISSUES_REF} --title "<one-line symptom>" --category bug-report --body "<what you ran, what you expected, what happened, the VERBATIM response JSON, and the bridge version from bridge_list>"\`. Include the machine's OS and whether it is Windows or macOS. Reports from real machines are the only way this bridge gets correct — the maintainers cannot reproduce every environment, browser install, or concurrency pattern locally. (A Bridge-core problem — the relay, bridge lifecycle, caller identity — belongs on adom/adom-bridge instead.)`; } function maybeAttachBugInvite(obj, verb) { try {@@ -9700,7 +9715,7 @@ function render(){ // from his actual taskbar), and NO overlay at all when pup has not painted one (a bare Chrome // button must render bare here too, otherwise the mimic lies). const ghosts=(d.untrackedWindows||[]).map(h=>'<div class="tbi ghost" title="a window of the pup-managed Chrome is on the real taskbar but pup has NO session for it (hwnd '+h+'). The mimic cannot know its icon - this is why the header LED is red.">?<span class="led red"></span></div>').join('');- _q('#taskbar').innerHTML='<span class="lbl" title="mimics ONLY the taskbar buttons of pup-managed Chrome windows. Other Adom apps (Hydrogen Desktop, Adom Desktop) have their own teal-tile buttons that are out of pup scope.">TASKBAR (pup)</span>'+groups.map((g,i)=>{+ _q('#taskbar').innerHTML='<span class="lbl" title="mimics ONLY the taskbar buttons of pup-managed Chrome windows. Other Adom apps (Adom Hydrogen, Adom Bridge) have their own teal-tile buttons that are out of pup scope.">TASKBAR (pup)</span>'+groups.map((g,i)=>{ const fg=g.wins.some(w=>w.foreground); return '<div class="tbi'+(fg?' fg':'')+'" data-jump="g'+i+'" title="'+esc(g.label)+': '+esc(g.ledWhy||'')+'">' +'<img class="base" src="'+esc(g.baseIcon||'')+'" data-fallback="hide">'@@ -9833,7 +9848,7 @@ const SETTINGS_DEF=[ {k:'minimalTouch',l:'Minimal-touch mode',d:'The safety mode from the Chrome crash hunt. ON means pup keeps its hands off your windows: it only opens them and drives the pages inside, and skips the extra Windows machinery (identity stamps, window sweeps, shell churn) that was suspected of freezing Chrome. The toggles above are the real per-feature controls now and they win over this switch. Rule of thumb: leave this ON; if pup ever gets crashy, make sure it is ON and turn features back on one at a time to find the culprit.'}, ]; // Import-logins picker (the adom-you seed). Discovery runs today via pup_import_scan; the-// actual decrypt is AD-core (adom/adom-desktop #51) so the Import button reports honest-pending.+// actual decrypt is AD-core (adom/adom-bridge #51) so the Import button reports honest-pending. let _importScan=null; let _importSel={}; function _idTag(a){return '<span style="display:inline-block;background:rgba(120,150,205,.22);border-radius:4px;padding:1px 6px;margin:0 4px 2px 0;font-size:11px;white-space:nowrap">'+esc(a)+'</span>';} function _idBadges(accts){return (accts&&accts.length)?accts.map(_idTag).join(''):'<span style="opacity:.55;font-size:11px">no linked account</span>';}@@ -9853,12 +9868,12 @@ function importSectionHtml(){ const has=s&&s.profiles&&s.profiles.length; const summary=s?'<div class="sdesc" style="margin:8px 0">Found <b>'+(s.profiles?s.profiles.length:0)+'</b> profile(s), ~<b>'+(s.totalPasswords||0)+'</b> saved logins across '+((s.accounts&&s.accounts.length)?s.accounts.map(esc).join(', '):'no linked accounts')+'.'+(s.pythonAvailable?'':' <span style="opacity:.6">(login counts unavailable - python not found)</span>')+'</div>':''; return '<div class="srow" style="flex-direction:column;align-items:stretch;border-top:2px solid rgba(255,255,255,.16);margin-top:16px;padding-top:14px">'- +'<div class="slbl" style="font-size:15px">Import logins from Chrome / Edge <span class="ibadge" data-tip="Seeds the shared adom-you profile with the logins you already have, tagged by which account they came from ([email protected] vs [email protected] vs [email protected]) so the agent acts as the right you on each site. Discovery runs now; the actual decrypt needs one Windows admin approval, being built in Adom Desktop core (issue #51).">i</span></div>'+ +'<div class="slbl" style="font-size:15px">Import logins from Chrome / Edge <span class="ibadge" data-tip="Seeds the shared adom-you profile with the logins you already have, tagged by which account they came from ([email protected] vs [email protected] vs [email protected]) so the agent acts as the right you on each site. Discovery runs now; the actual decrypt needs one Windows admin approval, being built in Adom Bridge core (issue #51).">i</span></div>' +'<div class="sdesc">Build a comprehensive adom-you profile from every browser profile on this machine. Each login stays tagged by its source account so the agent picks the right identity per site.</div>' +'<div style="display:flex;gap:8px;margin:12px 0;flex-wrap:wrap"><button class="btn sm" id="importScanBtn">'+(s?'Rescan browsers':'Scan my browsers')+'</button>' +(has?'<button class="btn sm" id="importRunBtn">Import selected logins</button>':'')+'</div>' +summary+rows- +(has?'<div id="importStatus" class="sdesc" style="opacity:.6;margin-top:10px">Import pops one Adom Desktop consent dialog + one Windows admin (UAC) prompt, then decrypts your selected logins into the adom-you vault, tagged by account. Passwords are never shown or logged.</div>':'')+ +(has?'<div id="importStatus" class="sdesc" style="opacity:.6;margin-top:10px">Import pops one Adom Bridge consent dialog + one Windows admin (UAC) prompt, then decrypts your selected logins into the adom-you vault, tagged by account. Passwords are never shown or logged.</div>':'') +'</div>'; } async function runImportScan(){const b=_q('#importScanBtn');if(b){b.disabled=true;b.textContent='Scanning…';}const j=await act({action:'import-scan'});_importScan=(j&&j.ok)?j:{profiles:[],accounts:[]};renderDlg();}@@ -9866,7 +9881,7 @@ async function runImport(){ const sel=((_importScan&&_importScan.profiles)||[]).filter(p=>_importSel[p.browser+'/'+p.profileDir]!==false); if(!sel.length){toast('Select at least one profile','error');return;} const btn=_q('#importRunBtn');if(btn){btn.disabled=true;btn.textContent='Approve on your desktop…';}- const el=_q('#importStatus');if(el)el.textContent='Waiting for the Adom Desktop consent dialog and Windows admin (UAC) prompt on your screen…';+ const el=_q('#importStatus');if(el)el.textContent='Waiting for the Adom Bridge consent dialog and Windows admin (UAC) prompt on your screen…'; const r=await fetch('/dashboard/action',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'import-run',profiles:sel.map(p=>({browser:p.browser,profileDir:p.profileDir}))})}); const j=await r.json().catch(()=>({})); if(btn){btn.disabled=false;btn.textContent='Import selected logins';}@@ -10088,7 +10103,7 @@ const server = http.createServer(async (req, res) => { } if (act === 'import-run') { // AD >= 1.9.220 ships desktop_decrypt_pup_credentials (the SYSTEM-elevated ABE decrypt,- // adom/adom-desktop #51). It MUST be called LOCALLY: chrome.adCommand routes over AD's+ // adom/adom-bridge #51). It MUST be called LOCALLY: chrome.adCommand routes over AD's // direct API (127.0.0.1), which is exactly the loopback path AD requires — a relayed call is // refused with errorCode:relay_forbidden and pops no UAC. AD fires its OWN consent dialog + // UAC prompt (that IS the user's permission for the decrypt). Plaintext returns ONCE over@@ -10108,9 +10123,9 @@ const server = http.createServer(async (req, res) => { if (ec) { const emap = { relay_forbidden: 'The decrypt helper refused a relayed call (pup must call it locally). This is a pup bug — please report it.',- consent_declined: 'You declined the Adom Desktop consent dialog. Nothing was read.',+ consent_declined: 'You declined the Adom Bridge consent dialog. Nothing was read.', elevation_declined: 'You cancelled the Windows admin (UAC) prompt. Nothing was decrypted.',- helper_missing: 'The signed decrypt helper is not installed — update or repair Adom Desktop.',+ helper_missing: 'The signed decrypt helper is not installed — update or repair Adom Bridge.', }; return done({ ok: false, errorCode: ec, message: emap[ec] || ('Decrypt failed: ' + ec) }); }@@ -10603,7 +10618,7 @@ const server = http.createServer(async (req, res) => { // no-id no-thread case is refused, down there, with a teaching error. if (!args.sessionId && !_callerThread) { sendJSON(res, { success: false, errorCode: 'caller_identity_required',- error: 'No sessionId AND no caller identity. Either pass a sessionId, or (better) identify your thread — adom-desktop --ai-thread "<your thread>" ... — and pup gives your thread its OWN window automatically: first open creates it, later opens become tabs in it, and no other thread can touch it.' });+ error: 'No sessionId AND no caller identity. Either pass a sessionId, or (better) identify your thread — adom-bridge-cli --ai-thread "<your thread>" ... — and pup gives your thread its OWN window automatically: first open creates it, later opens become tabs in it, and no other thread can touch it.' }); return; } // ── Cold-start gate (v1.9.90: NATIVE-FIRST) — READ-ONLY detect; never blocks when drivable ──@@ -10700,7 +10715,7 @@ const server = http.createServer(async (req, res) => { if (!args.sessionId) { if (!_callerThread) { sendJSON(res, { success: false, errorCode: 'caller_identity_required',- error: 'No sessionId AND no caller identity. Either pass a sessionId, or (better) identify your thread — adom-desktop --ai-thread "<your thread>" ... — and pup gives your thread its OWN window automatically: first open creates it, later opens become tabs in it, and no other thread can touch it.' });+ error: 'No sessionId AND no caller identity. Either pass a sessionId, or (better) identify your thread — adom-bridge-cli --ai-thread "<your thread>" ... — and pup gives your thread its OWN window automatically: first open creates it, later opens become tabs in it, and no other thread can touch it.' }); return; } args.sessionId = 'ai-' + String(_callerThread).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40);@@ -10782,10 +10797,10 @@ const server = http.createServer(async (req, res) => { errorCode: 'caller_identity_required', error: `'pup_open_window' needs to know WHICH AI THREAD is opening this window. pup owns every session by ai-thread NAME now — the sessionId is not an identity.`, supplyIdentity: {- bestWay: `adom-desktop --ai-thread "<your conversation name>" pup_open_window '<json>'`,+ bestWay: `adom-bridge-cli --ai-thread "<your conversation name>" pup_open_window '<json>'`, orInArgs: { caller: { aiThread: '<your conversation name>', containerName: '<container>' } }, },- _hint: `Re-run with your ai-thread name so pup can own this window as YOU — e.g. adom-desktop --ai-thread "chip-fetcher tab 3" pup_open_window '{"sessionId":"chip-fetcher-web","url":"..."}'. That NAME (not the sessionId) is the window's owner and what protects it from other threads; sessionId is just the window's key, never who owns it.`,+ _hint: `Re-run with your ai-thread name so pup can own this window as YOU — e.g. adom-bridge-cli --ai-thread "chip-fetcher tab 3" pup_open_window '{"sessionId":"chip-fetcher-web","url":"..."}'. That NAME (not the sessionId) is the window's owner and what protects it from other threads; sessionId is just the window's key, never who owns it.`, }); return; }@@ -11468,7 +11483,7 @@ const server = http.createServer(async (req, res) => { console.log(`[wikiview] "${sessionId}" AUTO-SIGNED from AD desktop token (${_autoWho}) — no manual login`); sendJSON(res, { success: true, output: JSON.stringify({ ok: true, view: "authed", autoSigned: true, url: session.page.url(),- _hint: "pup signed this window into the Adom wiki AUTOMATICALLY using the identity AD already holds on this desktop (~/.adom/session.json) — no manual login. The shared vault profile now carries the session, so every thread's authed view works and it persists ~30 days.",+ _hint: "pup signed this window into the Adom wiki AUTOMATICALLY using the identity Bridge already holds on this desktop (~/.adom/session.json) — no manual login. The shared vault profile now carries the session, so every thread's authed view works and it persists ~30 days.", }) }); return; }@@ -11863,7 +11878,7 @@ const server = http.createServer(async (req, res) => { let applied = false; if (stop) { try { const r2 = await chrome.adCommand('notify_dismiss', { id: toastId }, { timeoutMs: 5000 }); applied = !!r2; } catch (e) {}- sendJSON(res, { success: true, output: JSON.stringify({ ok: true, sessionId, alerted: false, cleared: applied, applied, clearMethod: applied ? 'toast-dismissed' : null, _hint: applied ? 'Alert toast dismissed.' : 'No toast to dismiss (or AD unreachable).' }) });+ sendJSON(res, { success: true, output: JSON.stringify({ ok: true, sessionId, alerted: false, cleared: applied, applied, clearMethod: applied ? 'toast-dismissed' : null, _hint: applied ? 'Alert toast dismissed.' : 'No toast to dismiss (or Bridge unreachable).' }) }); return; } try { await macSetAppHiddenByPid(macSessionPid(session), false); } catch (e) {}@@ -11876,7 +11891,7 @@ const server = http.createServer(async (req, res) => { }, { timeoutMs: 6000 }); applied = !!(r2 && r2.success !== false); } catch (e) {}- sendJSON(res, { success: true, output: JSON.stringify({ ok: true, sessionId, alerted: true, cleared: false, applied, clearMethod: null, _hint: applied ? 'macOS nudge: window un-hidden (not focused) + a native toast posted — its click focuses the window. Pass {stop:true} to dismiss the toast.' : 'Window un-hidden, but the AD toast could not be posted (AD unreachable or notify unsupported).' }) });+ sendJSON(res, { success: true, output: JSON.stringify({ ok: true, sessionId, alerted: true, cleared: false, applied, clearMethod: null, _hint: applied ? 'macOS nudge: window un-hidden (not focused) + a native toast posted — its click focuses the window. Pass {stop:true} to dismiss the toast.' : 'Window un-hidden, but the AD toast could not be posted (Bridge unreachable or notify unsupported).' }) }); return; } // v1.8.43: route the FLASH through AD's desktop_flash_window (reliable, native).@@ -13519,8 +13534,8 @@ const server = http.createServer(async (req, res) => { // with: source account email(s) (the identity tag — Preferences.account_info[].email), // saved-password count (parsed from the Login Data SQLite via the desktop's python, // NEVER a byte-scan — see dev-skills/pup-browser-import), Login Data size, and ABE- // status. It DECRYPTS NOTHING — decryption needs the AD-core elevated SYSTEM helper- // (adom/adom-desktop #51, desktop_decrypt_pup_credentials). This verb is the+ // status. It DECRYPTS NOTHING — decryption needs the Bridge-core elevated SYSTEM helper+ // (adom/adom-bridge #51, desktop_decrypt_pup_credentials). This verb is the // foundation the first-run prompt + Settings "Import logins" picker read. if (process.platform !== 'win32') { sendJSON(res, { success: true, output: JSON.stringify({ profiles: [] }), profiles: [], _hint: 'Credential import discovery is Windows-only (Chromium ABE / DPAPI). No profiles enumerated on this platform.' });@@ -14191,7 +14206,7 @@ const server = http.createServer(async (req, res) => { // v1.8.78: the ONE non-cosmetic degradation on old AD gets a visible chip warning. if (_adRaiseGateBypassed) { if (led === 'green') led = 'yellow';- tooltip += `\n⚠ AD ${_adVersion} < 1.9.124: raise intercepted — the foreground gate does not apply on this box. Update Adom Desktop.`;+ tooltip += `\n⚠ AD ${_adVersion} < 1.9.124: raise intercepted — the foreground gate does not apply on this box. Update Adom Bridge.`; } sendJSON(res, { ok: true,@@ -15044,7 +15059,7 @@ async function desktopRecordStartImpl(opts) { 'pass confirmDesktopNotTabRecording: true and resend the call.' ); err.code = 'desktop_record_needs_confirmation';- err.hint = 'For pup tab recording: adom-desktop --ai-thread "<your thread>" pup_record_start \'{"sessionId":"<id>","fps":30}\'. ' ++ err.hint = 'For pup tab recording: adom-bridge-cli --ai-thread "<your thread>" pup_record_start \'{"sessionId":"<id>","fps":30}\'. ' + 'For Hydrogen-driven desktop recording: hydrogen recording start --share screen. ' + 'For a parallel desktop clip alongside Hydrogen tab capture: re-call with confirmDesktopNotTabRecording: true.'; throw err;
src/welcome.html+2−2@@ -3,7 +3,7 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0">-<title>Pup — Adom Desktop</title>+<title>Pup — Adom Bridge</title> <style> @font-face { font-family: 'Familjen Grotesk';@@ -320,7 +320,7 @@ </svg> </div> <div class="hero-text">- <h1><span class="pup">pup</span><span class="sep">—</span><span class="sub">Adom Desktop</span></h1>+ <h1><span class="pup">pup</span><span class="sep">—</span><span class="sub">Adom Bridge</span></h1> <p class="tagline">Browser automation for your desktop, controlled by Adom's cloud AI</p> </div> </div>