bootstrap
Adom Hydrogen (platform base)
Public Made by Adomby adom
Layer Hydrogen's workspace onto a standard Adom workstation.
← Commit history
Delete skills/hydrogen-ai-threads-api
1 file changed
−288
skills/hydrogen-ai-threads-api/SKILL.mddeleted−288@@ -1,288 +0,0 @@-name: hydrogen-ai-threads-api-description: >- How to open and drive AI conversations in Hydrogen from your own app, panel, or script:- open a new thread (letting the user's preferred AI decide), list what is open, put text- in a chat box, send a prompt and confirm it landed, reopen a past conversation, and close- a tab. Plain HTTP on Hydrogen's control API, no Tauri IPC and no special privileges.- Read this before shelling out to a CLI or driving the editor UI yourself. Trigger words -- open an AI thread, new claude thread, new codex thread, new kimi thread, ai threads api,- send a prompt to an AI, put text in the chat box, list open AI conversations, reopen a- conversation, close an AI tab, default AI, which AI should I use, dock opens a thread.--# Driving Hydrogen's AI threads from your own app--Hydrogen keeps every open AI conversation (Claude, Codex, Kimi) in one live tree and exposes-it over its control API. **Anything that can reach that port can do everything the AI Threads-panel does** — the panel itself is a plain HTTP client with no privileged access, so there is-no capability you are missing.--## Find the API--The control port is dynamic (it drifts when 47084 is busy), so never hardcode it.--Inside the workspace:--```bash-BASE="$(cat ~/.adom/hydrogen-control-url)" # e.g. http://127.0.0.1:55038-curl -s "$BASE/ai-threads" | jq .-```--From a Hydrogen panel or webview, ask your own origin: `fetch('/ports')` returns the resolved-`control` port. If neither is available, the discovery responder answers on the first-bindable of `47080, 48080, 49180, 50280` and returns the live `control_url`.--## From Adom Bridge or the Windows host--The same discovery works OUTSIDE the workspace (an Adom Bridge caller, a dock card, a-wiki page acting through ab): read `%APPDATA%\hydrogen-desktop\ports.json` for the live-`control` port, or probe the discovery responder (`GET http://127.0.0.1:<c>/` for c in-47080, 48080, 49180, 50280; the first answer carries `control_url`). Then call the same-`/ai-threads/*` routes. Requires Adom Hydrogen 1.0.141 or newer. Note: while the AI-Threads panel is off (its default), the first driving call wakes the machinery for that-session; read-only census calls while parked return `ok:false` by design.--## Open a new thread--```bash-curl -sX POST "$BASE/ai-threads/new" -H 'content-type: application/json' -d '{}'-```--**Omit `type` and the user's own preference decides.** That is the point: a caller like the-dock knows which APP the user clicked, not which AI they like, and hardcoding one imposes a-choice that is not yours to make. The default is **claude**, the workhorse every Adom user is-required to have, so the fallback is always something the user actually has installed.--```jsonc-{"ok": true, "type": "claude", "from_default": true, "result": { "via": "keybinding", ... }}-```--- `type` is the AI that was actually used.-- `from_default: true` means it came from the setting rather than your request.-- `via: "keybinding"` means it opened without flashing the command palette at the user.--**THE RESPONSE CARRIES NO THREAD ID, EVER** (`sid: null, tid: null`, explicit since-1.0.143): a new thread has no identity until its first submitted prompt, and the tab-itself settles in a background task after this call returns. Do NOT open-then-target:-`new` followed by `run-prompt {sid}` always fails because there is no sid to pass.-To open a thread AND deliver a first prompt, use the ONE-CALL composite:--```bash-curl -sX POST "$BASE/ai-threads/run-prompt" -H 'content-type: application/json' \- -d '{"new": true, "prompt": "Search Mouser for STM32G474", "submit": true,- "name": "Parts - say it", "tags": ["dock:my-app"]}'-```--`name` and `tags` stage exactly as they do on `/new` (1.0.143+) and attach when the-sid appears; the response verifies delivery against the on-disk transcript-(`delivered_to`). To find a thread you opened earlier, poll `GET /ai-threads` for your-staged name/tags to attach, never guess by position.--**Client timeouts: 45 seconds for targeted calls, 120 for the `new:true` composite.**-Opening drives the editor (extension command, dual detection, tab placement) and its-internal create cap alone is 60s on a busy workbench; every CDP touch also serializes-through one gate, so calls queue behind each other rather than failing. A 5-10s client-timeout manufactures phantom failures. On a create-timeout error, follow the response's-`_hints`: poll GET /ai-threads for your staged name/tags (a late tab claims them), then-deliver by `tid`; a blind retry opens a duplicate.--**Adopt semantics (1.0.143+):** when an idle EMPTY Claude tab already exists, the-composite delivers into it (`adopted_empty_tab: true`) instead of asking the editor for-a new tab, which is deterministic where the editor's own "new conversation" may reuse an-arbitrary tab. Every /ai-threads response also now carries `_hints` telling the calling-AI what happened and how to recover; surface that text in your failure UI.--Name one explicitly only when your feature is genuinely AI-specific:--```bash-curl -sX POST "$BASE/ai-threads/new" -H 'content-type: application/json' \- -d '{"type":"codex","prompt":"review the diff in this repo"}'-```--`prompt` is optional and seeds the conversation's first message.--`tags` is optional and marks the thread as YOURS before it even has an id:--```bash-curl -sX POST "$BASE/ai-threads/new" -H 'content-type: application/json' \- -d '{"tags":["dock","app:kicad"],"prompt":"help me with this board"}'-```--A brand-new thread has no stable id until its first message creates the conversation, so-tags passed here STAGE and attach automatically the moment the id appears (the response-carries `tags_staged` confirming that). Staged tags expire after 10 minutes if no thread-materializes, so a failed open cannot mis-tag a stranger.--**Read or change the preference** (it is a normal setting, so let the user own it):--```bash-curl -s "$BASE/ai-threads/settings" | jq .default_ai # "claude"-curl -sX POST "$BASE/ai-threads/settings" -H 'content-type: application/json' \- -d '{"default_ai":"kimi"}'-```--**Open the AI's CLI in a terminal tab instead** (the caret menu next to each toolbar-button does exactly this):--```bash-curl -sX POST "$BASE/ai-threads/new-tui" -H 'content-type: application/json' \- -d '{"type":"claude"}'-```--A named terminal ("Claude TUI" / "Codex TUI" / "Kimi TUI") opens as an editor tab-running that AI's full CLI. TUI threads are FIRST-CLASS rows in the tree (marked-`tui: true`): they list alongside webview threads, and `/ai-threads/rename` and-`/ai-threads/close` work on them (close also answers VS Code's "terminate running-processes?" dialog). Delivery endpoints (`run-prompt`, `inject`) do not type into-TUIs; drive a TUI conversation in its terminal.--## See what is open--```bash-curl -s "$BASE/ai-threads" | jq '.groups[].threads[] | {tid, type, label, sid, instantiated}'-```--- `tid` is the CURRENT position (`g0t3`). It changes when tabs move, so do not store it.-- `sid` is the STABLE conversation id. Store this one.-- `instantiated: false` means the tab exists but VS Code has not materialized its webview- yet. Text cannot be typed into it until it has been focused once.-- `summary.identity` reports how each row was identified, so you can tell a solid row from a- guessed one.-- `tags` is the thread's durable tag list (see "Tag a thread" below). Filter on it to find- the threads that belong to your app: `jq '.groups[].threads[] | select(.tags | index("dock"))'`.-- `mention` (claude rows with a live session) is the thread's cross-session @mention- address, e.g. `"@project-d5"`: any other Claude session on the machine can message it by- typing that name in its composer.--`GET /ai-threads/wait` long-polls and returns when the tree changes. Use it instead of-polling on a timer.--## Put text in a chat box, or send it--```bash-# stage it for the human to review, nothing is sent-curl -sX POST "$BASE/ai-threads/inject" -H 'content-type: application/json' \- -d '{"tid":"g0t1","prompt":"summarize this file","submit":false}'--# send it, and confirm it actually landed in THAT conversation-curl -sX POST "$BASE/ai-threads/run-prompt" -H 'content-type: application/json' \- -d '{"sid":"<stable id>","prompt":"what model are you?"}'-```--Two guarantees worth relying on:--- **`submit: false` really does not send.** Prefer it when a human should see the text first.-- **`run-prompt` confirms delivery by reading the AI's own message list**, not by assuming- the keystrokes went somewhere. If it says it delivered, it checked.--Typical latency is single-digit to low-double-digit milliseconds against a loaded thread.-A thread whose webview has never been materialized costs a few seconds the first time while-VS Code creates it, so if you care about that, `POST /ai-threads/focus {tid}` first.--## Tag a thread, and find it again forever--Tags are durable strings keyed to the conversation's STABLE id, so they survive restart,-drag, rename, close, and reopen. This is how an external app (the dock) re-finds ITS-threads without guessing from titles.--```bash-# tag an existing thread (ai + sid, or its current tid, or an explicit "<ai>:<sid>" key)-curl -sX POST "$BASE/ai-threads/tag" -H 'content-type: application/json' \- -d '{"ai":"claude","sid":"<sid>","add":["dock","app:kicad"]}'--# remove one-curl -sX POST "$BASE/ai-threads/tag" -H 'content-type: application/json' \- -d '{"ai":"claude","sid":"<sid>","remove":["app:kicad"]}'--# the whole store-curl -s "$BASE/ai-threads/tags"-```--Tags come back on BOTH surfaces, open and closed:--```bash-curl -s "$BASE/ai-threads" | jq '.groups[].threads[] | {label, sid, tags}'-curl -s "$BASE/ai-threads/history?tag=app:adom/fusion-bridge" # server-side filter-```--**Bring the user back to their thread, with a visual cue.** `highlight: true` on focus-foregrounds the tab AND pulses its header for about a second, so the user sees WHY this-tab just came forward ("this existing thread is why nothing new opened"):--```bash-curl -sX POST "$BASE/ai-threads/focus" -H 'content-type: application/json' \- -d '{"sid":"<sid>","highlight":true}'-```--A thread with no conversation yet cannot be tagged here (there is no id to key on); pass-`tags` to `/ai-threads/new` instead and they attach when the id appears.--## Route wiki issues into a thread--Associate a thread with a wiki repo and Hydrogen's global notification watcher queues new-issues on that thread's row (amber count; the user injects with one click, or opts into-auto-inject in Settings):--```bash-curl -sX POST "$BASE/ai-threads/repo-link" -H 'content-type: application/json' \- -d '{"ai":"claude","sid":"<sid>","repo":"adom/adom-shotlog"}' # repo:"" clears-curl -s "$BASE/ai-threads/repo-links"-curl -s "$BASE/ai-threads/issue-queue" # or ?key=<ai>:<sid>-curl -sX POST "$BASE/ai-threads/issue-inject" -H 'content-type: application/json' \- -d '{"ai":"claude","sid":"<sid>"}' # injects the next queued issue-```--One thread per repo (a new association moves it and reports `replaced`). Injected issues-are framed as bug reports to evaluate, never as instructions, and only an injected-issue's notification is marked read.--## Reopen a past conversation--```bash-curl -s "$BASE/ai-threads/history" | jq '.items[] | {ai, id, title, updated_ms}'-curl -sX POST "$BASE/ai-threads/resume" -H 'content-type: application/json' \- -d '{"ai":"codex","id":"01a01bfb-a89d-75e0-9f17-01a163ba399b"}'-```--History lists conversations on disk for all three AIs and EXCLUDES anything currently open,-so "in History" means "not open right now". Every AI reopens into a real editor tab.--## Close a tab--```bash-curl -sX POST "$BASE/ai-threads/close" -H 'content-type: application/json' -d '{"tid":"g0t1"}'-```--The conversation is not deleted. It moves to History and can be reopened.--## Rules that will save you time--1. **Store `sid`, never `tid`.** `tid` is a position and moves.-2. **Do not pick an AI on the user's behalf.** Omit `type` unless your feature is genuinely- specific to one.-3. **Trust `ok`, and read the rest.** These endpoints verify against real state (a tab- appeared, the composer holds exactly your text, the message reached the conversation) and- report `ok: false` with a reason rather than claiming success.-4. **Do not drive the editor UI yourself.** Clicking tabs or typing into webviews from- outside races Hydrogen's own sweep and will deliver to the wrong conversation. Everything- you need is here.-5. **Do not hardcode the port.**--## Complete example: a dock icon opens a thread about its app--```javascript-const base = await fetch('/ports').then(r => r.json()).then(p => `http://127.0.0.1:${p.control}`);--async function openThreadFor(app) {- const r = await fetch(`${base}/ai-threads/new`, {- method: 'POST',- headers: { 'content-type': 'application/json' },- // No `type`: the user's default_ai decides. No prompt if you would rather they type.- body: JSON.stringify({ prompt: `I just opened ${app.name}. Help me use it.` }),- }).then(r => r.json());-- if (!r.ok) throw new Error(r.error || 'could not open a thread');- return r; // {ok, type, from_default, result:{via, ...}}-}-```