← Commit history

v2.0.293: Foreground etiquette + firewall + real images (John). (1) FIREWALL: three stacked --disable-features flags meant Chrome honored only the last, silently re-enabling MediaRouter mDNS on 0.0.0.0:5353 since v1.8.95 - the Windows Firewall dialog on every new CfT exe. mergeChromiumFeatureFlags() now unions them at both launch sites; recorder Chrome gets the disables too. (2) pup_annotate no longer raises for AI callers (user jump-list/toolbar clicks still raise); AI must use the captioned pup_raise_os_window. (3) pup_screenshot foreground caption: idle>3s gate removed - captions ALWAYS when the capture fronted the window. (4) desktop_caption failures now log loudly instead of vanishing. (5) taskbarActivity row moved out of the overlay-badges cluster to its own top-level row. (6) ALL five settings example images replaced with REAL screenshots captured on ConfRoomROG (real progress bar/flash/plain taskbar states, a real annotate capture with the real caption strip, the real toolbar). New dev skills: pup-real-images, pup-no-firewall.

John Lauer ·5163b514a8 ·2mo ago ·parent 6719a00
4 files changed +78−24
adom-bridge-puppeteer-manifest.json+5−5
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "puppeteer",-  "version": "2.0.292",-  "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.292/adom-bridge-puppeteer-v2.0.292.zip",-  "sha256": "a6b8ab90a27b1f0cad2e7dbb5c2cec51eb107b118a3b4316a5aab86d5f5910a9",-  "size": 996148,+  "version": "2.0.293",+  "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.293/adom-bridge-puppeteer-v2.0.293.zip",+  "sha256": "fb7915fc4ea568839fef9efec22985eeed687465f810b9e2290871e298cb9759",+  "size": 1090862,   "verbPrefixes": [     "browser_",     "desktop_recorder_",@@ -12,5 +12,5 @@   ],   "healthEndpoint": "/status",   "statusVerb": "browser_readiness",-  "released_at": "2026-08-23T11:21:20.850287Z"+  "released_at": "2026-08-23T12:08:51.544442Z" }
src/BRIDGE_VERSION+1−1
@@ -1 +1 @@-2.0.292\ No newline at end of file+2.0.293\ No newline at end of file
src/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "puppeteer",   "displayName": "Puppeteer (Chrome for Testing + Native)",-  "version": "2.0.292",+  "version": "2.0.293",   "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/server.js+71−17
@@ -2517,7 +2517,14 @@ async function pupCaption(text, expiresInMs = PUP_CAPTION_MAX_MS, id = 'pup-assi   // clamp NEVER REACHED AD: current AD's desktop_caption takes `duration`, pup sent `expiresInMs`   // (an older AD spelling), so AD ignored it and used its 30s default. Send BOTH names so every AD   // build honors its own; the clamp finally bites.-  try { await chrome.abCommand('desktop_caption', { text, duration: expiresInMs, expiresInMs, id, position: 'bottom', size: 'medium' }, { timeoutMs: 4000 }); } catch (e) {}+  // v2.0.293 (John: "you just foregrounded a pup window and never showed an ab caption"): a failed+  // caption used to vanish silently — the raise proceeded and nobody could tell WHY no caption+  // showed. Failures now log loudly; the raise still proceeds (the caption is a courtesy, but the+  // silence made every miss undebuggable).+  try {+    const _r = await chrome.abCommand('desktop_caption', { text, duration: expiresInMs, expiresInMs, id, position: 'bottom', size: 'medium' }, { timeoutMs: 4000 });+    if (!_r || _r.ok === false || _r.success === false) console.log(`[caption] desktop_caption did NOT confirm (id=${id}): ${JSON.stringify(_r || null).slice(0, 200)} — text was: ${String(text).slice(0, 80)}`);+  } catch (e) { console.log(`[caption] desktop_caption FAILED (id=${id}): ${e.message} — text was: ${String(text).slice(0, 80)}`); } } // v2.0.137 (John's Activity Log flooded with find_window: "why do you loop like crazy?"): // NEGATIVE-RESULT BACKOFF for the title hunt. A session whose window cannot be resolved by title@@ -9909,6 +9916,29 @@ async function getOrLaunchBrowser(profileName, { freshProfile = false, strictPer     console.log('[launch] no system browser or cached CfT detected — falling back to puppeteer.executablePath()');   } +  // v2.0.293 — MERGE duplicate --disable-features / --enable-features into ONE flag each.+  // Chrome honors only the LAST occurrence of a switch, so when three code eras each appended+  // their own --disable-features (v2.0.120 MediaRouter/mDNS, v1.9.198 CalculateNativeWinOcclusion,+  // v1.8.95 SigninIntercept...), only the last list applied — which SILENTLY UNDID the v2.0.120+  // firewall fix: MediaRouter's mDNS re-bound 0.0.0.0:5353 (measured live on ConfRoomROG, CfT 148)+  // and every new CfT exe re-popped the Windows Firewall "allow access?" dialog at John.+  // pup's HARD RULE is zero firewall prompts (see dev-skills/pup-no-firewall): every listener+  // loopback-only. Any future feature-flag addition MUST go through this merge (it runs at both+  // launch sites), never as a hand-appended second --disable-features.+  function mergeChromiumFeatureFlags(args) {+    const out = [];+    const merged = {};   // switchName -> { idx, values:[] }+    for (const a of args) {+      const m = typeof a === 'string' && a.match(/^--(disable-features|enable-features)=(.*)$/);+      if (!m) { out.push(a); continue; }+      const key = m[1];+      if (!merged[key]) { merged[key] = { idx: out.length, values: [] }; out.push(null); }+      for (const v of m[2].split(',')) { if (v && !merged[key].values.includes(v)) merged[key].values.push(v); }+    }+    for (const key of Object.keys(merged)) out[merged[key].idx] = `--${key}=${merged[key].values.join(',')}`;+    return out.filter(a => a !== null);+  }+   const launchOpts = {     headless: false,     defaultViewport: null,@@ -10037,6 +10067,9 @@ async function getOrLaunchBrowser(profileName, { freshProfile = false, strictPer     launchOpts.args = launchOpts.args.filter(a => !stripFlags.has(a));     console.log(`nativeSite mode: stripped ${stripFlags.size} flags for real-site compatibility`);   }+  // v2.0.293: collapse duplicate feature flags for the puppeteer.launch path too (the detached+  // Windows path merges chromeArgs at its own assembly). See mergeChromiumFeatureFlags above.+  launchOpts.args = mergeChromiumFeatureFlags(launchOpts.args);    let userDataDir = null;   if (!freshProfile) {@@ -10218,13 +10251,13 @@ async function getOrLaunchBrowser(profileName, { freshProfile = false, strictPer         '--disable-features=SigninIntercept,ForceSignInReauth,SyncPromoAfterSignin,DiceWebSigninInterception,ProfilePicker',         '--disable-signin-promo',       ];-      const chromeArgs = [+      const chromeArgs = mergeChromiumFeatureFlags([         ...launchOpts.args.filter(a => a !== '--remote-debugging-port=0' && (foreground || a !== '--start-maximized')),         `--remote-debugging-port=${debugPort}`,         `--user-data-dir=${userDataDir}`,         ...noBrowserIdentityFlags,         ...(foreground ? [] : ['--no-startup-window']),   // v1.9.135 (John): with no-startup-window there IS no window at launch — the -32000 position arg was fighting nothing, and it poisoned every Chrome-created window (popups, restores) with an off-screen default. Park owns geometry now.-      ];+      ]);       console.log(`Chrome launch attempt ${attemptNum} for profile "${profileName}" on CDP port ${debugPort}`);       // v1.8.34: snapshot the user's foreground window right before launch (background only)       // so we can hand keyboard focus back to it after the browser grabs it.@@ -12627,7 +12660,6 @@ const SETTINGS_DEF=[  {k:'overlayBadges',l:'Favicon overlay badges',d:'Paint each pup taskbar icon with the favicon of its active tab plus the Adom mark.'},  {k:'overlaySize',l:'Badge size',d:'Percent of full size for the badge. Smaller keeps it clear of the tile\u2019s text.',sel:['50','66','75','100'],selLabels:{'50':'50% \u2014 smallest, maximum text clearance','66':'66%','75':'75% \u2014 balanced (default)','100':'100% \u2014 full size'},dep:'overlayBadges'},  {k:'taskbarGrouping',l:'AUMID grouping',d:'Only applies when AUMID icons are on. split = one taskbar button per window. grouped = windows grouped BY CATEGORY when icon-style is "category" (up to 5 buttons: signed-in wiki, public wiki, open web, Adom app, plain — e.g. all your signed-in-wiki windows collapse under the one padlock button); with icon-style "standard" grouped collapses everything under a single Adom Pup button.',sel:['split','grouped'],depMode:'aumid',imgBy:{split:'groupsplit',grouped:'groupgrouped'}},- {k:'taskbarActivity',l:'Taskbar activity signal',d:'The single control for how pup signals it is working on a window, on that window’s taskbar button. "progress" (default): a determinate progress bar sized to how long the operation usually takes (applying the window icon, taking a screenshot) plus a strip while an AI actively drives the window; pup keeps a per-operation running average and fills the bar over it. "flash": the older orange Windows attention flash, when a window opens and when an AI changes it. "off": neither. Windows only. The example switches with your choice.',sel:['progress','flash','off'],selLabels:{'progress':'progress bar (default)','flash':'orange flash (old)','off':'off'},imgBy:{progress:'tbactprogress',flash:'tbactflash',off:'tbactoff'}},  {k:'aumidIconStyle',l:'AUMID icon style',d:'Only applies when AUMID icons are on: standard = one pup tile for every window; category = the custom set (wiki signed-out, wiki signed-in padlock, world icon for the open web, adom app icon, mixed).',sel:['standard','category'],depMode:'aumid',imgBy:{standard:'iconstylestd',category:'iconstylecat'}},  {k:'jumpLists',l:'Jump lists',d:'The menu Windows shows when you right-click a pup taskbar icon (real screenshot below, captured on this machine). Requires AUMID icons on. Off by default. Quirk: the FIRST right-click can blip the menu closed — right-click again ~1s later and it holds (a Windows shell timing race, not a pup bug).',img:'jumplist'},  {k:'jlViewTasks',l:'',d:'Wiki view tasks: Switch to logged-in view / Switch to public view on every wiki window.',dep:'jumpLists',subtgl:1,img:'jlviewtasks'},@@ -12639,6 +12671,11 @@ const SETTINGS_DEF=[  {k:'tbSnipHotkey',l:'',d:'Alt+Shift+A starts annotate (red pen) on the current pup window.',dep:'toolbarExtension',subtgl:1},  {k:'tbFloatyPencil',l:'',d:'The tiny pencil in the upper-right corner of each page: one click starts annotate. Off by default: the corner hover zone can fight buttons in app headers, and the toolbar pen button covers discovery.',dep:'toolbarExtension',subtgl:1},  {k:'floatyHitPx',l:'',d:'Pencil corner hit area (px): how big the invisible top-right target is that reveals the pencil. Applies to open pages immediately.',slider:[6,120,2],dep:'tbFloatyPencil'},+ // v2.0.293 (John: "why did you make progress bar vs orange flash a child of Favicon overlay+ // badges? that hierarchy doesn't make sense"): taskbarActivity is its OWN top-level row here in+ // the activity/foreground section, NOT inside the taskbar-identity cluster where its position+ // between the badge/AUMID rows rendered it as a sub-setting of overlay badges.+ {k:'taskbarActivity',l:'Taskbar activity signal',d:'The single control for how pup signals it is working on a window, on that window’s taskbar button. "progress" (default): a determinate progress bar sized to how long the operation usually takes (applying the window icon, taking a screenshot) plus a strip while an AI actively drives the window; pup keeps a per-operation running average and fills the bar over it. "flash": the older orange Windows attention flash, when a window opens and when an AI changes it. "off": neither. Windows only. The example switches with your choice.',sel:['progress','flash','off'],selLabels:{'progress':'progress bar (default)','flash':'orange flash (old)','off':'off'},imgBy:{progress:'tbactprogress',flash:'tbactflash',off:'tbactoff'}},  {k:'fgCaption',l:'Caption on AI foreground',d:'When an AI brings a window to your screen, show its stated reason as an on-screen caption. The AI is always forced to give a reason; this shows it to you.'},  {k:'fgCaptionMs',l:'',d:'',slider:[500,5000,250],dep:'fgCaption',test:1},  {k:'sleepRecovery',l:'Sleep and crash recovery',d:'After the laptop wakes or Chrome dies, automatically bring every pup window back at its last page, signed in. Never restarts a live Chrome.'},@@ -16256,12 +16293,15 @@ const server = http.createServer(async (req, res) => {             if (_hw) {               const _fgAfter = await osGetForegroundWindow().catch(() => null);               if (_fgAfter && String(_fgAfter) === String(_hw) && String(_fgAfter) !== String(_fgBefore)) {-                let _idle = 99999; try { _idle = await osIdleMs(); } catch (e) {}-                if (_idle > 3000) {-                  const _thread = session.owner || session._ownerThread || 'an AI thread';-                  pupCaption(`${_thread} · screenshotting`, 4000, 'pup-screenshot').catch(() => {});-                  console.log(`[screenshot] "${resolvedId}" came to the FOREGROUND during capture (user idle ${_idle}ms = pup did it) — captioned "${_thread} · screenshotting"`);-                }+                // v2.0.293 (John: "you keep foregrounding windows without ever giving me ab captions"):+                // the old idle>3000 gate assumed an ACTIVE user must have clicked the window up — but the+                // active-user case is precisely when a silent foreground is most disruptive, and the fg+                // flipping to OUR hwnd mid-capture is pup's doing regardless of user activity. Caption+                // ALWAYS. (The idle reading is kept only for the log.)+                let _idle = -1; try { _idle = await osIdleMs(); } catch (e) {}+                const _thread = session.owner || session._ownerThread || 'an AI thread';+                pupCaption(`${_thread} · screenshotting`, 4000, 'pup-screenshot').catch(() => {});+                console.log(`[screenshot] "${resolvedId}" came to the FOREGROUND during capture (user idle ${_idle}ms) — captioned "${_thread} · screenshotting"`);               }             }           } catch (e) {}@@ -17884,12 +17924,21 @@ const server = http.createServer(async (req, res) => {               return;             }             await startAnnotation(session, sessionId, _callerThread, { tool: args.tool === 'snip' ? 'snip' : 'pen', tabId: args._resolvedTabId || null });-            // the human is about to draw — the window must be in front of them. A jump-list click IS-            // the user asking, so this raise is user-sanctioned by construction.-            session._foreground = true; session._userActiveAt = Date.now();-            try { const t0 = (session.tabs || [])[0]; if (t0 && t0.page) await t0.page.bringToFront(); } catch (e) {}-            try { await osRaiseSessionWindow(session, sessionId, 'you clicked Annotate: draw on the page, then hit Done'); } catch (e) {}-            sendJSON(res, { success: true, output: JSON.stringify({ ok: true, started: true, _hint: 'Overlay is live: pen/arrow/box/text, color dot cycles, Done captures (drawing stays visible so the owning AI thread\'s next screenshot shows it), \u2716 cancels.' }) });+            // v2.0.293 (John: "you just foregrounded a pup window and never showed an ab caption"):+            // the old unconditional raise claimed a jump-list click is user-sanctioned by+            // construction — TRUE only for USER callers (jump list / toolbar). An AI THREAD calling+            // pup_annotate got the same screen-steal, and a scripted annotate workflow re-raised+            // the window on every arm. Now: USER callers get the raise (they clicked, they are+            // waiting for the window); AI callers arm the overlay in the BACKGROUND and must+            // foreground explicitly via pup_raise_os_window + foregroundReason (the reason-gated,+            // captioned path) if the user should draw now.+            const _userCaller = _callerThread === 'user-taskbar-menu' || _callerThread === 'user-toolbar';+            if (_userCaller) {+              session._foreground = true; session._userActiveAt = Date.now();+              try { const t0 = (session.tabs || [])[0]; if (t0 && t0.page) await t0.page.bringToFront(); } catch (e) {}+              try { await osRaiseSessionWindow(session, sessionId, 'you clicked Annotate: draw on the page, then hit Done'); } catch (e) {}+            }+            sendJSON(res, { success: true, output: JSON.stringify({ ok: true, started: true, raised: _userCaller, _hint: _userCaller ? 'Overlay is live: pen/arrow/box/text, color dot cycles, Done captures (drawing stays visible so the owning AI thread\'s next screenshot shows it), \u2716 cancels.' : 'Overlay is armed but the window stays in the BACKGROUND (AI-called annotate never steals the screen). If the user should draw NOW, call pup_raise_os_window with a foregroundReason: that captions the reason first, then foregrounds.' }) });           } catch (e) { sendJSON(res, { success: false, error: `annotate failed: ${e.message}` }); }           return;         }@@ -19564,7 +19613,12 @@ async function ensureRecorderWindowOpen(reason) {     '--no-sandbox',     '--no-first-run',     '--no-default-browser-check',-    '--disable-features=Translate',+    // v2.0.293: MediaRouter's mDNS binds 0.0.0.0:5353 — a non-loopback listener that pops the+    // Windows Firewall dialog (per-exe). pup's hard rule is ZERO firewall prompts, so the+    // recorder Chrome disables it exactly like the main launch path (one flag, comma-merged —+    // never add a second --disable-features here, Chrome only honors the last).+    '--disable-features=Translate,MediaRouter,DialMediaRouteProvider,CastMediaRouteProvider',+    '--media-router=0',     `--user-data-dir=${RECORDER_PROFILE_DIR}`,   ];