← Commit history

1.8.93: wiki dual-view (public + authed persistent profile) with one-time SSO login and verify-visibility hints

John Lauer ·641851ee8b · ·parent 13f3c1e
6 files changed +86−8
CHANGELOG.md+14
@@ -1,5 +1,19 @@ # Changelog — Adom Desktop Puppeteer Bridge +## 1.8.93 — 2026-07-19 — Wiki dual-view: public (default) + logged-in (wikiView:"authed")++pup can now open a wiki.adom.inc page from EITHER viewpoint, and hints teach the AI to show BOTH+so the user can verify publish visibility ("did private source leak to the public?").+- Default open = PUBLIC/anonymous (fresh profile, logged out — what the world sees).+- `browser_open_window {url, wikiView:"authed"}` routes to a reserved PERSISTENT profile whose+  on-disk cookie jar holds the 30-day SSO session. First use returns `wikiLoginNeeded:true` with+  a hint to raise the window for a one-time hydrogen.adom.inc SSO; thereafter it stays logged in.+- No token→cookie shortcut exists (wiki auth is cookie-based SSO; the container CLI bearer is a+  separate credential) — the persistent profile IS the mechanism, so no manual cookie+  capture/cache/reinject is needed; the 30-day cookie lives in the profile and re-auth only fires+  on real expiry (a logged-out check catches it). Dual-view hints on every wiki open; the+  pup-adom-wiki skill documents the verify-visibility workflow.+ ## 1.8.92 — 2026-07-19 — App-favicon overlay: a pup window WEARS the app that drives it  John's standard: every Adom app already serves a favicon (<link rel="icon">, monochrome white
adom-bridge-puppeteer-manifest.json+4−4
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "puppeteer",-  "version": "1.8.92",-  "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.8.92/adom-bridge-puppeteer-v1.8.92.zip",-  "sha256": "e8fc86696b86b59f3f8312ce9802b3cdeb8ca5177fdbdca5f35b5bc732822dd7",-  "size": 205278,+  "version": "1.8.93",+  "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.8.93/adom-bridge-puppeteer-v1.8.93.zip",+  "sha256": "10d8c99cc1aceb4e2535051dced7c7ac6f9d3651785e6d5deaa86f3fc43bc792",+  "size": 206809,   "verbPrefixes": [     "browser_",     "desktop_recorder_",
skills/pup-adom-wiki/SKILL.md+26
@@ -45,6 +45,32 @@ The wiki renders widgets (download cards, sample-prompt cards, the hero) and oft  ## Login — pup sees the wiki LOGGED OUT (for now) +## Two views: PUBLIC (default) and LOGGED-IN (`wikiView:"authed"`) — and why you show BOTH++pup can now open a wiki page from **either viewpoint**, and the killer use is showing the user+**both** to verify publish visibility:++- **Public / anonymous (default):** `browser_open_window {sessionId, url:"https://wiki.adom.inc/..."}`+  — a fresh logged-out profile: exactly what the WORLD sees.+- **Logged-in:** `browser_open_window {sessionId, url:"...", wikiView:"authed"}` — routes to pup's+  reserved persistent authed-wiki profile whose cookie jar holds a 30-day SSO session.++**THE verification pattern (do this after any publish where visibility matters):** open the page+BOTH ways and show the user side by side. Example John cares about: *"I published an app where the+public should get only the binaries but I keep the source private — did the AI accidentally leak+the source?"* The public view answers that directly (if you can see `src/` there, it leaked). The+response `_hint` tells you which view you opened and nudges you to open the other.++**One-time login for the authed view.** The first `wikiView:"authed"` open (or after the ~30-day+session expires) comes back with `wikiLoginNeeded:true`. To sign in: `browser_raise_os_window`+with a `foregroundReason` like `"sign into the Adom wiki"` so the user completes the+hydrogen.adom.inc SSO once; the session then persists in the authed profile and every later+`wikiView:"authed"` open is logged in automatically. Tell the user it is a one-time login.+(Container-identity auto-login — no interactive step — is still tracked in `adom/wiki` #89; when it+lands, this becomes zero-touch.)++## Historical note — pup used to be logged-out only+ pup drives a **fresh, isolated browser** with no cookies, so it views the wiki **logged out** — it sees only public content. There is currently **no supported way to auto-login pup as the container's identity**: the wiki uses centralized Adom SSO (`hydrogen.adom.inc`), the container's token works only as an API
src/BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.8.92\ No newline at end of file+1.8.93\ 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": "1.8.92",+  "version": "1.8.93",   "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. 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, \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.",   "homepage": "https://wiki.adom.inc/adom/adom-desktop",   "author": "Adom Inc.",
src/server.js+40−2
@@ -1819,6 +1819,34 @@ async function brandSweep(reason) { // URL (localhost / *.adom.cloud proxy) serving NO favicon gets a loud reprimand hint teaching // the standard. When an app icon is worn, it takes the overlay slot; the tab count moves to // the hover tooltip (grouped mode's dual counter is unaffected).+// v1.8.93: WIKI DUAL-VIEW + AUTH. pup drives a fresh anonymous profile by default → the wiki+// renders LOGGED OUT (public view). For the LOGGED-IN view, route to a reserved PERSISTENT+// profile whose on-disk cookie jar holds the SSO session (30-day) — log in once via SSO, reused+// for a month, re-auth only on expiry. No token→cookie exchange exists (wiki auth is cookie-based+// SSO to hydrogen.adom.inc; the container's CLI bearer is a different credential). The two views+// together let a user VERIFY publish visibility — e.g. "did source leak to the public?" (a+// hide_source page shows source to authed, binaries-only to public).+const WIKI_AUTH_PROFILE = 'adom-wiki-authed';+function isWikiUrl(u) { try { return /(^|\.)wiki\.adom\.inc$/i.test(new URL(u).hostname); } catch (e) { return false; } }+// Ask the page (using ITS cookies) whether the wiki session is authenticated.+async function checkWikiAuth(page) {+  try {+    return await page.evaluate(async () => {+      try { const r = await fetch('/auth/me', { credentials: 'same-origin' }); if (!r.ok) return { authed: false }; const j = await r.json(); return { authed: !!(j && j.user), user: j && j.user && (j.user.displayName || j.user.username) }; }+      catch (e) { return { authed: false }; }+    });+  } catch (e) { return { authed: false }; }+}+function wikiViewHint(url, view, auth) {+  if (!isWikiUrl(url)) return '';+  if (view === 'authed') {+    if (auth && auth.authed) return ` 🔓 WIKI (LOGGED-IN view as ${auth.user || 'the user'}). To ALSO show the PUBLIC view — and CATCH visibility leaks (e.g. did source get published that should be private?) — open the SAME url WITHOUT wikiView (default = anonymous/public) and show the user BOTH side by side.`;+    return ` 🔑 WIKI LOGIN NEEDED: this window uses pup's persistent authed wiki profile but it is NOT signed in yet (first use, or the ~30-day session expired). To sign in ONCE: raise this window (browser_raise_os_window with a foregroundReason like "sign into the Adom wiki") so the user completes the hydrogen.adom.inc SSO; the session then persists in this profile and every later wikiView:"authed" open is logged in automatically. Tell the user this is a one-time login.`;+  }+  // anonymous/public open+  return ` 📖 WIKI (PUBLIC/anonymous view — pup's default fresh profile, logged out). This is the view the WORLD sees. To ALSO see the user's LOGGED-IN view (private source, unpublished drafts, owner-only cards), open the same url with wikiView:"authed". Showing BOTH is the way to verify what is/ isn't public before/after a publish.`;+}+ async function fetchPageFavicon(page) {   try {     return await page.evaluate(async () => {@@ -3974,7 +4002,7 @@ const server = http.createServer(async (req, res) => {                      hint: m.hint || summary, related: m.related || [], pitfalls: m.pitfalls || [] };           };           sendJSON(res, { success: true, bridge: 'puppeteer', verbs: [-            V('browser_open_window', 'Open a generic pup window — a fresh isolated Chrome/Edge process (never your real signed-in browser), fully drivable via CDP. ALWAYS opens in the BACKGROUND (does not steal focus; never disturbs the user\'s main work; still fully screenshottable/drivable) UNLESS you pass foreground:true — do that ONLY when the user explicitly wants to watch. Pass width+height (+ optional x,y, screen px) to place it at a specific size/position for screenshot framing — this NO LONGER foregrounds it (sizing stays in the background too; default unsized is maximized). profile defaults to sessionId. OWNERSHIP: sessionIds are shared across ALL AI threads — use a task-prefixed sessionId + owner:"<your-task>" so pup can refuse other threads stealing your window (reusing an existing id with a new url navigates it away; declared cross-owner reuse errors with session_owned_by_another_thread unless takeover:true). (For the user\'s REAL Chrome/Edge, use the Adom extension\'s nbrowser_* verbs.)', { sessionId: 'required str — task-prefix it (e.g. "mytask-web"); generic ids collide across threads', url: 'optional str', profile: 'optional str — defaults to sessionId', owner: 'optional str — your thread/task label; lets pup protect this window from other threads', takeover: 'optional bool — claim a window owned by another thread (only when the user explicitly asks)', foreground: 'optional bool — request showing the window on the user\'s screen. GATED (v1.8.70): requires foregroundReason too, else the open succeeds but stays BACKGROUND with foregroundDenied set. Only when the USER explicitly asked to watch.', foregroundReason: 'required WITH foreground:true — quote/paraphrase what the user said that justifies putting a window on their screen (min 10 chars; logged + recorded in lastAgentUpdate, auditable)', width: 'optional int px — sizes for screenshot framing, stays background', height: 'optional int px', x: 'optional int px', y: 'optional int px', appName: 'optional str — the APP this window is driving (e.g. "shotlog"); shown in the hover tooltip', appIconB64: 'optional base64 PNG — override the app icon worn in the taskbar overlay. Usually UNNECESSARY: pup auto-fetches the page\'s own <link rel="icon"> favicon (the adom-ui-design standard artifact every app must serve) and wears it automatically. An app-looking URL with NO favicon gets a reprimand hint.', updateNote: 'optional str — a short "what I did" note, surfaced back in list_tabs/list_windows as lastAgentUpdate so the user knows what changed', autoFlash: 'optional bool (default true) — mutating verbs auto-flash this window\'s taskbar (debounced, never steals focus) so the user notices the agent touched it; pass false to make this window silent by default', silent: 'optional bool — suppress the auto-flash for just this call' }, 60, { sessionId: 'mytask-web', owner: 'mytask', url: 'https://example.com', width: 960, height: 540 }),+            V('browser_open_window', 'Open a generic pup window — a fresh isolated Chrome/Edge process (never your real signed-in browser), fully drivable via CDP. ALWAYS opens in the BACKGROUND (does not steal focus; never disturbs the user\'s main work; still fully screenshottable/drivable) UNLESS you pass foreground:true — do that ONLY when the user explicitly wants to watch. Pass width+height (+ optional x,y, screen px) to place it at a specific size/position for screenshot framing — this NO LONGER foregrounds it (sizing stays in the background too; default unsized is maximized). profile defaults to sessionId. OWNERSHIP: sessionIds are shared across ALL AI threads — use a task-prefixed sessionId + owner:"<your-task>" so pup can refuse other threads stealing your window (reusing an existing id with a new url navigates it away; declared cross-owner reuse errors with session_owned_by_another_thread unless takeover:true). (For the user\'s REAL Chrome/Edge, use the Adom extension\'s nbrowser_* verbs.)', { sessionId: 'required str — task-prefix it (e.g. "mytask-web"); generic ids collide across threads', url: 'optional str', profile: 'optional str — defaults to sessionId', owner: 'optional str — your thread/task label; lets pup protect this window from other threads', takeover: 'optional bool — claim a window owned by another thread (only when the user explicitly asks)', foreground: 'optional bool — request showing the window on the user\'s screen. GATED (v1.8.70): requires foregroundReason too, else the open succeeds but stays BACKGROUND with foregroundDenied set. Only when the USER explicitly asked to watch.', foregroundReason: 'required WITH foreground:true — quote/paraphrase what the user said that justifies putting a window on their screen (min 10 chars; logged + recorded in lastAgentUpdate, auditable)', width: 'optional int px — sizes for screenshot framing, stays background', height: 'optional int px', x: 'optional int px', y: 'optional int px', wikiView: 'optional "authed"|"public" — for wiki.adom.inc URLs. Default (omit) = PUBLIC/anonymous view (logged out, what the world sees). "authed" routes to pup\'s reserved persistent authed-wiki profile (30-day SSO session; first use needs a one-time login — the response says wikiLoginNeeded). Open the same page BOTH ways to verify publish visibility (e.g. did private source leak to the public?).', appName: 'optional str — the APP this window is driving (e.g. "shotlog"); shown in the hover tooltip', appIconB64: 'optional base64 PNG — override the app icon worn in the taskbar overlay. Usually UNNECESSARY: pup auto-fetches the page\'s own <link rel="icon"> favicon (the adom-ui-design standard artifact every app must serve) and wears it automatically. An app-looking URL with NO favicon gets a reprimand hint.', updateNote: 'optional str — a short "what I did" note, surfaced back in list_tabs/list_windows as lastAgentUpdate so the user knows what changed', autoFlash: 'optional bool (default true) — mutating verbs auto-flash this window\'s taskbar (debounced, never steals focus) so the user notices the agent touched it; pass false to make this window silent by default', silent: 'optional bool — suppress the auto-flash for just this call' }, 60, { sessionId: 'mytask-web', owner: 'mytask', url: 'https://example.com', width: 960, height: 540 }),             V('browser_open_tab', 'Open a new tab in an EXISTING session/window (returns {tabId, tabCount, activeTabId} — no need to diff list_tabs). This is the right verb for multi-URL work: ONE window with many tabs (one taskbar button), not many windows. The window stays in the background; the tab auto-flashes the taskbar (debounced) so the user notices. The USER can click the window\'s taskbar button at any time to view it — pup never pulls it back down.', { sessionId: 'required str', url: 'optional str', updateNote: 'optional str — short "what I did" note surfaced in list_tabs as lastAgentUpdate', silent: 'optional bool — suppress the auto-flash for this call' }, 30, { sessionId: 'work', url: 'https://example.com' }),             V('browser_close_tab', 'Close a tab.', { sessionId: 'required str', tabId: 'optional str (default active)' }, 15, { sessionId: 'work' }),             V('browser_close_window', 'Close a window/session (its tabs go too).', { sessionId: 'required str' }, 20, { sessionId: 'work' }),@@ -4093,6 +4121,8 @@ const server = http.createServer(async (req, res) => {           // sessionId so a bare "open in pup" (just sessionId + url) works cleanly.           // (To drive the user's REAL Chrome/Edge, that's the Adom browser EXTENSION's           // nbrowser_* verbs — a separate bridge, not pup.)+          // v1.8.93: wikiView:"authed" → reserved persistent authed-wiki profile (SSO cookie jar).+          if (args.wikiView === 'authed' && isWikiUrl(args.url) && !args.profile) args.profile = WIKI_AUTH_PROFILE;           if (!args.profile) args.profile = args.sessionId;           const sessionId = args.sessionId;           // v1.6.3+: optional downloadPath override. Defaults to the user's@@ -4168,6 +4198,12 @@ const server = http.createServer(async (req, res) => {             try { _appIconWorn = await Promise.race([applyAppOverlay(session, sessionId), new Promise(r => setTimeout(() => r(false), 4000))]); } catch (e) {}           }           const appIconHint = appIconReprimand(args.url, _appIconWorn);+          // v1.8.93: wiki dual-view hint + auth-state check.+          let _wikiAuth = null;+          if (session && isWikiUrl(args.url)) {+            if (args.wikiView === 'authed') { try { _wikiAuth = await Promise.race([checkWikiAuth(session.page), new Promise(r => setTimeout(() => r({ authed: false }), 3500))]); } catch (e) {} }+          }+          const wikiHint = wikiViewHint(args.url, args.wikiView === 'authed' ? 'authed' : 'public', _wikiAuth);           const info = await getSessionInfo(sessionId, session);           // v1.8.0: BACKGROUND BY DEFAULT. pup is the AI's workspace, not the user's —           // a window that pops to the foreground and covers what the user is doing is@@ -4269,8 +4305,10 @@ const server = http.createServer(async (req, res) => {               reused: reuse ? { navigatedAway: reuse.navigatedAway, previousUrl: reuse.previousUrl, previousOwner: reuse.previousOwner, takeover: reuse.takeover } : null,               owner: session.owner || null,               appIconWorn: _appIconWorn || undefined,+              wikiView: isWikiUrl(args.url) ? (args.wikiView === 'authed' ? 'authed' : 'public') : undefined,+              wikiLoginNeeded: (args.wikiView === 'authed' && _wikiAuth && !_wikiAuth.authed) || undefined,               foregroundDenied: fgDenied ? 'foreground_reason_required' : undefined,-              _hint: `${authSteerHint(args.url)}${fgDenyHint}${appIconHint}${bgHint}${ownHint} ${brep._hint}${staleWindowsHint()}${adUpgradeHint()}`,+              _hint: `${wikiHint}${authSteerHint(args.url)}${fgDenyHint}${appIconHint}${bgHint}${ownHint} ${brep._hint}${staleWindowsHint()}${adUpgradeHint()}`,               ...info,             }),           });