← Commit history

v1.8.43: route pup's taskbar flash + clear through AD-core desktop_flash_window (native, reliable) instead of pup's own FlashWindowEx; browser_alert_window now returns AD's real applied result (no fire-and-forget cleared:true lies)

John Lauer ·d0c45bdd7b · ·parent b91346d
4 files changed +35−46
adom-bridge-puppeteer-manifest.json+5−5
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "puppeteer",-  "version": "1.8.42",-  "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.8.42/adom-bridge-puppeteer-v1.8.42.zip",-  "sha256": "4d8a3965e66381008f7435a07e6a0ae715180456bb0063e7fbc8695eec140a1f",-  "size": 160110,+  "version": "1.8.43",+  "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.8.43/adom-bridge-puppeteer-v1.8.43.zip",+  "sha256": "14435f0ead47ea81a211e5a5f1345df2e108c029d838b96ddc0df42b52ee5d5c",+  "size": 160144,   "verbPrefixes": [     "browser_",     "desktop_recorder_",@@ -12,5 +12,5 @@   ],   "healthEndpoint": "/status",   "statusVerb": "browser_readiness",-  "released_at": "2026-07-14T22:13:43Z"+  "released_at": "2026-07-14T23:54:24Z" }\ No newline at end of file
src/BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.8.42+1.8.43
src/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "puppeteer",   "displayName": "Puppeteer (Chrome for Testing + Native)",-  "version": "1.8.42",+  "version": "1.8.43",   "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+28−39
@@ -887,6 +887,7 @@ async function rescanProfile(browser, profileName, { adoptOrphans = false } = {}     let session = sessions.get(sessionId);     if (!session) {       session = createSession(profileName);+      session._sessionId = sessionId; // v1.8.43: for AD taskbar/flash titleContains resolution       sessions.set(sessionId, session);     } else if (session.profileName !== profileName) {       // SessionId collision across profiles — shouldn't happen in practice@@ -1084,6 +1085,7 @@ async function recoverSessions() {        const session = createSession(profileName);       attachTab(session, sessionId, page);+      session._sessionId = sessionId; // v1.8.43: for AD taskbar/flash titleContains resolution       sessions.set(sessionId, session);       const be = browsers.get(profileName);       if (be) be.refCount++;@@ -1645,38 +1647,20 @@ 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, opts) {-  if (process.platform !== 'win32') return;+// v1.8.43: pup's taskbar flash routes through AD-CORE desktop_flash_window (native ++// reliable, ONE mechanism for flash AND clear) instead of pup's own FlashWindowEx — which+// duplicated an AD capability, and whose fire-and-forget FLASHW_STOP wouldn't actually+// clear the Win11 attention tint. Resolves the window by its "(session: <id>)" title suffix.+// {stop:true} clears it; otherwise a finite 4-count flash. Returns AD's real result so+// callers can report truthfully (no more cleared:true lies).+async function flashViaAD(sessionId, opts) {   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 psScript = path.join(require('os').tmpdir(), `_adom_flash_${pid}.ps1`);-    const ps = [-      'Add-Type @"',-      'using System; using System.Runtime.InteropServices;',-      'public class WinFlash {',-      '  [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();',-      // 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, $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);-  } catch (e) {}+  if (process.platform !== 'win32' || !sessionId) return null;+  const args = opts.stop+    ? { titleContains: `(session: ${sessionId}`, stop: true }+    : { titleContains: `(session: ${sessionId}`, mode: 'count', count: 4 };+  try { return await chrome.adCommand('desktop_flash_window', args, { timeoutMs: 6000 }); }+  catch (e) { return null; } }  // v1.8.23: verbs that MUTATE a session's visible state → auto-flash the window so the@@ -1685,16 +1669,16 @@ const MUTATING_VERBS = new Set(['browser_open_window', 'browser_open_tab', 'brow  // Debounced auto-flash: one flash per window per ~5s burst of verbs, re-armed after // quiet. {silent:true} on the verb OR session._flashSilent (session default) suppresses.-const _flashState = new Map(); // profileName -> last flash ts+const _flashState = new Map(); // sessionId -> last flash ts function scheduleAutoFlash(session, opts) {   opts = opts || {};-  if (process.platform !== 'win32' || !session) return;+  if (process.platform !== 'win32' || !session || !session._sessionId) return;   if (opts.silent || session._flashSilent) return;-  const key = session.profileName;+  const key = session._sessionId;   const now = Date.now();   if (now - (_flashState.get(key) || 0) < 5000) return; // coalesce the burst   _flashState.set(key, now);-  osFlashWindow(key);+  flashViaAD(session._sessionId, {}); // fire-and-forget (async → AD desktop_flash_window) }  // Record what the agent just did to a session, for lastAgentUpdate (browser_list_tabs /@@ -2270,7 +2254,7 @@ async function launchSession(sessionId, url, { freshProfile = false, profile, st   // real URL's domcontentloaded, which for heavy sites is another 1–3s. Set _flashState so   // the open handler's later scheduleAutoFlash debounces (no double flash). Fire-and-forget.   if (!foreground && !silent && process.platform === 'win32') {-    try { _flashState.set(profileName, Date.now()); osFlashWindow(profileName); } catch (e) {}+    try { _flashState.set(sessionId, Date.now()); flashViaAD(sessionId, {}); } catch (e) {}   }    if (url) {@@ -2328,6 +2312,7 @@ async function launchSession(sessionId, url, { freshProfile = false, profile, st   // resolves in attachTab itself if unset.   if (downloadPath) session._downloadPath = downloadPath;   attachTab(session, sessionId, page);+  session._sessionId = sessionId; // v1.8.43: for AD taskbar/flash titleContains resolution   sessions.set(sessionId, session);   activeSessionId = sessionId; @@ -3415,9 +3400,13 @@ const server = http.createServer(async (req, res) => {           const session = sessions.get(sessionId);           try {             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.' }) });+            // v1.8.43: route through AD's desktop_flash_window (reliable, native). AWAIT the+            // real result — no more fire-and-forget cleared:true lies.+            const r = await flashViaAD(session._sessionId || sessionId, { stop });+            let applied = false;+            try { const o = r && (typeof r.output === 'string' ? JSON.parse(r.output) : r.output || r); applied = !!(o && (o.ok || o.count)); } catch (e) {}+            console.log(`${stop ? 'Cleared' : 'Flashed'} taskbar for session "${sessionId}" (AD applied=${applied})`);+            sendJSON(res, { success: true, output: JSON.stringify({ ok: true, sessionId, alerted: !stop, cleared: stop, applied, _hint: applied ? (stop ? 'Cleared the taskbar flash for this window via AD desktop_flash_window.' : 'Flashed the taskbar (finite 4-count) via AD, no focus steal. Pass {stop:true} to clear.') : 'AD desktop_flash_window did not match the window (title changed? window closed?) — nothing applied.' }) });           } catch (e) {             sendJSON(res, { success: false, error: `Alert failed: ${e.message}` });           }