← Commit history

v1.3.3: Send to AI: 120s budget, delivery proven by delivered_to, honest recovery

John Lauer ·ea1584f0d0 ·1mo ago ·parent 6b38201
7 files changed +58−102
Cargo.lock+1−1
@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"  [[package]] name = "adom-parts-search"-version = "1.3.2"+version = "1.3.3" dependencies = [  "base64",  "clap",
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-parts-search"-version = "1.3.2"+version = "1.3.3" edition = "2021" description = "Parts Search Electronics component search — CLI, backend HTTP service, and Hydrogen webview app" license = "MIT"
VERSION+1−1
@@ -1 +1 @@-1.3.2+1.3.3
package.json+1−40
@@ -38,44 +38,5 @@     "SKILL.md",     "BUILD-SKILL.md",     "CHANGELOG.md"-  ],-  "changelog": "Send to AI: 120s budget, delivery proven by delivered_to, honest recovery",-  "discovery_triggers": [-    "parts search",-    "find part",-    "source component",-    "source part",-    "compare distributors",-    "compare vendors",-    "Mouser or DigiKey",-    "BOM sourcing",-    "price this part",-    "where can I get",-    "check stock",-    "parts availability",-    "fastest delivery",-    "cheapest part",-    "component search",-    "part lookup",-    "price breaks"-  ],-  "discovery_pitch": "Search Mouser, DigiKey and JLCPCB in parallel with product photos; Mouser-preferred best-source pick. Say 'with parts search' to your AI.",-  "sample_prompts": [-    {-      "label": "source an STM32F103RBT6",-      "prompt": "source an STM32F103RBT6"-    },-    {-      "label": "where can I get an ESP32-S3-WROOM cheape",-      "prompt": "where can I get an ESP32-S3-WROOM cheapest and fastest"-    },-    {-      "label": "compare Mouser, DigiKey and JLCPCB for a",-      "prompt": "compare Mouser, DigiKey and JLCPCB for an LM555"-    },-    {-      "label": "price this BOM across distributors",-      "prompt": "price this BOM across distributors"-    }   ]-}\ No newline at end of file+}
page.json+7−48
@@ -36,22 +36,10 @@     "path": "docs/hero.png"   },   "sample_prompts": [-    {-      "label": "source an STM32F103RBT6",-      "prompt": "source an STM32F103RBT6"-    },-    {-      "label": "where can I get an ESP32-S3-WROOM cheape",-      "prompt": "where can I get an ESP32-S3-WROOM cheapest and fastest"-    },-    {-      "label": "compare Mouser, DigiKey and JLCPCB for a",-      "prompt": "compare Mouser, DigiKey and JLCPCB for an LM555"-    },-    {-      "label": "price this BOM across distributors",-      "prompt": "price this BOM across distributors"-    }+    "source an STM32F103RBT6",+    "where can I get an ESP32-S3-WROOM cheapest and fastest",+    "compare Mouser, DigiKey and JLCPCB for an LM555",+    "price this BOM across distributors"   ],   "discovery_triggers": [     "parts search",@@ -66,41 +54,12 @@     "where can I get",     "check stock",     "parts availability",-    "fastest delivery",-    "cheapest part",-    "component search",-    "part lookup",-    "price breaks"+    "fastest delivery"   ],-  "discovery_pitch": "Search Mouser, DigiKey and JLCPCB in parallel with product photos; Mouser-preferred best-source pick. Say 'with parts search' to your AI.",+  "discovery_pitch": "Search Mouser + DigiKey + JLCPCB in parallel from one interface, with product photos and a Mouser-preferred best-source pick.",   "metadata": {     "platform": "linux"   },   "category": "tools",-  "org": "adom",-  "description": "One search box across Mouser, DigiKey and JLCPCB. Parallel queries, side-by-side product photos, and a Mouser-preferred recommendation that reasons around stock and lead time. Dock Bar ready: serve, health, settings, Say it.",-  "dependencies": {-    "adom/adom-mouser": "*",-    "adom/adom-digikey": "*",-    "adom/adom-jlcpcb": "*"-  },-  "needs_sudo": false,-  "scripts": {-    "install": "./install.sh",-    "uninstall": "./uninstall.sh"-  },-  "files": [-    "bin/**",-    "skills/**",-    "docs/icon.svg",-    "dockbar.json",-    "install.sh",-    "uninstall.sh",-    "package.json",-    "README.md",-    "SKILL.md",-    "BUILD-SKILL.md",-    "CHANGELOG.md"-  ],-  "changelog": "Send to AI: 120s budget, delivery proven by delivered_to, honest recovery"+  "org": "adom" }\ No newline at end of file
publish.sh+13−3
@@ -143,11 +143,21 @@ CHECK_TGZ=$(mktemp) fetch_tarball() { curl -fsSL -H "Authorization: Bearer $TOK" "$REG/api/packages/adom/adom-parts-search/$VERSION/tarball" -o "$CHECK_TGZ"; } # The registry can serve a just-superseded listing for a while after a publish # (observed on 1.1.3, 1.2.0 and 1.3.2); poll up to ~60s before believing it.-for _try in 1 2 3 4 5 6; do-  fetch_tarball-  tar tzf "$CHECK_TGZ" | grep -q "dockbar\.json" && break+# Observed on 1.1.3, 1.2.0, 1.3.2 and 1.3.3: the registry can serve a stale+# listing for well over a minute after a SUCCESSFUL publish. Poll up to 3+# minutes, and if it still looks wrong, say exactly what we saw rather than+# asserting a failure the publish did not actually have.+VERIFY_OK=""+for _try in $(seq 1 18); do+  if fetch_tarball && tar tzf "$CHECK_TGZ" 2>/dev/null | grep -q "dockbar\.json"; then VERIFY_OK=1; break; fi   sleep 10 done+if [ -z "$VERIFY_OK" ]; then+  echo "--- registry still not serving a complete $VERSION tarball after 3 minutes."+  echo "--- last fetch: $(curl -s -o /dev/null -w 'http %{http_code}, %{size_download} bytes' -H "Authorization: Bearer $TOK" "$REG/api/packages/adom/adom-parts-search/$VERSION/tarball")"+  echo "--- members seen: $(tar tzf "$CHECK_TGZ" 2>/dev/null | tr '\n' ' ' | cut -c1-200)"+  fail "tarball verify never passed; the publish itself may still be fine, re-check with: curl -H \"Authorization: Bearer \$TOK\" $REG/api/packages/adom/adom-parts-search/$VERSION/tarball | tar tz"+fi if tar tzf "$CHECK_TGZ" | grep -q "page\.json"; then rm -f "$CHECK_TGZ"; fail "published tarball still contains page.json"; fi if ! tar tzf "$CHECK_TGZ" | grep -q "dockbar\.json"; then rm -f "$CHECK_TGZ"; fail "published tarball is missing dockbar.json"; fi rm -f "$CHECK_TGZ"
src/app/mod.rs+34−8
@@ -231,10 +231,10 @@ fn hget(path: &str) -> Result<Value, String> {     let agent = ureq::AgentBuilder::new().timeout(Duration::from_millis(8000)).build();     agent.get(&format!("{}{}", hydrogen_ctl(), path)).call().map_err(|e| e.to_string())?.into_json().map_err(|e| e.to_string()) }-/// Opening an AI thread drives the editor and legitimately takes 10-30s on a-/// busy workbench (every CDP touch serializes through one gate), so the-/// AI-threads calls get 45s. A short client timeout manufactures phantom-/// failures out of sends that actually worked.+/// Opening an AI thread drives the editor and the tab CREATE alone can take+/// 60s on a loaded workbench, so the AI-threads calls get 120s. A short+/// client timeout manufactures phantom failures out of sends that actually+/// worked, and worse, invites a blind retry that duplicates the thread. fn hpost_with(path: &str, body: Value, timeout_ms: u64) -> Result<(u16, Value), String> {     let agent = ureq::AgentBuilder::new().timeout(Duration::from_millis(timeout_ms)).build();     match agent.post(&format!("{}{}", hydrogen_ctl(), path)).send_json(body) {@@ -258,6 +258,20 @@ fn shell_version() -> Option<(u32, u32, u32)> { /// everything earlier, including the widely-installed 1.0.141, can crash the /// user's whole shell on a send. const MIN_SHELL: (u32, u32, u32) = (1, 0, 142);+/// The panel's `_hints` teach the recovery for a late/failed open. Surface+/// that text verbatim instead of inventing wording.+fn hints_text(v: &Value) -> Option<String> {+    let h = v.get("_hints")?;+    let text = match h {+        Value::String(s) => s.clone(),+        Value::Array(a) => a.iter().filter_map(|x| x.as_str()).collect::<Vec<_>>().join(" "),+        Value::Object(o) => o.values().filter_map(|x| x.as_str()).collect::<Vec<_>>().join(" "),+        _ => return None,+    };+    let text = text.trim();+    if text.is_empty() { None } else { Some(text.chars().take(300).collect()) }+}+ /// Send to AI. ONE call: /ai-threads/run-prompt with `new: true` opens the /// thread AND delivers the prompt. The old open-then-target flow could never /// work, because /ai-threads/new returns no id (a thread has no sid until its@@ -279,12 +293,24 @@ fn say_send(phrase: &str) -> Value {         "name": "Parts Search - say it",         "tags": [APP_TAG, "dock:adom-parts-search"],     });-    match hpost_with("/ai-threads/run-prompt", body, 45_000) {-        Ok((code, v)) if (200..300).contains(&code) && v.get("ok").and_then(|b| b.as_bool()) != Some(false) => {+    match hpost_with("/ai-threads/run-prompt", body, 120_000) {+        // Delivery proof is `delivered_to`, never the thread's name: the+        // composite deterministically ADOPTS an idle empty Claude tab when one+        // exists, and a staged name can lag or miss on an adopted tab.+        Ok((code, v)) if (200..300).contains(&code) && v.get("ok").and_then(|b| b.as_bool()) != Some(false) && v.get("delivered_to").map(|d| !d.is_null()).unwrap_or(false) => {             json!({"ok": true, "delivered_to": v.get("delivered_to").cloned().unwrap_or(Value::Null)})         }-        Ok((code, v)) => json!({"ok": false, "why": format!("The AI panel refused the send ({}): {}", code, v.get("error").and_then(|e| e.as_str()).unwrap_or("no reason given"))}),-        Err(_) => json!({"ok": false, "why": "The AI panel did not answer in 45 seconds. Copied the phrase instead."}),+        // Anything else is NOT proven delivered. Never retry blind, that+        // duplicates the thread: surface the panel's own _hints text, which is+        // written to say what actually happened and how to recover.+        Ok((code, v)) => {+            let hint = hints_text(&v);+            json!({"ok": false, "why": match hint {+                Some(h) => format!("The AI panel did not confirm delivery ({}). {}", code, h),+                None => format!("The AI panel did not confirm delivery ({}): {}. Copied the phrase instead.", code, v.get("error").and_then(|e| e.as_str()).unwrap_or("no reason given")),+            }})+        }+        Err(_) => json!({"ok": false, "why": "The AI panel did not answer within 2 minutes, so the send is unconfirmed. It may still land: check your AI threads before sending again, because sending twice opens two threads. Copied the phrase instead."}),     } }