From 0aa7fc232e85e114647836df83c1225a1de1e6e4 Mon Sep 17 00:00:00 2001 From: nntrivi2001 Date: Mon, 28 Sep 2026 10:40:45 +0700 Subject: [PATCH] =?UTF-8?q?docs(spec):=20AI=20Tools=20part=20A1=20?= =?UTF-8?q?=E2=80=94=20catalog,=20detection,=20enable/default,=20lanes=20f?= =?UTF-8?q?or=20any=20tool=20(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../specs/2026-09-28-ai-tools-a1-design.md | 268 ++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-28-ai-tools-a1-design.md diff --git a/docs/superpowers/specs/2026-09-28-ai-tools-a1-design.md b/docs/superpowers/specs/2026-09-28-ai-tools-a1-design.md new file mode 100644 index 00000000..98c710a2 --- /dev/null +++ b/docs/superpowers/specs/2026-09-28-ai-tools-a1-design.md @@ -0,0 +1,268 @@ +# AI Tools — part A1: catalog, detection, enable/default, lanes for any tool + +Issue: #40. Date: 2026-09-28. Status: design agreed in chat, awaiting spec review. + +Part of a four-part split. **A1** (this spec) makes AgentHub know about many AI +coding CLIs and run a lane on any of them. **A2** adds Install / Update, remote +catalog sync and favicons (decisions recorded at the end, own spec later). +**B** moves per-tool settings into each tool's Settings tab. **C** brings Codex +to parity (token usage + pricing, hooks). **D** does the same for Antigravity +after a spike. + +References studied: Orca (`stablyai/orca`, MIT) and 9Router (npm `9router` +0.5.91, MIT). Catalog data (commands, flags, homepages) is taken from Orca's +`agent-catalog.tsx`, `tui-agent-config.ts` and `tui-agent-permissions.ts`, +with attribution in `server/data/ai-tools/README.md`. + +## Goals + +- Settings → Dashboard gets an **AI Tools** section: **Installed (N detected)** + with **Refresh**, and **Available to install**. Each row: icon (letter avatar + in A1, favicon in A2), label, command, **Enabled / Disabled**, + **Set default / ✓ Default**, homepage link ↗. +- A newly installed CLI shows up without editing any config: the catalog is + broad (~40 tools, Oh My Pi included), detection re-reads the user's login-shell + PATH, and (A2) the catalog updates itself. +- A lane can be created for any installed, enabled tool; it runs the tool's CLI + in the lane's tmux pane. +- A disabled tool is hidden from Add lane and its Settings tab, and the server + refuses a NEW lane for it. Existing lanes keep working. + +## Non-goals (A1) + +Installing or updating a tool, remote catalog sync, favicons (A2). Per-tool +settings tabs, pricing and hooks for non-Claude tools (B, C, D). Per-tool +command/args/env overrides in the UI (Orca has them; rejected — they let a +browser choose an executable for the server to run; a custom JSON file in +`DASHBOARD_AI_TOOLS_DIR` covers the need). Heuristic discovery of CLIs that no +catalog lists. + +## 1. Catalog + +One JSON file per tool in `server/data/ai-tools/.json`, plus any +`*.json` in `DASHBOARD_AI_TOOLS_DIR` (same override rule as +`DASHBOARD_PIPELINES_DIR`: a custom file with a built-in id replaces it). + +```json +{ + "id": "antigravity", + "label": "Antigravity", + "detect": ["agy"], + "homepageUrl": "https://antigravity.google/docs/cli/overview", + "faviconDomain": "antigravity.google", + "prompt": { "mode": "flag", "flag": "-i" }, + "resume": ["--continue"], + "bypass": ["--dangerously-skip-permissions"] +} +``` + +- `id` — `^[a-z0-9][a-z0-9-]{0,39}$`. It is what `lanes.tool` stores. +- `detect` — bare command names, no `/`. The first one found on PATH is also + the command the lane runs (Orca's `detectCmd` + aliases). +- `requires` (optional) — other commands that must also be on PATH. +- `args` (optional) — fixed args right after the command, before anything + else (e.g. Hermes needs `["chat"]` before its `--query` prompt flag). +- `prompt` — how the first prompt reaches the tool: + - `{ "mode": "argv", "separator": "--" }` — trailing positional, optional + option terminator before it; + - `{ "mode": "flag", "flag": "-i" }` — `[flag, prompt]`; + - `{ "mode": "after-start", "delayMs": 3000 }` — start the bare CLI, then + paste the prompt into the pane after `delayMs` (`ponytail:` fixed delay; + add readiness detection like Orca's if a tool loses prompts). +- `resume` (optional) — args that continue the last session. Absent: no Resume + button. +- `bypass` / `bypassEnv` (optional) — args / env that turn off the tool's + permission prompts. Absent: no bypass switch. +- `launcher` (built-ins only) — `"claude"` or `"codex"`: the lane keeps using + the existing hand-written argv builders (model, effort, permission mode, + sandbox, approval, AI Router). A custom file may not set it. +- `faviconDomain`, `install` — used by A2; ignored in A1. + +**Where the data comes from.** Orca ships and exercises this data for ~40 +tools, so it is the source: `detectCmd`+aliases → `detect`, `requires` from +`detectRequiredCommands`, prompt modes mapped as `argv` → `argv` (+ its +`argvPromptSeparator`), `flag-prompt` → flag `--prompt`, +`flag-prompt-interactive` → flag `--prompt-interactive`, `flag-interactive` → +flag `-i`, `stdin-after-start` → `after-start`, `hermes-query` → `args +["chat"]` + flag `--query`; `YOLO_TUI_AGENT_ARGS` split into argv arrays → +`bypass`, `YOLO_TUI_AGENT_ENV` → `bypassEnv`. `resume` is filled only where +the tool's `--help` or official docs confirm a continue-last flag; anything +unconfirmed is **omitted**, not guessed. Tools whose launch is not "their own +command" (Orca's `claude-agent-teams`, which launches through Orca itself) are +left out. + +**Validation** (`server/lib/ai-tools.js`): a file that fails any rule above is +skipped with one log line; it never throws at startup. Duplicate ids: custom +beats built-in, else first file wins. Antigravity moves from hard-coded +`pty-run.js` code to its catalog file. + +## 2. Detection + +- `detectTools()` resolves each tool's `detect` names by walking the PATH + directories with `fs` and checking for an executable file — no process per + tool (Windows is out of scope; AgentHub runs on Linux/macOS/WSL). Cached + until the next refresh. +- **PATH hydration.** The server's PATH is whatever the systemd unit / + LaunchAgent captured at install time, so a CLI installed later into a new + directory is invisible. At startup and on Refresh, run the user's `$SHELL` + (fallback `/bin/bash`) with `-ilc` and a command that prints `$PATH` between + two delimiters, with a 5 s timeout; the text between the delimiters is merged + into `process.env.PATH` (new segments appended). Interactive (`-i`) because + nvm and most installers add to PATH in `.bashrc`, which `-lc` skips; the + delimiters discard whatever the rc files print. stdin is `ignore`, so an rc + file that waits for input or auto-attaches tmux gets EOF instead of hanging + until the timeout. Failure keeps the existing PATH. It runs after the server + is listening, never before. Off with `AGENTHUB_SHELL_PATH=0` for users whose + rc files must not run from a service. Taken from Orca's + `hydrate-shell-path.ts`. +- Result per tool: `installed`, `path` (resolved binary), `command` (the name + that matched). +- **The pane gets the same PATH.** A lane pane's environment comes from the + tmux server, not from AgentHub's process, and an npm-installed CLI is a + `#!/usr/bin/env node` script that dies without `node` on PATH. So every spawn + passes the hydrated `PATH` through `tmux new-session -e` / `respawn-pane -e` + (the channel `AGENTHUB_LANE_ID` already uses), and a catalog tool is started + by its resolved absolute `path`. + +## 3. Enable / default + +Stored in `/ai-tools.json`: +`{ "disabled": ["grok"], "default": "claude" }`. + +- Tools are enabled unless listed in `disabled` — a freshly detected or + installed tool is on. +- `default`: null or an id. Effective default = `default` if it is installed + and enabled, else `claude` if installed and enabled, else the first installed + enabled tool in catalog order, else null. +- Only ids present in the catalog are accepted. Disabling the default clears it. + +## 4. API + +All mutating routes sit behind the existing `sameOriginGuard`. + +- `GET /api/ai-tools` → + `{ tools: [{ id, label, command, homepageUrl, installed, path, enabled, + source: "builtin"|"custom", launcher, prompt: {mode}, resume: bool, + bypass: bool, routerCapable: bool }], default: id|null }`. + `routerCapable` is true for `claude` and `codex` only. Arg arrays are not + returned. +- `POST /api/ai-tools/refresh` → hydrate PATH, re-detect, same body as GET. +- `PUT /api/ai-tools/:id` with `{ enabled?: boolean, default?: true }` → + same body as GET. Unknown id 404 `ENOTOOL`; making a not-installed or + disabled tool the default 400 `EBADDEFAULT`. +- `GET /api/run/binary?tool=` keeps its shape (`{found, path}`) but + resolves through the catalog; an unknown id stays 400 `EBADTOOL`. + +## 5. Lanes on any tool + +- `lanes.tool` accepts any catalog id. `VALID_TOOLS` (in `lanes.js`, + `pty-run.js`, `routes/lanes.js`) is replaced by `aiTools.has(id)`. No schema + change: `lanes.tool` and `dashboard_runs.tool` are unconstrained TEXT, and + `idx_lanes_cwd_tool` still allows one lane per (cwd, tool). +- Creating a lane (`POST /api/lanes`, `/ensure`, `/worktree`) for a disabled + tool → 400 `EDISABLED`; unknown → + 400 `EBADTOOL`. A tool that is not installed may still get a lane (it can be + installed afterwards); Start then fails with the existing missing-binary + banner. +- `agenthub lanes add` has no `--tool` flag today; it gains `--tool ` + (default `claude`), and the help strings saying `--tool claude|codex` become + `--tool `. +- **Spawn** (`pty-run.js`): `launcher: "claude"|"codex"` → the existing + builders. Any other tool → `[path, ...args, ...resume?, ...bypass?, + ...prompt]` per the prompt mode; `after-start` sets a named tmux buffer with + `set-buffer -b -- ` and pastes it with `paste-buffer -p -d -b + ` + Enter after `delayMs` (argv only — the tmux wrapper has no stdin), + skipped if the pane is gone. `POST /api/run` and `POST /api/lanes/:id/start` + both forward `bypass`. + Args come only from the catalog; the prompt is always ONE argv element or a + paste buffer, never part of a shell string. +- **Bypass switch.** The run request carries `bypass: true`; the server + appends the catalog's `bypass` args / `bypassEnv`. Codex gains it + (`--dangerously-bypass-approvals-and-sandbox`); Claude does not (its + `bypassPermissions` permission mode already exists). Default off, never + remembered between runs. This replaces CLAUDE.md's "bypass flags are never + emitted" rule with "only when the user ticks the switch for that run". +- A lane whose tool left the catalog: label falls back to the id, Start → + 400 `ENOTOOL`, the lane is not touched otherwise. +- Non-Claude tools: no pipeline, no agents install (as codex/antigravity + today); running/idle comes from the exited-pane heal sweep. +- Places that list tools by name today and must follow the catalog instead: + `dashboard-runs.js` (stores any id, not codex/antigravity-or-claude), + `router-apply.js` (the Antigravity special case becomes "a tool that is not + `routerCapable` accepts only `native`, else `EUNSUPPORTED`"), `routes/run.js` + (binary probe). Filters that mean exactly one tool stay (`codex-ingest.js`, + `cc-reload.js`, hook ingestion pinned to `claude`). + +## 6. UI + +- `client/src/hooks/useAiTools.ts` — fetches `/api/ai-tools`, refetches on + window focus and after a mutation. Replaces `LANE_TOOL_LABEL` and the + Workspace's per-tool binary probes; `LaneTool` becomes `string`. +- `client/src/components/settings/AiToolsSettings.tsx` — the AI Tools section + of Settings → Dashboard (Settings.tsx is ~1,900 lines already), with its own + table-of-contents entry. Rows as in Goals; A1 shows a letter avatar and no + Install button. An "Available to install" row has only the homepage link — + no Enabled/Default controls for a tool that is not there. +- **Add lane — Which CLI:** two buttons, **Default (