← Commit history

v1.8.35: browser_alert_window {stop:true} clears the taskbar highlight (FLASHW_STOP); fix alert_window to actually flash (was wrongly calling bringToFront)

John Lauer ·792c4394b0 · ·parent 9597d6f
4 files changed +27−22
adom-bridge-puppeteer-manifest.json+5−5
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "puppeteer",-  "version": "1.8.34",-  "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.8.34/adom-bridge-puppeteer-v1.8.34.zip",-  "sha256": "70d25fb8e42555c176b906d9891b34d44b8e5c035cbd8ee0b8f3a7769e9efe4f",-  "size": 157011,+  "version": "1.8.35",+  "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.8.35/adom-bridge-puppeteer-v1.8.35.zip",+  "sha256": "ac811b0b884e2a24f65e28c72da62a20107fbc804d610f85c280faf1a8c847b1",+  "size": 157339,   "verbPrefixes": [     "browser_",     "desktop_recorder_",@@ -12,5 +12,5 @@   ],   "healthEndpoint": "/status",   "statusVerb": "browser_readiness",-  "released_at": "2026-07-14T20:05:21Z"+  "released_at": "2026-07-14T20:37:36Z" }\ No newline at end of file
src/BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.8.34+1.8.35
src/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "puppeteer",   "displayName": "Puppeteer (Chrome for Testing + Native)",-  "version": "1.8.34",+  "version": "1.8.35",   "description": "Drives a browser ALREADY on the machine — installed Chrome, else Microsoft Edge (every Windows PC ships with Edge) — 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 → Edge → 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. browser_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. browser_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, …). 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 — 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 — a separate bridge, not pup's job.",   "homepage": "https://wiki.adom.inc/adom/adom-desktop",   "author": "Adom Inc.",
src/server.js+20−15
@@ -1615,13 +1615,14 @@ function osBackgroundWindowByPid(pid, cdpPort, restoreHwnd) { // v1.8.24: a FINITE 4-blink pulse then stop (see Flash() below) — NOT a blink-forever // nag. Skips if the window is already foreground. Fire-and-forget (async exec) so it // never adds latency to the driving verb.-function osFlashWindow(profileName) {+function osFlashWindow(profileName, opts) {   if (process.platform !== 'win32') return;+  opts = opts || {};+  const stop = !!opts.stop; // v1.8.35: {stop:true} clears the taskbar highlight (FLASHW_STOP)   const be = browsers.get(profileName);   const pid = (be && be.browser && (be.browser._detachedPid || (be.browser.process() && be.browser.process().pid))) || null;   if (!pid) return;   try {-    const { exec } = require('child_process');     const psScript = path.join(require('os').tmpdir(), `_adom_flash_${pid}.ps1`);     const ps = [       'Add-Type @"',@@ -1630,17 +1631,18 @@ function osFlashWindow(profileName) {       '  [StructLayout(LayoutKind.Sequential)] public struct FLASHWINFO { public uint cbSize; public IntPtr hwnd; public uint dwFlags; public uint uCount; public uint dwTimeout; }',       '  [DllImport("user32.dll")] public static extern bool FlashWindowEx(ref FLASHWINFO pwfi);',       '  [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();',-      '  public static void Flash(IntPtr h){ if(h==IntPtr.Zero) return; if(GetForegroundWindow()==h) return;',-      // v1.8.24: FINITE pulse — FLASHW_TRAY (taskbar button only), 4 blinks, then STOP.-      // NOT FLASHW_TIMERNOFG (which blinks forever until focused — an annoying nag). A short-      // orange pulse is a glance-and-forget "this window was updated" nudge, not a demand.-      '    FLASHWINFO fi=new FLASHWINFO(); fi.cbSize=(uint)Marshal.SizeOf(fi); fi.hwnd=h; fi.dwFlags=0x2; fi.uCount=4; fi.dwTimeout=0; FlashWindowEx(ref fi); }',+      // v1.8.24: FINITE pulse — FLASHW_TRAY (taskbar button), 4 blinks, then stop (NOT+      // FLASHW_TIMERNOFG, which blinks forever = a nag). v1.8.35: stop=true → FLASHW_STOP+      // (dwFlags 0, count 0) to CLEAR a lingering highlight on demand.+      '  public static void Flash(IntPtr h, bool stop){ if(h==IntPtr.Zero) return; if(!stop && GetForegroundWindow()==h) return;',+      '    FLASHWINFO fi=new FLASHWINFO(); fi.cbSize=(uint)Marshal.SizeOf(fi); fi.hwnd=h; fi.dwFlags=(stop?(uint)0:(uint)0x2); fi.uCount=(stop?(uint)0:(uint)4); fi.dwTimeout=0; FlashWindowEx(ref fi); }',       '}',       '"@',       `$targetPid = ${pid}`,+      `$doStop = ${stop ? '$true' : '$false'}`,       '$p = Get-Process -Id $targetPid -ErrorAction SilentlyContinue',-      'if ($p -and $p.MainWindowHandle -ne 0) { [WinFlash]::Flash($p.MainWindowHandle) }',-      'else { Get-CimInstance Win32_Process -Filter "ParentProcessId=$targetPid" -ErrorAction SilentlyContinue | Where-Object { $_.Name -in @("chrome.exe","msedge.exe") } | ForEach-Object { $cp = Get-Process -Id $_.ProcessId -ErrorAction SilentlyContinue; if ($cp -and $cp.MainWindowHandle -ne 0) { [WinFlash]::Flash($cp.MainWindowHandle) } } }',+      'if ($p -and $p.MainWindowHandle -ne 0) { [WinFlash]::Flash($p.MainWindowHandle, $doStop) }',+      'else { Get-CimInstance Win32_Process -Filter "ParentProcessId=$targetPid" -ErrorAction SilentlyContinue | Where-Object { $_.Name -in @("chrome.exe","msedge.exe") } | ForEach-Object { $cp = Get-Process -Id $_.ProcessId -ErrorAction SilentlyContinue; if ($cp -and $cp.MainWindowHandle -ne 0) { [WinFlash]::Flash($cp.MainWindowHandle, $doStop) } } }',     ].join('\r\n');     fs.writeFileSync(psScript, ps);     runPsHiddenAsync(psScript);@@ -2894,7 +2896,7 @@ const server = http.createServer(async (req, res) => {             V('browser_use', 'Pin (or clear) the browser pup drives by default: chrome | edge | cft | auto (or an explicit executablePath). "cft" fetches Chrome for Testing then pins it. With {browser:"chrome", install:true} it DOWNLOADS & INSTALLS real Google Chrome on an Edge-only box, then pins it — SILENT on a normal PC; on a locked-down box (VM/managed device) it needs a Windows approval, so the bridge pops a native "click YES on the UAC" notification and, if it expires, re-notifies with a one-tap "Approve now" (all autonomous — no AI turn). Poll browser_readiness.chromeStablePhase: installing → awaiting_uac → ready. Persists across restarts; launch still spawn-verifies + falls back.', { browser: '"chrome" | "edge" | "cft" | "auto"', install: 'optional bool — with browser:"chrome", install real Chrome if missing', elevate: 'optional "auto"(default)|true|false — auto: quiet install, prompt only if it needs admin; false: never prompt; true: go straight to the prompt', executablePath: 'optional — pin an exact browser binary' }, 240, {}),             V('browser_lower_os_window', 'Send a pup window to the BACKGROUND (behind the user\'s windows) without stealing focus — still fully drivable. The default for open; use this to re-hide a window you foregrounded.', { sessionId: 'optional str' }, 15, {}),             V('browser_raise_os_window', 'Bring a pup window to the FOREGROUND (user\'s screen). Use SPARINGLY — only when the user EXPLICITLY must SEE it (a demo, recording, "show me"). It STEALS focus and can leak a mid-typed keystroke into the page — to just signal "I updated this window", use browser_alert_window (or rely on the auto-flash), NOT raise. pup blurs the page\'s focused element before raising to reduce the keystroke leak. Alias: browser_focus_window.', { sessionId: 'optional str' }, 15, {}),-            V('browser_alert_window', 'Flash the taskbar icon for a pup window — a non-intrusive "it\'s ready/DONE" nudge that does NOT steal focus. NOTE (v1.8.23): mutating verbs already AUTO-FLASH (debounced, one per ~5s burst per window) so the user always knows the agent touched a window — use this for an explicit end-of-task "it\'s ready" nudge on top of that. STRONGLY prefer this (or the auto-flash) over browser_raise_os_window, which steals focus and can leak a mid-typed keystroke into the page.', { sessionId: 'optional str' }, 15, {}),+            V('browser_alert_window', 'Flash the taskbar icon for a pup window — a non-intrusive "it\'s ready/DONE" nudge that does NOT steal focus. NOTE (v1.8.23): mutating verbs already AUTO-FLASH (debounced, one per ~5s burst per window) so the user always knows the agent touched a window — use this for an explicit end-of-task "it\'s ready" nudge on top of that. STRONGLY prefer this (or the auto-flash) over browser_raise_os_window, which steals focus and can leak a mid-typed keystroke into the page.', { sessionId: 'optional str', stop: 'optional bool — CLEAR the highlight instead of flashing' }, 15, {}),             V('browser_render_html', 'Render a local HTML string in the tab via page.setContent — UTF-8-correct (a data: URL mojibakes accented chars without an explicit charset). Great for previewing generated markup without a temp file. Auto-flashes the window.', { sessionId: 'required str', html: 'required HTML string', waitUntil: 'optional (domcontentloaded default)', updateNote: 'optional note for lastAgentUpdate', silent: 'optional bool — suppress the auto-flash' }, 30, { sessionId: 'work', html: '<h1>Café</h1>' }),             // Session/tab/window management + probes (were routed but undocumented).             V('browser_status', 'List all open pup sessions + their tabs (NOT a readiness check — use browser_readiness for that).', {}, 10, {}),@@ -3335,7 +3337,10 @@ const server = http.createServer(async (req, res) => {         }          case 'browser_alert_window': {-          // Flashes the taskbar icon for a session's Chrome window (does NOT bring to foreground)+          // Flash the taskbar button for a session's window WITHOUT foregrounding it. v1.8.35:+          // {stop:true} (alias {clear:true}) CLEARS a lingering orange highlight instead.+          // (Pre-1.8.35 this wrongly called bringToFront — it raised the window instead of+          // flashing. Now it uses the real FlashWindowEx path, same as the auto-flash.)           const sessionId = args.sessionId || activeSessionId;           if (!sessionId || !sessions.has(sessionId)) {             sendJSON(res, { success: false, error: `Session "${sessionId}" not found` });@@ -3343,10 +3348,10 @@ const server = http.createServer(async (req, res) => {           }           const session = sessions.get(sessionId);           try {-            await session.page.bringToFront();-            activeSessionId = sessionId;-            console.log(`Alerted session "${sessionId}" (taskbar flash)`);-            sendJSON(res, { success: true, output: JSON.stringify({ ok: true, sessionId, alerted: true }) });+            const stop = !!(args.stop || args.clear);+            osFlashWindow(session.profileName, { stop });+            console.log(`${stop ? 'Cleared' : 'Flashed'} taskbar for session "${sessionId}"`);+            sendJSON(res, { success: true, output: JSON.stringify({ ok: true, sessionId, alerted: !stop, cleared: stop, _hint: stop ? 'Cleared the taskbar highlight for this window (FLASHW_STOP).' : 'Flashed the taskbar (finite ~4-pulse) without stealing focus. Pass {stop:true} to clear it.' }) });           } catch (e) {             sendJSON(res, { success: false, error: `Alert failed: ${e.message}` });           }