← Commit history

Publish 2.0.307

John Lauer ·64193d6e21 ·2mo ago ·parent b6c0bb0
3 files changed +33−15
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "pup-bridge",-  "version": "2.0.306",+  "version": "2.0.307",   "type": "app",   "description": "The pup SKILL PACK \u2014 the AI skills that teach a cloud/container AI how to drive the Adom Desktop Puppeteer (pup) bridge (browser_* verbs): the everyday `pup` skill plus focused sub-skills for windows/sessions/tabs, screenshots+recording, browsers+Chrome install, and driving the Adom wiki. Installs into ~/.claude/skills + ~/.codex/skills. DOCS ONLY \u2014 this is NOT the bridge runtime. The bridge itself (the Node program that drives the browser) runs on the DESKTOP: Adom Desktop bundles it and auto-installs/updates it, and it's published separately on the Releases tab as adom-bridge-puppeteer-v<ver>.zip. The pkg version is kept in lockstep with the Release version so the page's max version row always carries BOTH a pkg tarball (skills) and a runtime zip (bridge) \u2014 a skills-only or runtime-only bump on the top row breaks constraint installs. TL;DR: pkg install = the skills for your AI; the bridge = the Release zip, which Adom Desktop manages for you.",   "keywords": [
page.json+1−1
@@ -4,7 +4,7 @@   "slug": "pup-bridge",   "title": "Pup - Puppeteer Bridge",   "brief": "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.",-  "version": "2.0.306",+  "version": "2.0.307",   "tags": [     "skills",     "pup",
tools/pup-dashboard+31−13
@@ -55,22 +55,40 @@ function hpost(urlPath, body) {     req.end(data);   }); }-function findSid(j) {-  // the history/census payloads are lists (or {threads:[...]}) of thread records carrying sid/id-  const list = Array.isArray(j) ? j : (j && (j.threads || j.items || j.history || j.results)) || [];-  for (const t of list) { if (t && (t.sid || t.id || t.session_id)) return String(t.sid || t.id || t.session_id); }+// Thread lookup against the live Hydrogen control API (shapes measured by the dock thread,+// 2026-08-23, now in the Dock Bar guide):+//   CENSUS (open threads) GET /ai-threads -> {ok, groups:[{threads:[{tid, type, label, sid|null, tags:[...]}]}]}+//   HISTORY (closed)      GET /ai-threads/history?tag=... -> {ok, items:[{ai, id, title, updated_ms}], open_excluded}+// Census FIRST: history EXCLUDES open threads by design, so history-first comes back empty exactly+// when the thread is open. A closed thread is resumed (POST /ai-threads/resume {ai,id}) before staging.+const APP_TAG = 'app:adom/pup-bridge';+function censusThread(j) {+  for (const g of (j && j.groups) || []) for (const t of (g && g.threads) || []) {+    if (t && Array.isArray(t.tags) && t.tags.includes(APP_TAG)) return t;+  }   return null; } async function stagePhrase(phrase) {   if (!SAY.includes(phrase)) return { ok: false, error: 'unknown phrase' };-  const h = await hget('/ai-threads/history?tag=' + encodeURIComponent('app:adom/pup-bridge'));-  if (!h.status) return { ok: false, error: 'Hydrogen is not reachable on 127.0.0.1:47084 (' + (h.error || 'no answer') + '). Paste the phrase into any Adom AI thread instead.' };-  let sid = findSid(h.json);-  if (!sid) { const c = await hget('/ai-threads/census'); sid = findSid(c.json); }-  if (!sid) return { ok: false, error: 'No pup AI thread exists yet. Click "Run the demo" once (it creates one), or paste the phrase into any Adom AI thread.' };-  const r = await hpost('/ai-threads/run-prompt', { sid, prompt: phrase, submit: false });-  if (r.status >= 200 && r.status < 300) return { ok: true, sid, staged: phrase };-  return { ok: false, error: 'Hydrogen refused the stage (' + (r.status || r.error) + ')' + (r.raw ? ': ' + r.raw : '') };+  const c = await hget('/ai-threads');+  if (!c.status) return { ok: false, error: 'Hydrogen is not reachable on 127.0.0.1:47084 (' + (c.error || 'no answer') + '). Paste the phrase into any Adom AI thread instead.' };+  const open = censusThread(c.json);+  if (open) {+    const target = open.sid ? { sid: open.sid } : { tid: open.tid };+    const r = await hpost('/ai-threads/run-prompt', { ...target, prompt: phrase, submit: false });+    if (r.status >= 200 && r.status < 300 && !(r.json && r.json.ok === false)) return { ok: true, ...target, staged: phrase, where: 'open thread' + (open.label ? ' "' + open.label + '"' : '') };+    return { ok: false, error: 'Hydrogen refused the stage (' + (r.status || r.error) + ')' + (r.raw ? ': ' + r.raw : '') };+  }+  const h = await hget('/ai-threads/history?tag=' + encodeURIComponent(APP_TAG));+  const item = h.json && Array.isArray(h.json.items) ? h.json.items.find(i => i && i.id) : null;+  if (item) {+    const rs = await hpost('/ai-threads/resume', { ai: item.ai || 'claude', id: item.id });+    if (!(rs.status >= 200 && rs.status < 300)) return { ok: false, error: 'Found your pup thread "' + (item.title || item.id) + '" but Hydrogen could not reopen it (' + (rs.status || rs.error) + '). Paste the phrase into any Adom AI thread instead.' };+    const r = await hpost('/ai-threads/run-prompt', { sid: item.id, prompt: phrase, submit: false });+    if (r.status >= 200 && r.status < 300 && !(r.json && r.json.ok === false)) return { ok: true, sid: item.id, staged: phrase, where: 'reopened thread' + (item.title ? ' "' + item.title + '"' : '') };+    return { ok: false, error: 'Reopened your pup thread but the stage was refused (' + (r.status || r.error) + ')' + (r.raw ? ': ' + r.raw : '') };+  }+  return { ok: false, error: 'No pup AI thread exists yet. Click "Run the demo" once (it creates one), or paste the phrase into any Adom AI thread.' }; } let TOOL_VERSION = '0'; try { TOOL_VERSION = String(require(path.join(__dirname, '..', 'package.json')).version || '0'); } catch (e) {}@@ -299,7 +317,7 @@ async function load(){     if(ph){const chip=ph.closest('.chip'); const p=say[+chip.dataset.i]; try{await navigator.clipboard.writeText(p);}catch(e){} chip.classList.add('copied'); ph.textContent='copied'; setTimeout(function(){chip.classList.remove('copied'); ph.textContent='“'+p+'”';},1200); return;}     if(tr){tr.disabled=true; tr.textContent='…'; note.hidden=false; note.textContent='staging…';       try{const r=await fetch('api/try',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({phrase:say[+tr.dataset.i]})}); const d=await r.json();-        note.textContent=d.ok?('Staged in your pup thread: “'+d.staged+'”. Press Enter there to send it.'):(d.error||'could not stage');}+        note.textContent=d.ok?('Staged in your '+(d.where||'pup thread')+': “'+d.staged+'”. Press Enter there to send it.'):(d.error||'could not stage');}       catch(e){note.textContent='could not reach the dashboard: '+e;}       tr.disabled=false; tr.textContent='Try it';}   });