From bcc24002f8d43dc63642a3131f6656ef0e0a6a9e Mon Sep 17 00:00:00 2001 From: Sam & Claude Date: Sat, 27 Jun 2026 21:43:12 +0200 Subject: [PATCH 1/2] fix(mother): replace pg_read_file with psql variable interpolation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pg_read_file('/dev/stdin') requires superuser — colibri role had only INSERT. Use psql -v json_input="$(cat)" + :'json_input' for parameterized JSON ingestion. Works with non-superuser PostgreSQL roles. --- packaging/mother/colibri-mcp-ssh | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packaging/mother/colibri-mcp-ssh b/packaging/mother/colibri-mcp-ssh index 029e429..ad3230d 100755 --- a/packaging/mother/colibri-mcp-ssh +++ b/packaging/mother/colibri-mcp-ssh @@ -27,14 +27,10 @@ case "${SSH_ORIGINAL_COMMAND:-}" in ;; "report-task-cost") # Read TaskCostSummary JSON from stdin, INSERT into mother_hive.task_costs. - # Input: {"node_hostname":"debby","task_id":"abc","provider":"deepseek", - # "model":"deepseek-chat","input_tokens":150,"output_tokens":80, - # "cache_read_tokens":200,"cache_write_tokens":50, - # "cost_usd":0.0042,"success":true, - # "proof_text":"{\"agent\":\"zot\",\"state\":\"Done\",\"tokens_in\":150}", - # "screenshot_uuid":"a1b2c3d4e5f6", - # "finished_at":"2026-06-27T12:00:00Z"} - psql -d mother_hive -tA -v ON_ERROR_STOP=1 <<'PSQL' + # Uses psql variable interpolation (no pg_read_file — works with non-superuser). + # Input: {"node_hostname":"debby","task_id":"abc",...} + _json=$(cat) + psql -d mother_hive -tA -v ON_ERROR_STOP=1 -v json_input="$_json" <<'PSQL' INSERT INTO task_costs (node_id, task_id, provider, model, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, cost_usd, success, proof_text, screenshot_uuid, finished_at) @@ -52,7 +48,7 @@ SELECT NULLIF(j->>'proof_text', ''), NULLIF(j->>'screenshot_uuid', ''), COALESCE((j->>'finished_at')::TIMESTAMPTZ, now()) -FROM (SELECT (pg_read_file('/dev/stdin')::JSONB) AS j) AS _; +FROM (SELECT (:'json_input'::JSONB) AS j) AS _; PSQL ;; *) -- 2.45.3 From 0323a9817e3a709d8c6eb32dd2881d81c94f5530 Mon Sep 17 00:00:00 2001 From: Sam & Claude Date: Sat, 27 Jun 2026 22:08:31 +0200 Subject: [PATCH 2/2] =?UTF-8?q?fix(dashboard):=20remove=20dead=20screensho?= =?UTF-8?q?t=5Fuuid=20badge/code=20=E2=80=94=20daemon=20never=20populates?= =?UTF-8?q?=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The screenshot.rs module was never committed to main (PR #239 commit message was aspirational). The daemon pushes only proof_text (glasspane JSON). Removed: - hasScreenshot / ▸ screenshot badge branch - lightbox path (only proof_text
 remains)
- Orphaned else-if block from dual-badge logic
Kept:
- screenshot_uuid column in schema (future-proof, harmless)
- Comment explaining why only proof_text is checked
- JSON panel includes screenshot_uuid (raw field, matches schema)
---
 packaging/mother/dashboard/index.html | 32 ++++++++++-----------------
 1 file changed, 12 insertions(+), 20 deletions(-)

diff --git a/packaging/mother/dashboard/index.html b/packaging/mother/dashboard/index.html
index dc109de..8584fa9 100644
--- a/packaging/mother/dashboard/index.html
+++ b/packaging/mother/dashboard/index.html
@@ -190,7 +190,6 @@ h1 .dot{display:inline-block; width:8px; height:8px; border-radius:50%; margin-r
 
 
@@ -306,9 +305,10 @@ function renderCard(t) {
   const total = (t.input_tokens||0) + (t.cache_read_tokens||0);
   const cachePct = total > 0 ? Math.round((t.cache_read_tokens||0) / total * 100) : 0;
   const freshPct = 100 - cachePct;
-  const hasProofText = !!t.proof_text;
-  const hasScreenshot = !!t.screenshot_uuid;
-  const hasProof = hasProofText || hasScreenshot;
+  // Only proof_text exists — daemon captures glasspane state at task exit.
+  // screenshot_uuid is a schema column for future visual capture, never
+  // populated by the current daemon (no screenshot.rs module on main).
+  const hasProof = !!t.proof_text;
   const cls = hasProof ? 'card has-proof' : 'card';
   const onClick = hasProof
     ? `onclick="openProof(this,'${esc(t.task_id||'')}')"`
@@ -327,7 +327,7 @@ function renderCard(t) {
     
${fmtTokens(t.input_tokens||0)} in · ${fmtTokens(t.output_tokens||0)} out ${cachePct>0?` · ${cachePct}% cache` : ''}
- ${hasScreenshot ? '
▸ screenshot
' : hasProofText ? '
▸ text
' : ''} + ${hasProof ? '
▸ proof
' : ''} `; } @@ -336,24 +336,16 @@ function openProof(_el, taskId) { const t = (DATA.tasks || []).find(t => t.task_id === taskId); const lb = document.getElementById('lightbox'); const meta = document.getElementById('lightbox-meta'); - const img = document.getElementById('lb-img'); const pre = document.getElementById('lb-text'); meta.innerHTML = `${esc(taskId)} ${esc(t?.provider||'')} · $${(t?.cost_usd||0).toFixed(4)}`; - // Hide both, then show the appropriate one. - img.style.display = 'none'; - pre.style.display = 'none'; - - if (t?.screenshot_uuid) { - img.src = `../screenshots/${t.screenshot_uuid}.png`; - img.style.display = 'block'; - } else if (t?.proof_text) { - let display; - try { display = JSON.stringify(JSON.parse(t.proof_text), null, 2); } - catch(_) { display = t.proof_text; } - pre.textContent = display; - pre.style.display = 'block'; - } + // Proof is always inline text (glasspane state JSON). + const raw = t?.proof_text || '{}'; + let display; + try { display = JSON.stringify(JSON.parse(raw), null, 2); } + catch(_) { display = raw; } + pre.textContent = display; + pre.style.display = 'block'; lb.classList.add('open'); } function closeLb() { document.getElementById('lightbox').classList.remove('open'); } -- 2.45.3