← Commit history

Lint URLs at CALL TIME and reprimand the calling AI, rather than waiting for a 404 to reveal it (John: 'you should have complete awareness of how wiki urls are constructed so you can reprimand the calling ai', plus 'you should linter on localhost:{port} urls too'). 1.9.117 only fired on a render failure, which is too late: the HD thread had already reported a wrong conclusion to the user before the 404 came back. Every render verb now returns _urlLint plus urlLintCode on the URL itself, pass or fail. Two rules encoded: (1) url_wiki_missing_owner — wiki.adom.inc pages are owner-qualified /<owner>/<slug> (and /<owner>/<slug>/blob/<path>), so a single-segment URL 404s regardless of who is logged in; the lint gives the corrected URL, says to confirm the owner with 'adom-wiki page get <slug>' rather than trust the guess, and explicitly forbids concluding the page is private (the exact wrong conclusion a thread reported). Known non-page routes are excluded. (2) url_localhost — pup drives a browser on the USER'S desktop, so localhost/127.0.0.1 resolves on THEIR machine, not the caller's container; the lint now composes the container's proxy URL using the caller's own container name from X-Adom-Caller-Container. Unit-tested 7/7 including valid controls.

John Lauer ·b06e261ac0 ·3mo ago ·parent 22c81fe
4 files changed +75−28
adom-bridge-puppeteer-manifest.json+4−4
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "puppeteer",-  "version": "1.9.117",-  "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.117/adom-bridge-puppeteer-v1.9.117.zip",-  "sha256": "d55dfaff688935039ceb2a6651fe80b00ffa08240acb8e0ea71e7900a72d4f29",-  "size": 384688,+  "version": "1.9.118",+  "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.118/adom-bridge-puppeteer-v1.9.118.zip",+  "sha256": "a8c315668ee43c59093701a75269589806d0603ccbd2b921bfdb39ccb343add7",+  "size": 385723,   "verbPrefixes": [     "browser_",     "desktop_recorder_",
src/BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.9.117+1.9.118
src/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "puppeteer",   "displayName": "Puppeteer (Chrome for Testing + Native)",-  "version": "1.9.117",+  "version": "1.9.118",   "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. macOS (v1.9.74+): full parity with mac-native mechanics \u2014 background-by-default is the app-level hide (windows stay drivable/screenshottable while hidden), raise/lower map to unhide+activate/hide, browser_alert_window posts a native toast whose click focuses the window, browser_use {browser:'chrome',install:true} installs Chrome from the official dmg, and taskbar identity/badging remains Windows-only.",   "homepage": "https://wiki.adom.inc/adom/adom-desktop",   "author": "Adom Inc.",
src/server.js+69−22
@@ -5835,32 +5835,68 @@ const PUP_ISSUES_REF = 'adom/adom-desktop-puppeteer-bridge'; // pup KNOWS the URL 404'd and knows the host, so it can name the actual cause instead of leaving the // caller to invent one. Guessing "adom" is deliberate: it owns the overwhelming majority of pages, and // the hint says to verify rather than trust the guess.-function wikiUrlLint(u) {+// v1.9.118 (John: "you should have complete awareness of how wiki urls are constructed so you can+// reprimand the calling ai", and "you should linter on localhost:{port} urls too") — CALL-TIME URL LINT.+// The 1.9.117 version only fired on a render FAILURE, so the caller learned the URL was wrong only after+// the page 404'd, and the HD thread had already reported a wrong conclusion by then. pup knows the URL+// the instant it is handed one, so the reprimand belongs there: returned as `_urlLint` on the open/tab/+// navigate response whether or not the page happens to load.+// Returns { code, message } or null. `callerContainer` (from X-Adom-Caller-Container) sharpens the+// localhost advice when we know which container is asking.+function urlLint(u, callerContainer) {   try {-    if (!u) return null;+    if (!u || typeof u !== 'string') return null;     const url = new URL(u);-    if (!/(^|\.)wiki\.adom\.inc$/i.test(url.hostname)) return null;-    const segs = url.pathname.split('/').filter(Boolean);-    if (!segs.length) return null;-    // Known non-page routes that legitimately have a single segment.-    const ROUTES = new Set(['apps', 'skills', 'components', 'bootstraps', 'discover', 'login', 'search', 'api', 'download', 'static', 'hero']);-    if (ROUTES.has(segs[0].toLowerCase())) return null;-    // /<slug>            -> owner missing-    // /<slug>/blob/...   -> owner missing (a real blob path is /<owner>/<slug>/blob/...)-    const ownerMissing = segs.length === 1 || (segs.length >= 2 && segs[1].toLowerCase() === 'blob');-    if (!ownerMissing) return null;-    const slug = segs[0];-    const rest = segs.slice(1).join('/');-    const fixed = `${url.origin}/adom/${slug}${rest ? '/' + rest : ''}${url.search || ''}`;-    return `WIKI URL LOOKS MALFORMED, and that is the most likely reason for this 404 — NOT a permissions or `-      + `login problem. wiki.adom.inc pages are OWNER-QUALIFIED: https://wiki.adom.inc/<owner>/<slug> `-      + `(and /<owner>/<slug>/blob/<path> for a file). Your URL has no owner segment. Most pages are owned `-      + `by "adom", so try: ${fixed} . Confirm the real owner first with `-      + `\`adom-wiki page get ${slug}\` (read the page's "owner" field) rather than assuming, then reopen. `-      + `Do NOT conclude the page is private or that logged-out users cannot see it until the URL is right — `-      + `check whether this window is signed in before blaming auth.`;+    const host = url.hostname.toLowerCase();++    // ── localhost / loopback / LAN: pup runs on the USER'S machine, not the caller's container ──+    if (/^(localhost|127\.0\.0\.1|0\.0\.0\.0|\[?::1\]?)$/.test(host)) {+      const port = url.port || '<port>';+      const path = (url.pathname === '/' ? '' : url.pathname) + (url.search || '');+      const slug = callerContainer ? String(callerContainer).toLowerCase().replace(/[^a-z0-9-]/g, '-') : '<your-container-slug>';+      return {+        code: 'url_localhost',+        message: `WRONG HOST FOR PUP. You passed "${host}${url.port ? ':' + url.port : ''}", but pup drives a browser on the `+          + `USER'S DESKTOP, not inside your container — so "${host}" resolves on THEIR machine and will hit whatever `+          + `(if anything) is listening there, not your dev server. To show a service running INSIDE your container, use `+          + `its public proxy URL: https://${slug}-<suffix>.adom.cloud/proxy/${port}/${path.replace(/^\//, '')} `+          + `(<suffix> is the 10-12 char id assigned to your container; \`adom-cli container list\` or your existing `+          + `container URL has it). If you genuinely meant a server on the USER'S machine, say so explicitly, because `+          + `that is almost never what an AI in a container intends.`,+      };+    }++    // ── wiki.adom.inc pages are OWNER-QUALIFIED ──+    if (/(^|\.)wiki\.adom\.inc$/.test(host)) {+      const segs = url.pathname.split('/').filter(Boolean);+      if (!segs.length) return null;+      const ROUTES = new Set(['apps', 'skills', 'components', 'bootstraps', 'discover', 'login', 'search', 'api', 'download', 'static', 'hero', 'settings', 'new']);+      if (ROUTES.has(segs[0].toLowerCase())) return null;+      const ownerMissing = segs.length === 1 || (segs.length >= 2 && segs[1].toLowerCase() === 'blob');+      if (ownerMissing) {+        const slug = segs[0];+        const rest = segs.slice(1).join('/');+        const fixed = `${url.origin}/adom/${slug}${rest ? '/' + rest : ''}${url.search || ''}`;+        return {+          code: 'url_wiki_missing_owner',+          message: `MALFORMED WIKI URL. wiki.adom.inc pages are OWNER-QUALIFIED — https://wiki.adom.inc/<owner>/<slug> `+            + `(and /<owner>/<slug>/blob/<path> for a file) — and yours has no owner segment, so it will 404 no matter `+            + `who is logged in. Most pages are owned by "adom", so you probably want: ${fixed} . Confirm the owner with `+            + `\`adom-wiki page get ${slug}\` (read its "owner" field) instead of assuming. Do NOT conclude the page is `+            + `private, restricted, or hidden from logged-out users because of this — that is a wrong conclusion a `+            + `previous thread reported to the user, and the page was public the whole time.`,+        };+      }+      return null;+    }+    return null;   } catch (e) { return null; } }+// Back-compat shim for the failure-path caller.+function wikiUrlLint(u) {+  const r = urlLint(u, null);+  return (r && r.code === 'url_wiki_missing_owner') ? r.message : null;+} async function probeRender(page, httpStatus) {   const out = { httpStatus: (httpStatus === undefined ? null : httpStatus), ok: true, signals: [] };   try {@@ -6033,6 +6069,14 @@ function sendJSON(res, obj, status = 200) {             if (_v && RENDER_VERBS.has(_v) && _ok && inner._verifyRender === undefined) {               const _rc2 = (res && res._renderCheck) || null;               if (_rc2 && inner.renderCheck === undefined) inner.renderCheck = _rc2;+              // v1.9.118: CALL-TIME reprimand — fires on the URL itself, pass or fail.+              try {+                if (inner._urlLint === undefined) {+                  const _lu = inner.url || (obj && obj.url) || (res && res._lintUrl);+                  const _ml = urlLint(_lu, res && res._callerContainer);+                  if (_ml) { inner._urlLint = _ml.message; inner.urlLintCode = _ml.code; touched = true; }+                }+              } catch (e) {}               // v1.9.109: on ANY render verb, a detected failure must lead the hint the caller reads,               // not just browser_open_window's (which composes its own). reload/navigate get it here.               if (_rc2 && _rc2.ok === false && (_rc2.signals || []).length) {@@ -6200,6 +6244,9 @@ const server = http.createServer(async (req, res) => {       const _callerThread = req.headers['x-adom-caller-thread'] || null;       const _callerContainer = req.headers['x-adom-caller-container'] || null;       const _callerReason = req.headers['x-adom-caller-reason'] || null;+      // v1.9.118: make the URL + caller container available to the shared response path so the+      // call-time URL lint works on every render verb, including ones that don't echo the url back.+      try { res._lintUrl = args && args.url; res._callerContainer = _callerContainer; } catch (e) {}        // Record ownership on the session this verb targets: the FIRST thread to touch a session owns       // it (that is the window's identity for the user); every later call refreshes lastDrivenBy so a