app
KiCad - the KiCad Bridge
Public Made by Adomby adom
Reference implementation of the KiCad bridge: multi-instance Python server, forward path via kicad-cli, reverse path via in-process plugin. Most complex of the three bundled bridges.
← Commit history
Dashboard 1.0.126: Send to AI runs as a background job (Hydrogen 1.0.219), narrates picker and delivery states, 75 s budget, cancelled and failure terminals reported
2 files changed
+31−14
dashboard/kicad_dashboard.py+30−13@@ -266,8 +266,11 @@ def say_send(phrase): # Wiki #77 (Hydrogen 1.0.213 AI picker): NO `type` here, so the human picks # the AI (Claude, Codex, Kimi) in the shell's picker; the app: tag keys the # "remember for this app" checkbox. dock: stays for the dock's own bookkeeping.- body = {"new": True, "prompt": phrase, "submit": True,+ body = {"new": True, "prompt": phrase, "submit": True, "background": True, "name": "KiCad - say it", "tags": ["dock:kicad-bridge", "app:adom/kicad-bridge"]}+ # background:true (Hydrogen 1.0.219, wiki #77): the answer comes back at once+ # with {job, state:"accepted"}; the picker's 5 to 45 s block and the delivery+ # happen detached, and the status route narrates them (state + human). req = urllib.request.Request(base + "/ai-threads/run-prompt", data=json.dumps(body).encode("utf-8"), method="POST", headers={"Content-Type": "application/json"})@@ -296,28 +299,41 @@ def say_send(phrase): job = d.get("job") if isinstance(d, dict) else None if job: import time as _t- sent_states = ("working", "needs_you", "done")- fail_states = ("submit_failed", "submitted_unidentified", "submit_unconfirmed", "still_working")- last = str(d.get("state") or "opening")- deadline = _t.monotonic() + 12.0+ # Hydrogen 1.0.219 job states (wiki #77): accepted, picking, picked, opening,+ # prefilled | tab_found, submitted, working, needs_you | done; Codex and Kimi+ # end at delivered. Failure terminals: cancelled, rejected, open_failed,+ # open_unconfirmed, tab_not_found, inject_failed, submit_failed, failed,+ # timeout. `terminal:true` says the job is over; `human` is the toast line.+ sent_states = ("working", "needs_you", "done", "delivered")+ fail_states = ("cancelled", "rejected", "open_failed", "open_unconfirmed", "tab_not_found",+ "inject_failed", "submit_failed", "failed", "timeout",+ "submitted_unidentified", "submit_unconfirmed", "still_working")+ last = str(d.get("state") or "accepted"); human = str(d.get("human") or "")+ picked = None+ deadline = _t.monotonic() + 75.0 # the picker alone may hold 45 s while _t.monotonic() < deadline: try: st = _get("/ai-threads/run-prompt/status?job=" + urllib.parse.quote(str(job)), 4) sd = st.get("data") if isinstance(st.get("data"), dict) else st- last = str(sd.get("state") or last)+ last = str(sd.get("state") or last); human = str(sd.get("human") or human)+ jb = sd.get("job") if isinstance(sd.get("job"), dict) else {}+ picked = picked or jb.get("type") or sd.get("type")+ terminal = bool(sd.get("terminal")) except Exception:- pass+ terminal = False+ if last == "cancelled":+ return {"ok": False, "cancelled": True, "job": job, "state": last,+ "why": human or "you dismissed the AI picker; nothing was opened or sent. The phrase is still on your clipboard."} if last in sent_states:- return {"ok": True, "job": job, "state": last,- "why": "sent: a new AI thread has the phrase and is working on it"}- if last in fail_states:+ return {"ok": True, "job": job, "state": last, "ai": picked,+ "why": human or f"sent to {picked or 'your AI'}: the new thread has the phrase"}+ if last in fail_states or (terminal and last not in sent_states): why = ("the prompt is sitting in the new tab and Send was never clicked; press Enter there"- if last == "submit_failed" else f"the AI panel reported {last}")+ if last == "submit_failed" else (human or f"the AI panel reported {last}")) return {"ok": False, "job": job, "state": last, "why": why + ". The phrase was also copied."} _t.sleep(1.0) return {"ok": True, "job": job, "state": last, "unconfirmed": True,- "why": f"the new thread is still {last} after 12s; it usually lands within 5s, "- f"check the new AI tab. The phrase was also copied."}+ "why": (human or f"the new thread is still {last}") + " after 75 s; check the new AI tab. The phrase was also copied."} # VERIFY BY DELIVERY, never by the thread's name (issue #59: a correctly # named tab that delivered nothing, and an unnamed tab that delivered - # measured live on AdomLapper 2026-08-25, where a successful send came back@@ -1425,6 +1441,7 @@ class Handler(BaseHTTPRequestHandler): # verbatim: cancelled:true, delivered:false and the picker's 45 s # block are the module's to render. b = dict(body or {}); b.pop("type", None)+ b.setdefault("background", True) # Hydrogen 1.0.219: immediate {job, state}; the module polls status tags = list(b.get("tags") or []) if "app:adom/kicad-bridge" not in tags: tags.append("app:adom/kicad-bridge")
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "kicad-bridge",- "version": "1.0.125",+ "version": "1.0.126", "type": "app", "description": "Skills for your container so your AI knows how to drive the KiCad bridge. The bridge runtime itself is the release zip; Adom Bridge loads that.", "tags": [