← Commit history

v0.16.4 (insiders): Send to AI for Hydrogen 1.0.187+: newbie-panel.js re-vendored at 2.22.0 (job + state polling, pre-1.0.187 delivered_to path kept), the dashboard proxies GET /ai-threads/run-prompt/status with the query string intact, and all three control-API routes now pass status, content-type and body bytes through unchanged (503 with a hint when no Hydrogen answers, never 404).

John Lauer ·72e32628d9 ·2mo ago ·parent 44e4a6a
5 files changed +37−20
bin/nb-dashboard+33−16
@@ -20,7 +20,7 @@ const { execFile, spawn } = require('child_process'); const fs = require('fs'); const crypto = require('crypto'); -const VERSION = '0.16.3';           // keep in lockstep with package.json (pkg version)+const VERSION = '0.16.4';           // 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.@@ -72,6 +72,28 @@ function controlUrl() {   try { const u = fs.readFileSync(path.join(require('os').homedir(), '.adom', 'hydrogen-control-url'), 'utf8').trim(); if (u) return u.replace(/\/$/, ''); } catch {}   return 'http://127.0.0.1:47084'; }+// Raw pass-through to the control API: bytes in, bytes out. `u` is the path+query exactly as the page+// asked for it. Only the transport is ours; the payload is Hydrogen's.+function proxyRaw(method, u, body, res) {+  let up; try { up = new URL(controlUrl() + u); } catch { up = null; }+  const fail = (why) => {+    res.writeHead(503, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store', ...CORS_ALL });+    res.end(JSON.stringify({ ok: false, error: 'control API unreachable', why, _hint: 'This dashboard proxies ' + u.split('?')[0] + ' to the Hydrogen control API read from ~/.adom/hydrogen-control-url at request time. 503 means the route IS wired but no Hydrogen answered here (not running, or a different host); a 404 would mean the route is missing.' }));+  };+  if (!up) return fail('bad control URL');+  const data = method === 'POST' ? Buffer.from(body || '', 'utf8') : null;+  const req2 = http.request({ host: up.hostname, port: up.port, path: up.pathname + up.search, method, timeout: method === 'POST' ? 120000 : 10000,+    headers: data ? { 'Content-Type': 'application/json', 'Content-Length': data.length } : {} }, (r2) => {+    const chunks = []; r2.on('data', (c) => chunks.push(c)); r2.on('end', () => {+      const buf = Buffer.concat(chunks);+      res.writeHead(r2.statusCode || 502, { 'Content-Type': r2.headers['content-type'] || 'application/json', 'Content-Length': buf.length, 'Cache-Control': 'no-store', ...CORS_ALL });+      res.end(buf);+    });+  });+  req2.on('error', (e) => fail(e.message)); req2.on('timeout', () => { req2.destroy(); fail('timeout'); });+  if (data) req2.write(data); req2.end();+}+const CORS_ALL = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type' }; function httpJson(method, url, body, timeoutMs) {   return new Promise((resolve) => {     try {@@ -915,21 +937,16 @@ function startServer() {     }     // The module talks to the AI panel from the PAGE, so these two paths proxy through this     // backend: same-origin for the page, and the control URL (a DYNAMIC port) stays server-side.-    if (u.startsWith('/updater/status')) {-      httpJson('GET', controlUrl() + '/updater/status').then((r) => {-        res.writeHead(r.status || 502, { 'Content-Type': 'application/json', ...CORS });-        res.end(JSON.stringify(r.json || { error: r.error || 'control API unreachable' }));-      });-      return;-    }-    if (u.startsWith('/ai-threads/run-prompt') && req.method === 'POST') {-      let b = ''; req.on('data', (c) => (b += c)); req.on('end', () => {-        let body = {}; try { body = JSON.parse(b); } catch {}-        httpJson('POST', controlUrl() + '/ai-threads/run-prompt', body, 120000).then((r) => {-          res.writeHead(r.status || 502, { 'Content-Type': 'application/json', ...CORS });-          res.end(JSON.stringify(r.json || { error: r.error || 'control API unreachable' }));-        });-      });+    // CONTROL API PROXY (control-api-proxy skill): three routes, forwarded RAW. Status code, content-type+    // and body BYTES pass through untouched (a JSON round trip reorders keys, drops 1.0 to 1, loses+    // precision past 2^53, and turns a non-JSON body into an invented error while every probe stays+    // green). The query string travels intact: /ai-threads/run-prompt/status is called as ?job=<uuid>+    // and a proxy that forwards only the path answers about no job at all (Hydrogen 1.0.187+, newbie-panel+    // 2.22.0). When ah is absent the answer is 503 WITH A HINT, never 404: 404 means "not wired".+    if ((u.startsWith('/updater/status') && req.method === 'GET') ||+        (u.startsWith('/ai-threads/run-prompt/status') && req.method === 'GET') ||+        (u.startsWith('/ai-threads/run-prompt') && !u.startsWith('/ai-threads/run-prompt/') && req.method === 'POST')) {+      let b = ''; req.on('data', (c) => (b += c)); req.on('end', () => proxyRaw(req.method, u, b, res));       return;     }     if (u.startsWith('/api/say-send') && req.method === 'POST') {
bridge/BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.16.3\ No newline at end of file+0.16.4\ 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.3",+  "version": "0.16.4",   "expectedExtensionVersion": "0.2.10",   "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.3",+  "version": "0.16.4",   "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",
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "nb-bridge",-  "version": "0.16.3",+  "version": "0.16.4",   "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.",