← Commit history

v0.16.21 (insiders): New nb windows land on pup's placement rect by default (rule R1: the primary monitor's work area inset 10 px on every edge, read through ab's desktop_list_monitors, never OS-maximized), so nb and pup windows are placed identically; newWindowSize is fill|95|90|75|chrome with fill the default.

John Lauer ·a116a1914f ·1mo ago ·parent 097fde5
7 files changed +31−22
bin/nb-dashboard+7−7
@@ -20,7 +20,7 @@ const { execFile, spawn } = require('child_process'); const fs = require('fs'); const crypto = require('crypto'); -const VERSION = '0.16.20';           // keep in lockstep with package.json (pkg version)+const VERSION = '0.16.21';           // keep in lockstep with package.json (pkg version) // SEND TO AI IS UN-PARKED (John: "yeah, we are re-enabling the send to ai. it seems to be good now"). // The ah build thread certified the crash family dead on the gate-fixed build and the delivery leg is // proven end to end. The park const is DELETED rather than flipped, so no dead branch can resurrect it.@@ -573,10 +573,10 @@ function page() {     </div>     <div class="settings-card">       <div class="card-title">New windows: <span class="tt">how big they open</span><span class="card-status" id="st-size"></span></div>-      <p class="card-desc">Every window nb opens is sized to this share of your primary monitor's work area and centered. Without it Chrome sizes an extension-created window with its own cascaded default, about 60 percent of a large screen, and never the size you last set by hand. Chrome default hands that decision back to Chrome. A caller that passes its own bounds always wins.</p>+      <p class="card-desc">Every window nb opens is placed on your primary monitor's work area. Fill is pup's placement rule: the work area inset 10 pixels on every edge, the visible sign of a window that was placed on purpose, never the OS-maximized state, which raises. A percentage centers a smaller window. Without any of this Chrome sizes an extension-created window with its own cascaded default, about 60 percent of a large screen, and never the size you last set by hand. A caller that passes its own bounds always wins.</p>       <div class="setting-row"><span class="row-label">New window size</span>-        <select id="sel-size" onchange="setSetting('newWindowSize', this.value)"><option value="95">95% of the screen</option><option value="90">90%</option><option value="85">85%</option><option value="75">75%</option><option value="chrome">Chrome default</option></select></div>-      <div class="default-hint">Default: 95%</div>+        <select id="sel-size" onchange="setSetting('newWindowSize', this.value)"><option value="fill">Fill the work area (10 px inset, same as pup)</option><option value="95">95% centered</option><option value="90">90% centered</option><option value="75">75% centered</option><option value="chrome">Chrome default</option></select></div>+      <div class="default-hint">Default: Fill the work area</div>     </div>     <div class="settings-card">       <div class="card-title">Browser toolbar: <span class="tt">the nbe icon</span><span class="card-status" id="st-icon"></span></div>@@ -753,8 +753,8 @@ function paintSettings(per){   const live = _settingsPer.filter(p => p.settings);   const agg = (k) => { const vals = [...new Set(live.map(p => String(p.settings[k])))]; return vals.length === 1 ? vals[0] : (vals.length ? 'mixed' : 'unknown'); };   const ov = agg('overlayBadge'), ac = agg('taskbarActivity'), fm = agg('flashMode'), ti = agg('toolbarIcon'), ws = agg('newWindowSize');-  const selS = document.getElementById('sel-size'); if (selS) selS.value = ['95','90','85','75','chrome'].includes(ws) ? ws : '95';-  const stS = document.getElementById('st-size'); if (stS) { stS.textContent = ws === 'chrome' ? 'Chrome default' : (ws + '%'); stS.className = 'card-status' + (ws === '95' ? ' ok' : ''); }+  const selS = document.getElementById('sel-size'); if (selS) selS.value = ['fill','95','90','75','chrome'].includes(ws) ? ws : 'fill';+  const stS = document.getElementById('st-size'); if (stS) { stS.textContent = ws === 'chrome' ? 'Chrome default' : ws === 'fill' ? 'fill' : (ws + '%'); stS.className = 'card-status' + (ws === 'fill' ? ' ok' : ''); }   const selI = document.getElementById('sel-icon'); if (selI) selI.value = ['auto','on','off'].includes(ti) ? ti : 'auto';   const stI = document.getElementById('st-icon'); if (stI) { stI.textContent = ({auto:'auto', on:'on', off:'off'})[ti] || ti; stI.className = 'card-status' + (ti === 'auto' ? ' ok' : ''); }   const tg = document.getElementById('tg-overlay'); tg.setAttribute('aria-checked', ov === 'true' ? 'true' : 'false');@@ -793,7 +793,7 @@ let _armed = 0; async function restoreDefaults(btn){   if (!_armed) { _armed = setTimeout(() => { _armed = 0; btn.classList.remove('armed'); btn.textContent = 'Restore defaults'; }, 4000); btn.classList.add('armed'); btn.textContent = 'Click again to confirm'; return; }   clearTimeout(_armed); _armed = 0; btn.classList.remove('armed'); btn.textContent = 'Restore defaults';-  const r = await fetch('api/settings', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ set: { overlayBadge: true, taskbarActivity: 'progress', flashMode: 'quiet', toolbarIcon: 'auto', newWindowSize: '95' } }) });+  const r = await fetch('api/settings', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ set: { overlayBadge: true, taskbarActivity: 'progress', flashMode: 'quiet', toolbarIcon: 'auto', newWindowSize: 'fill' } }) });   const d = await r.json(); paintSettings(d.per); } function copyPrompt2(btn){ try { navigator.clipboard.writeText(document.getElementById('pr2').textContent); btn.textContent = 'Copied!'; } catch(e){} }
bridge/BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.16.20\ No newline at end of file+0.16.21\ No newline at end of file
bridge/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "native-browser",   "displayName": "Adom Native Browser",-  "version": "0.16.20",+  "version": "0.16.21",   "expectedExtensionVersion": "0.2.15",   "description": "Drives the user's REAL signed-in Chrome/Edge, in two layers. Layer 1 (nb) needs NOTHING installed and works on any machine: nbrowser_open_os_window (open a URL in the user's real profile, background+flashing, the OAuth/consent path), nbrowser_os_windows, nbrowser_profiles, nbrowser_wake_profile, nbrowser_force_close. Layer 2 (nbe, the Adom Native Browser Extension) unlocks the rest: CDP driving (navigate/click/read/screenshot) inside their logged-in profile. The bridge binds an ephemeral loopback port and the native-messaging host pumps stdio <-> bridge.",   "homepage": "https://wiki.adom.inc/adom/nb-bridge",
bridge/package.json+1−1
@@ -1,6 +1,6 @@ {   "name": "adom-native-browser-bridge",-  "version": "0.16.20",+  "version": "0.16.21",   "private": true,   "description": "Native-browser bridge for Bridge (ab): layer 1 (nb) plus layer 2 via nbe, the Adom Native Browser Extension (Chrome + Edge).",   "main": "server.js",
bridge/server.js+14−7
@@ -589,8 +589,8 @@ let flashMode = 'quiet';           // quiet | chatty | off  (mirrors SETTINGS.fl //   taskbarActivity enum   progress | flash | off  - what the button does WHILE a verb runs //   flashMode       enum   quiet | chatty | off    - auto-flash verbosity on come-look moments // verbDurations (rolling averages, ms) ride in the same file so the progress bar can be sized.-const SETTINGS_DEFAULTS = { overlayBadge: true, taskbarActivity: 'progress', flashMode: 'quiet', toolbarIcon: 'auto', newWindowSize: '95' };-const SETTINGS_ENUMS = { taskbarActivity: ['progress', 'flash', 'off'], flashMode: ['quiet', 'chatty', 'off'], toolbarIcon: ['auto', 'on', 'off'], newWindowSize: ['95', '90', '85', '75', 'chrome'] };+const SETTINGS_DEFAULTS = { overlayBadge: true, taskbarActivity: 'progress', flashMode: 'quiet', toolbarIcon: 'auto', newWindowSize: 'fill' };+const SETTINGS_ENUMS = { taskbarActivity: ['progress', 'flash', 'off'], flashMode: ['quiet', 'chatty', 'off'], toolbarIcon: ['auto', 'on', 'off'], newWindowSize: ['fill', '95', '90', '75', 'chrome'] }; // NEW WINDOW SIZE (John, 2026-09-18: "you should be opening nb windows at like 95% the size of my screen"). // An extension-created Chrome window with no bounds gets Chrome's cascaded default, about 60% of a big // screen, and never the user's last hand-set size. So every nb open carries bounds: newWindowSize percent@@ -611,11 +611,18 @@ async function primaryWorkArea() { } function callerGaveBounds(a) { return !!(a && (a.width != null || a.height != null || a.left != null || a.top != null || a.state)); } async function defaultWindowBounds() {-  const pct = Number(SETTINGS.newWindowSize);-  if (!Number.isFinite(pct)) return null;                       // 'chrome': leave it to Chrome+  const mode = String(SETTINGS.newWindowSize);+  if (mode === 'chrome') return null;                           // leave it to Chrome   const wa = await primaryWorkArea(); if (!wa) return null;+  if (mode === 'fill') {+    // pup's placement rule R1 (John): the primary work area inset 10 px on all four sides. The inset is+    // deliberate and visible, it is how you can tell a window was PLACED rather than landing somewhere by+    // accident; never the OS-maximized state, which raises. nb and pup windows now land on the same rect.+    return { left: wa.x + 10, top: wa.y + 10, width: Math.max(400, wa.width - 20), height: Math.max(300, wa.height - 20), _from: `the primary monitor's work area (${wa.width}x${wa.height} logical px) inset 10 px, pup's R1` };+  }+  const pct = Number(mode);   const w = Math.round(wa.width * pct / 100), h = Math.round(wa.height * pct / 100);-  return { left: wa.x + Math.round((wa.width - w) / 2), top: wa.y + Math.round((wa.height - h) / 2), width: w, height: h, _from: `${pct}% of the primary monitor's work area (${wa.width}x${wa.height} logical px)` };+  return { left: wa.x + Math.round((wa.width - w) / 2), top: wa.y + Math.round((wa.height - h) / 2), width: w, height: h, _from: `${pct}% of the primary monitor's work area (${wa.width}x${wa.height} logical px), centered` }; } // toolbarIcon (John, 2026-09-12): what the nbe's toolbar slot shows. auto = Chrome's default (icon exists, // unpinned, the small surface for a user's own browser); on = icon + "nb" badge + a clear title (for a host@@ -1685,7 +1692,7 @@ const VERB_META = {   nbrowser_flash: { hint: 'Flash the driven window\'s taskbar button - the "come look" cue (e.g. at a captcha/2FA/login human-gate).', related: ['nbrowser_flash_stop', 'nbrowser_flash_mode'], pitfalls: ['auto-clears on focus; nbrowser_flash_stop to clear early'] },   nbrowser_flash_stop: { hint: 'Stop the taskbar flash.', related: ['nbrowser_flash'], pitfalls: ['the flash already auto-clears when the user focuses the window - only stop it early if you no longer need their attention'] },   nbrowser_apply_settings: { hint: 'INTERNAL push from the bridge to a connected extension (toolbarIcon and future per-extension settings); sent on every hello and on nbrowser_settings changes. Not for callers: set settings with nbrowser_settings.', related: ['nbrowser_settings'], pitfalls: ['calling it directly changes nothing durable - the bridge re-pushes the stored settings on the next hello'] },-  nbrowser_settings: { hint: 'Read or set the user settings behind the NB Dashboard Settings panel: overlayBadge (bool), taskbarActivity (progress|flash|off), flashMode (quiet|chatty|off), newWindowSize (95|90|85|75|chrome: percent of the primary monitor\'s work area every new nb window opens at, centered; chrome = Chrome\'s own default, which is about 60% of a big screen and never the user\'s last hand-set size), toolbarIcon (auto|on|off: what the nbe\'s browser toolbar slot shows; auto = Chrome default, unpinned; on = icon with an nb badge for a host that PINS it, such as pup; off = transparent and disabled). {} reads; {set:{...}} writes, validated by type, persisted on the desktop, and returns the STORED literal.', related: ['nbrowser_flash_mode', 'nbrowser_taskbar'], pitfalls: ['settings are PER DESKTOP (stored on the machine the bridge runs on) - set them on every --target the user uses', 'a rejected key changes nothing; read the returned settings rather than assuming your write landed', 'overlayBadge:false also restores the native avatar on windows currently badged'] },+  nbrowser_settings: { hint: 'Read or set the user settings behind the NB Dashboard Settings panel: overlayBadge (bool), taskbarActivity (progress|flash|off), flashMode (quiet|chatty|off), newWindowSize (fill|95|90|75|chrome: fill = the primary monitor\'s work area inset 10 px on every edge, pup\'s placement rule R1, the default; a percent = that share of the work area, centered; chrome = Chrome\'s own default, about 60% of a big screen and never the user\'s last hand-set size), toolbarIcon (auto|on|off: what the nbe\'s browser toolbar slot shows; auto = Chrome default, unpinned; on = icon with an nb badge for a host that PINS it, such as pup; off = transparent and disabled). {} reads; {set:{...}} writes, validated by type, persisted on the desktop, and returns the STORED literal.', related: ['nbrowser_flash_mode', 'nbrowser_taskbar'], pitfalls: ['settings are PER DESKTOP (stored on the machine the bridge runs on) - set them on every --target the user uses', 'a rejected key changes nothing; read the returned settings rather than assuming your write landed', 'overlayBadge:false also restores the native avatar on windows currently badged'] },   nbrowser_flash_mode: { hint: 'Set auto-flash verbosity: quiet (come-look moments: page load, screenshot, error/refusal, human-gate, spawned window) | chatty (every action) | off (no auto-flash at all). Flash manually with nbrowser_flash at task-done.', related: ['nbrowser_flash'], pitfalls: ['off is the user opt-out - if the user says the orange flashing is annoying, set flash_mode:"off" and it stops entirely', 'quiet is the default; the flash is a "come look" cue, so keep it for human-gates even if you dial the rest down'] },   nbrowser_taskbar: { hint: 'Paint progress/overlay/flash on the window taskbar button (e.g. paused=yellow for a human gate). The "Adom is driving" badge is AUTOMATIC: the bridge composites the profile\'s circle avatar + the Adom favicon at open/drive, and idle-expires it (~3 min) back to the plain native avatar - you rarely need to touch overlay yourself.', related: ['nbrowser_flash'], pitfalls: ['clear progress+flash at task end: {progress:{state:"none"},flash:"stop"} - the badge clears itself (idle-expiry / close)', 'overlay.badge:"none" RESTORES the native avatar look (the bridge never blanks the overlay slot - blanking would erase the user\'s profile avatar)'] },   nbrowser_describe: { hint: 'Returns per-verb schemas + hint/related/pitfalls for ab\'s Verbs tab.', related: ['nbrowser_help'], pitfalls: ['this is the catalog - a few extension-only verbs (e.g. nbrowser_login_form) are real but resolved in the extension, not listed here'] },@@ -1936,7 +1943,7 @@ function dispatch(verb, args) {         d('nbrowser_install_chrome', 'Install NATIVE Google Chrome for the user (Windows). Downloads Google\'s official installer + runs a quiet user-scoped install (no admin on a normal PC); escalates to a notify-first UAC on a locked-down box. NON-BLOCKING - poll nbrowser_chrome_status. Step 1 of putting the AI onto the user\'s real signed-in browser.', { elevate: 'optional "auto"(default) | true (skip quiet, straight to UAC) | false (quiet only, no prompt)', retryUac: 'optional bool - re-raise the Windows security prompt from the installer already on this PC (no re-download), for a caller with eyes on the screen when the toast could not be seen' }, { ok: 'bool', installing: 'bool', installed: 'bool?', alreadyInstalled: 'bool?', executablePath: 'str?', statusVerb: 'str' }, 30, { statusVerb: 'nbrowser_chrome_status', example: {} }),         d('nbrowser_chrome_status', 'READ-ONLY: is native Google Chrome installed / how is an install going? Poll after nbrowser_install_chrome.', {}, { notifications: '{checked,toastsEnabled,adomBridgeEnabled,toastMayBeMuted,_hint} - Windows toast switches, so a caller knows when the Approve-now toast could not have been seen', canRetryUac: 'bool', installed: 'bool', executablePath: 'str?', phase: '"idle"|"installing"|"awaiting_uac"|"ready"|"failed"', installing: 'bool', awaitingUac: 'bool', errorCode: 'str?' }, 15, { example: {} }),         d('nbrowser_ping', 'Round-trip liveness check through to the extension.', {}, { ok: 'bool', pong: 'bool' }, 15),-        d('nbrowser_open_window', 'Open a NEW window in the BACKGROUND (unfocused) and return a sessionId to drive it. REGISTRATION REQUIRED: sessionId + thread + purpose, or refused (session_unregistered). Never steals focus; a topmost driven window is auto-reverted (focus_violation).', { left: 'optional int (logical px)', top: 'optional int', width: 'optional int (default: the newWindowSize setting, 95% of the primary work area, centered)', height: 'optional int', state: 'optional "maximized"|"normal"', sessionId: 'required str (your short task id)', thread: 'required str (which AI thread you are)', purpose: 'required str (human-readable, e.g. "finishing John\'s IRS signup")', url: 'required str', profile: P, userRequestedForeground: 'optional bool - raise to foreground ONLY when the user asked or a browser/auth feature requires it (default false)', foregroundReason: 'REQUIRED with userRequestedForeground - human-readable why; without it the window STAYS background. Warn the user via ab notify_user FIRST.' }, { ok: 'bool', sessionId: 'str', tabId: 'str', windowId: 'int', hwnd: 'int (OS window handle - use with desktop_screenshot_window to capture the window+chrome+child popups)', url: 'str', _warning: 'str?', keptBackground: 'bool?' }, 30, { example: { sessionId: 'irs', thread: 'thread-7', purpose: 'finishing John IRS signup', url: 'https://wiki.adom.inc' } }),+        d('nbrowser_open_window', 'Open a NEW window in the BACKGROUND (unfocused) and return a sessionId to drive it. REGISTRATION REQUIRED: sessionId + thread + purpose, or refused (session_unregistered). Never steals focus; a topmost driven window is auto-reverted (focus_violation).', { left: 'optional int (logical px)', top: 'optional int', width: 'optional int (default: the newWindowSize setting; fill = the work area inset 10 px, pup R1)', height: 'optional int', state: 'optional "maximized"|"normal"', sessionId: 'required str (your short task id)', thread: 'required str (which AI thread you are)', purpose: 'required str (human-readable, e.g. "finishing John\'s IRS signup")', url: 'required str', profile: P, userRequestedForeground: 'optional bool - raise to foreground ONLY when the user asked or a browser/auth feature requires it (default false)', foregroundReason: 'REQUIRED with userRequestedForeground - human-readable why; without it the window STAYS background. Warn the user via ab notify_user FIRST.' }, { ok: 'bool', sessionId: 'str', tabId: 'str', windowId: 'int', hwnd: 'int (OS window handle - use with desktop_screenshot_window to capture the window+chrome+child popups)', url: 'str', _warning: 'str?', keptBackground: 'bool?' }, 30, { example: { sessionId: 'irs', thread: 'thread-7', purpose: 'finishing John IRS signup', url: 'https://wiki.adom.inc' } }),         d('nbrowser_close_window', 'Close a window the agent opened. includeSpawned:true also closes everything that session SPAWNED (composes/popups).', { sessionId: 'required str', includeSpawned: 'optional bool (also close this session\'s spawned windows)', profile: P }, { ok: 'bool', closed: 'bool', sessionId: 'str', alsoClosedSpawned: '[int]' }, 20, { example: { sessionId: 'work', includeSpawned: true } }),         d('nbrowser_owned', 'List everything a session owns: opened windows + adopted SPAWNED windows/tabs (compose/popup/target=_blank a driven page opened). Acknowledges spawns.', { sessionId: 'optional str (all sessions if omitted)', profile: P }, { ok: 'bool', opened: '[{sessionId,windowId,url,title,purpose,ageMs}]', spawned: '[{kind,windowId,tabId,url,ageMs,spawnedBy,opener}]' }, 20, { example: { sessionId: 'irs' } }),         d('nbrowser_cleanup', 'Close ONLY what a session SPAWNED (leaked composes/popups). Never the user\'s windows, never your primary opened window.', { sessionId: 'required str', profile: P }, { ok: 'bool', closed: '[{windowId,url}]', count: 'int', keptOpenedWindow: 'int?' }, 20, { example: { sessionId: 'irs' } }),
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "nb-bridge",-  "version": "0.16.20",+  "version": "0.16.21",   "type": "app",   "title": "NB - Native Browser Bridge",   "description": "This package installs the SKILLS into your container so your AI knows how to drive your real signed-in Chrome and Edge; Bridge loads the bridge runtime itself from the release zip.",
skills/driving-the-extension/SKILL.md+6−4
@@ -296,11 +296,13 @@ This is **not** screenshot→eyeball-a-pixel→click automation, so it does **no   the app state. **An unknown `input_dispatch` type is REFUSED (`unknown_input_type`)**; before 0.2.10 it fell   through to a click, which on a canvas editor with a tool armed placed four clicks for four zoom attempts.   Valid types: click | dblclick | rightclick | move | hover | wheel | type | key.-- **Every nb window opens at 95% of the primary monitor's work area, centered (bridge 0.16.20).** Chrome-  sizes an extension-created window with its own cascaded default, about 60% of a big screen, never the-  user's last hand-set size (John: "annoying as hell"). The bridge now passes bounds on every+- **Every nb window opens on pup's placement rect: the primary monitor's work area inset 10 px on every+  edge (bridge 0.16.21, pup rule R1).** Chrome sizes an extension-created window with its own cascaded+  default, about 60% of a big screen, never the user's last hand-set size (John: "annoying as hell"). The+  bridge reads the monitor through ab's `desktop_list_monitors` and passes bounds on every   `nbrowser_open_window` and `--window-size/--window-position` on every Layer-1 launch, from the-  `newWindowSize` setting (95|90|85|75|chrome). Your own `left/top/width/height/state` always win.+  `newWindowSize` setting (fill|95|90|75|chrome). Never the OS-maximized state, which raises. Your own+  `left/top/width/height/state` always win. - **The nbe's browser TOOLBAR icon is a setting (`nbrowser_settings {set:{toolbarIcon:"auto"|"on"|"off"}}`,   ext 0.2.11 / bridge 0.16.15).** `auto` (default) is Chrome's own default for an unpacked extension: the icon   exists but stays UNPINNED under the puzzle menu, the small surface John wants inside a user's own browser.