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
v2.0.506 (insiders): Proven on AdomLapper: ext window sized to the full work area (2561x1529), thread tile on the taskbar, no CfT automated-testing bar, no launch-on-startup promo bar, four teal toolbar buttons on the ext lane and three on the CDP lane.
2 files changed
+170−2
src/extension/manifest.json+2−1@@ -14,7 +14,8 @@ "storage", "tabs", "alarms",- "scripting"+ "scripting",+ "system.display" ], "host_permissions": [ "http://127.0.0.1/*",
src/extension/worker.js+168−1@@ -5,9 +5,26 @@ // an older worker painted. const CANDIDATE_PORTS = [64230, 8851]; +// pup STAGES port.json into this extension folder with the bridge's REAL port. Read it: a FRESH profile has+// empty chrome.storage, so without this the worker can only find pup when it happens to be on one of the two+// hardcoded candidates — which is why the extension never called home on ConfRoomROG (pup-bridge#109).+let _pkgPort = null;+let _pkgLane = ''; // only the extension-DRIVE browser gets a lane token in port.json+async function packagedPort() {+ if (_pkgPort !== null) return _pkgPort;+ try {+ const r = await fetch(chrome.runtime.getURL('port.json'), { cache: 'no-store' });+ const j = await r.json();+ _pkgPort = (j && j.port) || 0;+ _pkgLane = (j && j.lane) || '';+ } catch (e) { _pkgPort = 0; }+ return _pkgPort;+}+ async function findPort() { const { pupPort } = await chrome.storage.local.get('pupPort');- const ports = pupPort ? [pupPort, ...CANDIDATE_PORTS.filter(p => p !== pupPort)] : CANDIDATE_PORTS;+ const pkg = await packagedPort();+ const ports = [...new Set([pupPort, pkg, ...CANDIDATE_PORTS].filter(Boolean))]; for (const port of ports) { try { const r = await fetch(`http://127.0.0.1:${port}/health`, { signal: AbortSignal.timeout(1200) });@@ -90,3 +107,153 @@ chrome.commands.onCommand.addListener(async (command) => { body: JSON.stringify(payload), }).catch(() => {}); });++// ── extension-driven control channel (pup-bridge#109) ───────────────────────────────────+// Measured 2026-09-12: a Chrome carrying a CDP --remote-debugging-port is walled by several+// vendor edges (st.com and mouser RST at the HTTP/2 layer; arrow denies) while the SAME Chrome for+// Testing binary, same raw Chromium-148 brands, driven from this extension with NO debug port,+// loads all three. So pup can drive a window through here when CDP is the thing getting blocked.+// The worker polls pup for one command at a time and posts the result back.+let pupExtBusy = false;++async function pupExtTab(cmd) {+ if (cmd.tabId) { try { return await chrome.tabs.get(cmd.tabId); } catch (e) {} }+ if (cmd.windowId) {+ const inW = await chrome.tabs.query({ active: true, windowId: cmd.windowId });+ if (inW && inW[0]) return inW[0];+ }+ // Prefer a real page over the launcher's about:blank/newtab, then anything.+ const all = await chrome.tabs.query({});+ const real = all.filter((t) => t.url && /^https?:/.test(t.url));+ if (real.length) return real.find((t) => t.active) || real[real.length - 1];+ return all && all[0];+}++function pupExtWaitComplete(tabId, ms) {+ return new Promise((resolve) => {+ const done = () => { try { chrome.tabs.onUpdated.removeListener(h); } catch (e) {} resolve(); };+ const t = setTimeout(done, ms);+ const h = (id, info) => { if (id === tabId && info.status === 'complete') { clearTimeout(t); done(); } };+ chrome.tabs.onUpdated.addListener(h);+ });+}++let pupExtDiag = { tries: 0, pkg: null, lastErr: '' };+async function pupExtPoll() {+ if (pupExtBusy) return;+ pupExtDiag.tries++;+ pupExtDiag.pkg = await packagedPort();+ const found = await findPort();+ // Only the extension-driven browser serves this channel. A normal pup window loads the same extension+ // and must NEVER answer another thread's ext commands.+ if (!_pkgLane) return;+ if (!found) {+ // No pup reachable. Surface WHY on the action tooltip so a human (or a screenshot) can read it without+ // needing the very channel that is broken.+ try { chrome.action.setTitle({ title: `Adom Pup — CANNOT REACH BRIDGE (tries ${pupExtDiag.tries}, port.json ${pupExtDiag.pkg})` }); } catch (e) {}+ return;+ }+ let cmd = null;+ try {+ const r = await fetch(`http://127.0.0.1:${found.port}/extcmd?lane=${encodeURIComponent(_pkgLane)}`, { signal: AbortSignal.timeout(2500) });+ cmd = await r.json();+ } catch (e) { return; }+ if (!cmd || !cmd.id) return;+ pupExtBusy = true;+ let out = { id: cmd.id, ok: false, error: `unsupported action ${cmd.action}` };+ try {+ if (cmd.action === 'open') {+ const w = await chrome.windows.create({ url: cmd.url || 'about:blank', focused: false });+ const tab = w.tabs && w.tabs[0];+ if (tab) await pupExtWaitComplete(tab.id, cmd.waitMs || 30000);+ out = { id: cmd.id, ok: true, tabId: tab && tab.id, windowId: w.id };+ } else if (cmd.action === 'navigate') {+ const tab = await pupExtTab(cmd);+ await chrome.tabs.update(tab.id, { url: cmd.url });+ await pupExtWaitComplete(tab.id, cmd.waitMs || 30000);+ const t2 = await chrome.tabs.get(tab.id);+ out = { id: cmd.id, ok: true, tabId: tab.id, windowId: t2.windowId, url: t2.url, title: t2.title };+ } else if (cmd.action === 'eval') {+ const tab = await pupExtTab(cmd);+ const res = await chrome.scripting.executeScript({+ target: { tabId: tab.id }, world: 'MAIN',+ // AWAIT the value: an async expression returned a Promise, which stringified to "{}" and silently+ // swallowed every asynchronous probe (measured while tracing the Akamai sensor, 2026-09-12).+ func: async (src) => {+ try {+ const v = await eval(src);+ return typeof v === 'string' ? v : JSON.stringify(v);+ } catch (e) { return 'ERR:' + (e && e.message ? e.message : e); }+ },+ args: [cmd.expr || ''],+ });+ out = { id: cmd.id, ok: true, result: res && res[0] && res[0].result, tabId: tab.id, windowId: tab.windowId, url: tab.url };+ } else if (cmd.action === 'newtab') {+ // a TAB in the SAME window: the channel could only create new WINDOWS, so it could not show many+ // pages in one window the way the CDP lane does.+ const ref = await pupExtTab(cmd);+ const t = await chrome.tabs.create({ url: cmd.url || 'about:blank', windowId: (ref && ref.windowId) || undefined, active: false });+ await pupExtWaitComplete(t.id, cmd.waitMs || 30000);+ const t2 = await chrome.tabs.get(t.id);+ out = { id: cmd.id, ok: true, tabId: t.id, windowId: t2.windowId, url: t2.url, title: t2.title };+ } else if (cmd.action === 'activate') {+ const t = await pupExtTab(cmd);+ await chrome.tabs.update(t.id, { active: true });+ out = { id: cmd.id, ok: true, tabId: t.id, windowId: t.windowId };+ } else if (cmd.action === 'screenshot') {+ const t = await pupExtTab(cmd);+ await chrome.tabs.update(t.id, { active: true });+ await new Promise((r) => setTimeout(r, 350));+ const dataUrl = await chrome.tabs.captureVisibleTab(t.windowId, { format: 'png' });+ out = { id: cmd.id, ok: true, tabId: t.id, dataUrl: dataUrl };+ } else if (cmd.action === 'tabs') {+ const tabs = await chrome.tabs.query({});+ out = { id: cmd.id, ok: true, tabs: tabs.map((t) => ({ tabId: t.id, url: t.url, title: t.title, active: !!t.active, windowId: t.windowId })) };+ } else if (cmd.action === 'close') {+ if (cmd.tabId) await chrome.tabs.remove(cmd.tabId);+ else if (cmd.windowId) await chrome.windows.remove(cmd.windowId);+ out = { id: cmd.id, ok: true };+ } else if (cmd.action === 'closeAll') {+ const wins = await chrome.windows.getAll({});+ for (const w of wins) { try { await chrome.windows.remove(w.id); } catch (e) {} }+ out = { id: cmd.id, ok: true, closed: wins.length };+ } else if (cmd.action === 'fit') {+ // Size the lane's window to the primary display's WORK AREA, normal state.+ // Deliberately NOT chrome.windows.update({state:'maximized'}) and NOT ab's+ // desktop_set_window_bounds: pup-bridge-dev's geometry rule bans both because each one RAISES+ // the window over the user's work. Plain left/top/width/height leaves the z-order alone. Both+ // sides of this call are LOGICAL px (system.display workArea and windows.update agree), which+ // is why the DPI mismatch that bites the ab path cannot happen here.+ const wins = await chrome.windows.getAll({});+ const displays = await chrome.system.display.getInfo();+ const d = displays.find((x) => x.isPrimary) || displays[0];+ const wa = d && d.workArea;+ let fitted = 0;+ if (wa) {+ for (const w of wins) {+ if (w.type !== 'normal') continue;+ try {+ await chrome.windows.update(w.id, { state: 'normal', left: wa.left, top: wa.top, width: wa.width, height: wa.height });+ fitted++;+ } catch (e) {}+ }+ }+ out = { id: cmd.id, ok: true, fitted: fitted, workArea: wa || null };+ } else if (cmd.action === 'ping') {+ out = { id: cmd.id, ok: true, pong: true, ua: navigator.userAgent };+ }+ } catch (e) { out = { id: cmd.id, ok: false, error: String(e && e.message ? e.message : e) }; }+ try {+ await fetch(`http://127.0.0.1:${found.port}/extresult`, {+ method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(out),+ });+ } catch (e) {}+ pupExtBusy = false;+}++// A tight poll keeps the MV3 worker alive while pup is driving; the alarm revives it if it ever+// does get torn down between commands.+setInterval(pupExtPoll, 1200);+chrome.alarms.create('pup-extdrive', { periodInMinutes: 0.5 });+chrome.alarms.onAlarm.addListener((a) => { if (a.name === 'pup-extdrive') pupExtPoll(); });+pupExtPoll();