app
Pup - Puppeteer Bridge
Public Made by Adomby adom
pup is the AI's own browser: a real, full Chrome on the user's desktop that the AI fully controls (a sandbox, not the user's signed-in browser). Rides Bridge; pup_* verbs open windows and tabs, navigate, screenshot, and eval JS.
Stop cross-thread tab contamination (John: 'different ai threads opening pup window tabs into each others windows ... make sure their windows only open in tabs by default in the pup window they own'). Root cause: browser_open_tab with no sessionId fell back to activeSessionId — a SINGLE GLOBAL overwritten on every open by ANY thread — so a thread's tab landed in whatever window was last touched, often another agent's. Fix: the fallback is now callerOwnWindow(callerThread), the caller thread's own most-recently-created live session (matched via _ownerThread, which the bridge already stamps from the X-Adom-Caller-Thread header); the global activeSessionId is no longer a tab target. If the caller owns no window, open_tab now REFUSES with no_target_window + the caller's list of its own windows and a 'open one first' instruction, instead of silently contaminating. Plus a guard: adding a tab to a window owned by a DIFFERENT thread is refused (session_owned_by_another_thread) unless takeover:true, so even an explicit stray sessionId can't cross threads by accident. NOTE: does not touch the separate 'Not Responding' page-hang class John also saw (a spinning page in the herodeck-web/Orbital Lab window) — that is the per-session serialization / runaway-JS work still queued.
John Lauer
·
7bde4bd
·
15d ago
Publish 1.9.145
John Lauer
·
df538a1
·
15d ago
Placement sweep for the STANDING population (John: 'there's a pup window right now that when i click on it i get no browser window' — measured live: v21-review sat at -32000,-32000 all day). Root cause of the gap: every placement fix today applied to NEW windows (open path) and recovery relaunches, while healOffscreenWindow — built for exactly this — was only invoked from a few verb paths and never from boot or a timer, so any window predating the on-screen park kept its off-screen geometry forever. The reparent made it worse in the best way: windows now SURVIVE bridge restarts, so stale geometry survives too. New sweepPlacement asserts placement for every live session at startup (+15s) and every 2 minutes; the healer no-ops on healthy windows, and every actual rescue is reported through the report-once cleanup queue so it reaches the user instead of happening silently.
John Lauer
·
05ad6fd
·
15d ago
Background windows are BORN off-screen again (John: 'you keep opening pup windows on top of the one i'm working in ... it just happened for all those windows you opened'). Regression chain, owned: 1.9.135 removed the process-wide --window-position=-32000 launch default because it poisoned every window's RESTORE rect (the taskbar-click-shows-nothing bug) — but that default was also what made the birth moment invisible, so new windows started appearing at a visible position ON TOP of whatever the user had up (including the pup window they were working in) for the beat before the park pushed them to bottom-z. Fix: Target.createTarget now passes explicit per-window left/top -32000 for the one creation moment; the per-window unconditional park (1.9.138) immediately rewrites the normal rect on-screen with the inset frame at bottom z-order, so the restore-rect poisoning cannot recur (the park always lands, re-asserts at 2.5s, logs both attempts, and healOffscreenWindow sweeps any stragglers). Birth invisible, life on-screen at the bottom, reveal only on the user's click — the full contract.
John Lauer
·
7cb80a3
·
16d ago
Publish 1.9.143
John Lauer
·
760ff8e
·
16d ago
Serialize window opens per profile (John's 3-phase stress test, attempt 3, on the fixed 1.9.142: 7/10 parallel opens survived but 3 still died 'Navigating frame was detached' — the _pupCreated marker lands after creation returns, so a mid-creation target is still visible to a sibling's cleanup — and the sibling page-closes drove windows to zero tabs, at which point Chrome EXITS with its last window, wiping ALL TEN sessions by the fg-sweep phase). New _profileOpenChain: opens on the SAME profile run strictly in arrival order, each completing its create+cleanup before the next begins, while opens on DIFFERENT profiles remain fully parallel. The chain tail never rejects, so one failed open can never wedge the profile's queue. This is the 10,000-user case: a fleet of AI threads simultaneously opening default windows all land on the shared profile.
John Lauer
·
158fc1b
·
16d ago
Publish 1.9.142
John Lauer
·
8f333a8
·
16d ago
Concurrent opens on one profile no longer kill each other (found by John's 3-phase test: 10 PARALLEL opens on the shared profile — 9 of 10 died 'Navigating frame was detached'; every earlier stress pass had opened sequentially, hiding it, and ten threads opening default windows simultaneously is exactly what a fleet of AI threads does). The launch itself was already deduped (_launchInFlight); the kill happened AFTER: each open runs closeRestoredLeftoverTabs, which closes any page it doesn't recognize — including the SIBLING open's just-created page mid-navigation. Every pup-created page is now marked _pupCreated at all three creation sites (new-window path, tab fallback, plain newPage) and the cleanup skips marked pages, so it can only ever close genuine session-restore leftovers, which is its actual job.
John Lauer
·
18f1cc7
·
16d ago
Publish 1.9.141
John Lauer
·
d57529f
·
16d ago
Docs/hints parity release (John: 'are your skills fully updated? and your ai hints?' — audited, found three real gaps, fixed). (1) SKILL.md frontmatter description still said pup launches a FRESH isolated profile, contradicting the durable-shared-profile body from 1.9.123 — the description is what discovery matches and AIs skim, so it was actively lying; rewritten, with new trigger words (browser_login, autofill password, saved login, durable profile, isolated window) and a new 'Logins & credentials' section documenting the whole story: sign-in-once persistence, browser_login, the vault, auto-fill-on-load and its off switch, the Save-password bubble, isolated:true and webSecurity:false. (2) browser_describe had NO entry for browser_login — the verb was undiscoverable by any AI; full V() entry added. (3) The open_window catalog claimed 'profile defaults to sessionId' in two places — false since 1.9.123; now documents the adom-you default plus the new isolated and webSecurity args, and browser_configure documents autoLogin. Also fixed a Python 'True' I initially wrote into the JS example object, which parses (identifier) but would have thrown ReferenceError the first time browser_describe ran.
John Lauer
·
53783a8
·
16d ago
Publish 1.9.140
John Lauer
·
72e5af5
·
16d ago
One flash per open operation (John: 'did you orange flash them twice? ... i had to go click all of them again to expire the orange flash' — measured on the 10-window stress test, every heavy site double-flashed). The launch path flashes instantly and records _openFlashDone; the open handler now CONSUMES that marker and skips its own post-load flash, so suppression is scoped to the OPERATION instead of a 5s time debounce that any heavy site (YouTube, Maps) outruns — the second orange landed AFTER John had clicked, forcing him to re-click every window. Every flash now logs '[flash] <session> (launch|auto)' so a double-flash can never hide in silence again. Also: the complete window-UX doctrine from today's placement war (10 laws: background=behind never off-screen, per-window unconditional park, windowState normal before bounds, re-assert, no -32000 defaults, one flash per operation, no terminal blips ever, atomic reveal, foreground gated on the user's words, patch-the-code-that-runs/prove-on-glass) is now dev-skills/pup-bridge-dev SKILL.md law.
John Lauer
·
41cd599
·
16d ago
Publish 1.9.139
John Lauer
·
fe6e49f
·
16d ago
The 10px inset lands in the REAL park this time. Root cause of two failed proofs: parkSessionWindowDirect already handled the maximized state (windowState normal first) and already measured the true work area (screen.availWidth/Height from the page, DPI-correct) — it simply placed at 0,0 full work-area size, which is exactly the rect both proofs read. The PS sites patched in 1.9.136-1.9.137 (WinBgP/WinWd/WinMin/WinClr) are flash-clear and minimize paths, not the park; those edits were aimed at the wrong target. placeOnScreen now positions at availLeft+10/availTop+10 sized avail-20 (availLeft/availTop also NEW: a taskbar docked left or top shifts the work-area origin, and the old 0,0 assumption would tuck the window under it), and the mac park gets the identical inset. Combined with 1.9.138's per-window unconditional park + 2.5s re-assert, every background window should now wear the deliberate 10px frame.
John Lauer
·
a8efafa
·
16d ago
Publish 1.9.138
John Lauer
·
c8596d5
·
16d ago
Park EVERY background window, and re-assert after Chrome's late sizing — the two-part answer to John's 'why did the park not happen on the gmail window'. Measured in code: _needsPark was a per-LAUNCH flag consumed by the FIRST park, so in the shared durable-profile world (one Chrome, many windows) every window after the first was never parked at all and sat at Chrome's default geometry — historically the -32000 process default, i.e. stranded off-screen with a taskbar button that revealed nothing. The gate is removed: parking is per-WINDOW and unconditional for background opens. Second part: Chrome's own late first-run self-sizing can land AFTER the park and clobber it (the inset-proof test window read 0,0 full-size despite the inset code being live and its math verified on the machine), so the park re-asserts once at +2.5s. Both park attempts now LOG success or failure so a silent no-park can never happen again.
John Lauer
·
f51ac33
·
16d ago
10px inset on every side of the work-area placement (John: 'reduce it by 10px on all sides so i can tell you literally did this purposely') — position = work area X+10,Y+10, size = work area minus 20 in each dimension. A pup window now sits visibly, deliberately framed inside the work area: taskbar never covered, a uniform 10px margin proving the geometry is computed, not accidental.
John Lauer
·
e735e89
·
16d ago
Park and window-sizing now use the primary monitor's WORK AREA instead of raw resolution (John: 'use the work area'). All four GetSystemMetrics(0)/(1) sites swapped for Screen.PrimaryScreen.WorkingArea, with position taken from the work area's X/Y — so a taskbar on ANY edge (bottom, left, top) is respected and no strip of a pup window ever hides behind it. Same behavior as a genuinely maximized window.
John Lauer
·
a7fc801
·
16d ago
Drop --window-position=-32000 and --window-size from background launches (John: 'if --no-startup-window then stop fighting it'). With no-startup-window Chrome starts with NO window, so the off-screen position arg protected nothing on the main path — while silently poisoning every window Chrome itself creates (popups, target=_blank, restores) with an off-screen DEFAULT position, which is the likely mechanism behind windows found stranded at -32768 even after being foregrounded. Geometry now has exactly one owner: the park (on-screen, bottom z, full size, no-activate).
John Lauer
·
7541fae
·
16d ago
Emergency follow-up to 1.9.133, which shipped with a JS SYNTAX ERROR (a python-style # comment inside the PS-lines array — my parse check ran AFTER the ship gate instead of before, the documented patch/ship desync repeated). Also fixes the size John hit immediately: the CDP park kept 1280x800, so on-screen-parked windows appeared small; now near-fullscreen fallback at the CDP site and the SetWindowPos park corrects to the true screen work area right after. Park contract: on-screen, bottom z-order, no-activate, full size.
John Lauer
·
733e049
·
16d ago
Background windows now park ON-SCREEN at the BOTTOM of the z-order instead of at -32000,-32000 (John's design, implemented immediately after the trap bit him twice in one hour: agenda-monday, then the gmail window whose taskbar click showed nothing). Both park sites changed — the CDP Browser.setWindowBounds park and the SetWindowPos park, which already used HWND_BOTTOM + SWP_NOACTIVATE so only the coordinates were wrong. Background still means: no focus steal, covers nothing the user works on (it is BEHIND everything). But the window is now always at real screen coordinates, so a user's plain taskbar click is pure native Windows foregrounding of a correctly-placed window — no pup code in that path, nothing to catch, the failure is structurally impossible. Known accepted tradeoff: bottom-z windows show in desktop peek. The -32000 launch-instant args remain as belt-and-suspenders for the pre-park moment only.
John Lauer
·
0bc7c80
·
16d ago
Queue the double-flash fix: auto-flash is per-mutating-verb (5s debounce) so open+settle flashes twice; change to one flash per logical operation, fired when the window is ready
John Lauer
·
f2555a2
·
16d ago
Promote John's settle-on-screen-at-open design to PRIMARY fix: park only for the launch instant, then place at the real on-screen rect with SWP_NOACTIVATE + HWND_BOTTOM so a taskbar click needs no pup code at all; enforcement layers demoted to safety net
John Lauer
·
1f4ae0d
·
16d ago
Placement-invariant spec: one enforcement function, many triggers — from John's stranded agenda-monday window (user taskbar click activated a window parked 32000px off-screen). Next-session implementation spec plus the queued serialization and reparent items.
John Lauer
·
9d38e8f
·
16d ago
Publish 1.9.132
John Lauer
·
919185a
·
16d ago
Revert the 1.9.131 Start-Process reparent: it fixed a real hazard but broke LAUNCHING entirely — every open died 'CDP never became ready' across all 3 attempts, while the identical Start-Process line run manually via shell_execute launched fine and answered CDP with HTTP 200. The defect is therefore in composing the ~20-arg launch through node spawn -> powershell -Command -> -ArgumentList, not in the approach. Reverted to the proven 1.9.122 cmd spawn (launches, survives restarts) rather than blind-iterating on quoting at the end of a long session: a broken launch on every new window is strictly worse than the rare console-handle hang. The hang stays a KNOWN ISSUE with its measured signature (frozen tab spinner, dead CDP HTTP endpoint, live process, responsive sibling Chromes; recovery = kill the wedged process and reopen, the durable profile lands straight back signed in). The console-free reparent is PARKED with both measurements recorded in code and ship notes for a clean re-land.
John Lauer
·
1d564ee
·
16d ago
Publish 1.9.131
John Lauer
·
2660524
·
16d ago
Fix the full-browser hang introduced by my own 1.9.122 reparent (John: 'why does normal chrome work fine but you get frozen a lot?' — with a frozen tab spinner mid-load). Measured signature: one Chrome process wedged at the UI level (frozen spinner, CDP HTTP endpoint dead, trivial evals timing out) while the process stayed alive and SIBLING pup Chromes on other profiles answered instantly. Root cause: cmd's 'start /b' keeps the child in cmd's transient hidden console and passes that console's handles as Chrome's std handles; when conhost died with cmd, Chrome could block writing to the stale handles — a hazard Explorer-launched Chrome never has (no console at all). The reparent now goes through PowerShell Start-Process, which launches a GUI exe with NO console and null std handles while still orphaning Chrome from the bridge tree (the restart-survival win is kept). Recovery from the live hang cost nothing: kill the wedged process, reopen, and the durable profile lands straight back in Gmail signed in. Remaining known freeze-class, explicitly NOT fixed here: pup serializes verbs per session, so a slow page.goto queues every later verb into 'Request timed out' — that is issue #16 territory and needs its own change.
John Lauer
·
201c06f
·
16d ago
Publish 1.9.130
John Lauer
·
387215d
·
16d ago
Fix the taskbar overlay favicons my own 1.9.129 broke (John: 'why is there no overlay icon in windows taskbar right now?'). Measured cause in the log: fetchPageFavicon ran fetch() INSIDE the page, which only ever worked because --disable-web-security had CORS off globally; when 1.9.129 restored the same-origin policy, any favicon whose fetch crosses origins (accounts.google.com redirects /favicon.ico to www.google.com) began CORS-failing and those windows silently lost their overlay. The fix keeps the DOM read in-page (collecting link rel=icon hrefs is CORS-free) and moves the actual fetch into the BRIDGE process, which has no same-origin policy, with redirect-follow, a 4s timeout, and inline data: icons decoded directly. Accepted tradeoff, documented in code: the bridge fetch carries no page cookies, so a favicon behind auth won't resolve (rare; the count-badge fallback covers it); localhost favicons still work because the bridge runs on the same machine as Chrome. Lesson: removing a long-standing global flag means auditing what silently depended on it — the overlay was an undeclared dependent of disabled web security.
John Lauer
·
383fcee
·
16d ago
Publish 1.9.129
John Lauer
·
7af05e2
·
16d ago
Secure-by-default launch flags, and bring back Chrome's 'Save password?' bubble (John: 'chrome never offered to store the login' / 'i would like chrome to store the login though'). Root cause found in the launch args: --test-type suppresses Chrome's promo/save bubbles, so the password manager never offered; it existed only to hush the warning bars caused by --disable-web-security and --no-sandbox. And --disable-web-security is UNSAFE now that the default profile is durable and holds real logins (John's Gmail as of today): it kills the same-origin policy, so any page opened in a default window could read Gmail cross-origin. All four flags (--no-sandbox, --disable-setuid-sandbox, --disable-web-security, --test-type) are removed from default launches; the desktop sandbox is strictly better protection for a credentialed profile. Dev windows that genuinely need cross-origin eval opt back in per-window with {webSecurity:false}, which FORCES an isolated throwaway profile so the insecure flags can never share a process with the credentialed jar (they are per-process flags; isolation beats any explicit profile arg). prepareCleanProfile now also seeds credentials_enable_service + password_manager_enabled true so the durable profile's whole purpose — accumulating logins — has Chrome's own machinery on. The recorder HUD's separate no-sandbox launch (dedicated credential-free profile) is deliberately untouched.
John Lauer
·
30d3a6d
·
16d ago
Publish 1.9.128
John Lauer
·
79cca23
·
16d ago
Jump list: show BOTH wiki view tasks on every wiki window, always (John's design, asked days ago and dropped by me until he re-flagged it with a screenshot). The old code offered only the direction pup BELIEVED was the toggle, but pup's belief goes stale — auth flips in another window sharing the jar, a cookie expires, a recovery misses state — and when it is wrong the ONE task shown is the useless one. Now any wiki window's jump list carries 'Switch to logged-in view' AND 'Switch to public view' in a stable order (logged-in first) so the user's muscle memory holds; clicking the view the window is already in was verified to be graceful (browser_wiki_set_view returns unchanged:true and simply foregrounds the window). Side benefit: the task list no longer depends on pup's view belief at commit time, which makes the 1.9.116 stale-task bug class structurally impossible.
John Lauer
·
c5fc928
·
16d ago
Publish 1.9.127
John Lauer
·
2139dfe
·
16d ago
Revert 1.9.126 avatar branding — verified it cannot work. Chrome only shows a custom profile photo for a SIGNED-IN Google account (the gaia picture); with no account it PURGES a seeded picture (measured: after one launch Chrome blanked gaia_picture_file_name/gaia_name/gaia_id and DELETED the seeded PNG; the toolbar pill rendered a generic empty circle). pup suppresses sign-in by design (--allow-browser-signin=false), so the pill can't carry a custom image, and a signed-in profile would show the Google photo, not the Adom avatar. Removed brandProfileAvatar, the per-launch reseed, the browser_brand_profile verb, and the branding store. FORCE_PUSH: verified by full diff against the repo that the ONLY change vs 1.9.126 is this avatar removal — browser_login and all 1.9.122-1.9.125 work are preserved intact (an over-broad first revert had clipped browser_login; caught by diffing before push, restored from the repo copy).
John Lauer
·
f7baf51
·
16d ago
Publish 1.9.126
John Lauer
·
b4be5e0
·
16d ago
Brand a pup profile with the user's Adom avatar so the Chrome toolbar profile pill shows their real photo instead of a generic circle (John's request). Chrome only shows a custom photo for a signed-in Google account (the gaia picture), and pup suppresses sign-in — but the gaia picture is ultimately just a local PNG named in the profile's Local State info_cache, so pup seeds that file plus the info_cache fields (gaia_picture_file_name, use_gaia_picture, name) itself, no account needed. New browser_brand_profile verb {profile?, avatarUrl|avatarPath, displayName} downloads the image, seeds the profile, and persists the branding to pup-profile-branding.json; prepareCleanProfile re-applies it on EVERY launch so Chrome's own Local State rewrites can't erase it. Chrome reads branding only at launch, so a live profile must relaunch to show it (the verb reports relaunchNeeded).
John Lauer
·
73596f8
·
16d ago
Publish 1.9.125
John Lauer
·
b7bbacf
·
16d ago
Follow-on: auto-fill a vault-matched login form the instant the page renders, in the background. New autoLoginOnLoad runs after every render verb (open_window/open_tab/navigate/reload). Conservative by design: fires only when the vault holds a credential for that exact origin (so it is a per-host opt-in the user set deliberately), only on real http(s) pages, FILL ONLY never auto-submit (auto-submitting on load risks MFA prompts, lockouts, and wrong-form logins), never throws so it can't block or fail a navigation, is reported to the caller as _autoLogin (added to the output merge list so it survives AD unwrapping on success), and is silenceable with browser_configure {autoLogin:'off'}. This is the automatic counterpart to the explicit browser_login verb from 1.9.124: sign in once, store the credential, and thereafter pup pre-fills the login form on load with no Chrome dropdown and no foregrounding. Patch applied across two indentation variants of the tab.page probe chokepoint (anchored on newlines to avoid substring overlap).
John Lauer
·
386ca29
·
16d ago
Publish 1.9.124
John Lauer
·
e5fc440
·
16d ago
Q4: background login-form autofill with no Chrome floaty menu and no foregrounding. The vault already handled the NATIVE Basic-Auth dialog (page.authenticate); this adds the common case John asked about — HTML <form> logins, where Chrome's password manager would pop a native autofill dropdown that pup cannot click without stealing focus. Because pup owns the credential, it skips Chrome's UI entirely: new credential_vault.fillLoginForm finds the username + password fields (autocomplete=username / type=email / nearest visible text input before the password field) and fills them over CDP using the native value setter plus bubbled input/change events (React/Vue-safe), optionally submitting via the form's submit button or requestSubmit. New browser_login verb {sessionId, submit?, username?, password?, save?, host?} fills from the vault (matched on the tab's current URL) or from explicit creds, and with save:true stores the explicit cred so next time is automatic. Passwords are never echoed back (only the username is returned for confirmation). autofillFormFromVault is exported for a future auto-on-navigation hook. Pairs with the durable 'you' profile: sign in once and the login persists, and pup can re-fill in the background thereafter.
John Lauer
·
c42ee88
·
16d ago
Publish 1.9.123
John Lauer
·
f0fbddd
·
16d ago
Step 2 of the durable-profile plan: default to ONE shared, persistent 'you' profile instead of a throwaway per-session dir. Before this, an open with no explicit profile used the sessionId as the profile name, so every window was a fresh empty jar: signed out of everything, and app localStorage (e.g. Orbital Lab's saved 3D prefs, John's report) vanished when a new window opened. Now the default resolves to DEFAULT_PROFILE ('adom-you'), a durable profile every window shares, so logins/cookies/localStorage accumulate and are shared across all pup windows (the wiki already proved this jar-sharing model with adom-wiki-authed). Sharing a profile does NOT collapse windows into tabs: measured earlier that two sessions on one profile are two OS windows with two taskbar buttons in one Chrome process, so per-window identity is untouched. Order preserved: explicit profile honored; {isolated:true} gives a private throwaway dir (fresh-login test / act-as-other-user); wiki PUBLIC view stays per-session so the logged-out view is genuinely fresh; wiki AUTHED stays the shared vault. Step 1 (reparent so Chrome survives a bridge restart) is the prerequisite that makes a durable shared jar safe from dirty-kill corruption. Open hint and SKILL.md updated to teach the persistent/shared model and the isolation escape hatch.
John Lauer
·
346762e
·
16d ago
Publish 1.9.122
John Lauer
·
d897a2a
·
16d ago
Reparent Chrome out of the bridge's process tree so pup windows survive a bridge restart (step 1 of the durable-profile plan). Measured root cause: Node's { detached:true } gives a new process GROUP but the child stays a TREE child of the bridge node process (parentPid = bridge), so AD's tree-kill on every restart/upgrade takes Chrome with it — verified 7 Chrome procs going to 0 across a restart. Every pup window was dying on every upgrade, and Chrome was never getting a clean shutdown, which also blocks the durable 'you' profile (a repeatedly dirty-killed profile rots). Fix (Windows only; mac/Linux unaffected): launch Chrome through a transient 'cmd /c start' that returns and exits within milliseconds, orphaning Chrome from the bridge tree so a later taskkill /T no longer reaches it. CDP connects by PORT so the lost direct PID handle is harmless; the real Chrome pid is re-resolved after connect by matching --remote-debugging-port in the command line (with a short retry). The launcher's immediate exit is expected, so the alive-check is skipped on the reparent path and CDP is polled for the full budget; failure cleanup kills by profile since child.pid is the exited cmd. Quoting built and passed verbatim (windowsVerbatimArguments) to avoid the cmd re-parse hazard.
John Lauer
·
7853a4c
·
16d ago
Publish 1.9.121
John Lauer
·
f5765a4
·
16d ago
Fix the taskbar tasks I broke in 1.9.115. Root cause, measured on the actual binary: the jump-list tasks and the relaunch/pin command invoke the BUNDLED local adom-desktop-cli.exe, which is an OLDER build than the container CLI and rejects the global flag outright ('error: unexpected argument --ai-thread found'). So adding that flag to satisfy AD's caller-identity requirement made every task command INVALID, and John's 'Close ALL Adom Pup windows' and 'Switch to public view' clicks stopped doing anything at all. Identity now rides inside the verb's own JSON args as a caller block, which AD's documented precedence ranks ABOVE the flag and which every CLI version accepts as just another argument; it is also space-free by construction, dodging the Explorer / ad-taskrun argv re-parsing hazard I introduced on the way. Verified by running the args form against the bundled CLI directly. Lesson recorded: the taskbar path does not use the same CLI binary I test with, so a command that works from the container can be invalid there.
John Lauer
·
52d8800
·
16d ago
Publish 1.9.120
John Lauer
·
61ba34a
·
16d ago
Fix my own 1.9.119 zombie test: '0 tabs' was the wrong condition. Killing a window's Chrome outright does NOT empty the session's tabs array (it still holds dead page handles), so the gate skipped precisely the zombie it was written to catch — verified by killing a window's Chrome and watching the session stay listed with tabCount 1 and no cleanup report. The test is now whether EVERY tab page reports closed/detached, which together with the existing dead-CDP-port check is a definitive zombie. Deliberately conservative: if any tab still reports open the session is left alone, because wrongly dropping a live session is far worse than a lingering dead one. The report-once queue itself was verified working in the same test (a delivered report was gone from the following call).
John Lauer
·
572d59b
·
16d ago