feat(ai-tools): Settings → AI Tools, Default/Other lane picker, per-run bypass switch (#40 A1)

Settings → Dashboard gains an AI Tools section (installed tools with
Enabled/Disabled and Set default, available ones with a homepage link,
Refresh). useAiTools shares one catalog copy across the app and replaces
LANE_TOOL_LABEL. The Add-lane modal picks Default (<tool>) or Other…; the
router row reads Native (<tool>) and disables AI Router profiles for tools
that cannot use one. The run form gives catalog tools resume and a red
per-run Bypass permissions switch (Codex too). Claude Code / Codex settings
tabs hide while that tool is disabled. The Workspace probes each lane tool's
CLI fresh.

Docs: CLAUDE.md tool/bypass rule, AGENTS.md, LANES.md, API.md, CLI.md,
server/README.md, OpenAPI fragment + regenerated openapi.yaml.
This commit is contained in:
2026-09-28 11:19:30 +07:00
parent 77917203fd
commit fcfebe44f1
33 changed files with 1306 additions and 153 deletions
+14 -2
View File
@@ -2075,14 +2075,16 @@ GET /api/run/history?limit=&laneId= Persisted run history; laneId narrows to
GET /api/run/files?cwd=&q= Fuzzy file search inside cwd for the @-file autocomplete
POST /api/run/drop-image Raw image body (png/jpeg/gif/webp, ≤20 MB) → {path}: saved 0600 under <tmpdir>/agenthub-drops-<uid>/ with a random name, pruned after a day; the terminal pastes the path
(skips node_modules, .git, dist, build, .next, .cache, coverage, vendor)
POST /api/run Start/attach run — Body: { laneId, cwd?, model?, permissionMode?, effort?, resumeSessionId?, initialPrompt? }
POST /api/run Start/attach run — Body: { laneId, cwd?, model?, permissionMode?, effort?, resumeSessionId?, initialPrompt?, sandbox?, approval?, resumeLast?, bypass? }
GET /api/run/:id Run handle (returns live run state)
DELETE /api/run/:id Kill (SIGTERM → SIGKILL after 5 s)
```
`:id` must be a lane pane name, `agenthub-lane-<n>`; anything else answers 404 without reaching tmux, so these two routes can never touch a tmux session the dashboard did not start.
**`GET /api/run/binary`:** Probes for the binary (`claude`, `codex` or `agy`). Optional query parameter `?tool=codex` probes for Codex and `?tool=antigravity` for Antigravity's `agy`; omitted or `?tool=claude` probes for Claude. Returns `{ found: bool, path: string | null }`.
**`GET /api/run/binary`:** Probes PATH for a lane tool's CLI, looked up in the AI Tools catalog (`?tool=<id>`, default `claude`; an unknown id is `400 EBADTOOL`). Always a fresh look, not the catalog's cached detection. Returns `{ found: bool, path: string | null }`.
**`bypass: true`** (both `POST /api/run` and `POST /api/lanes/:id/start`) appends the lane tool's catalog bypass args/env for that run only; Codex's replaces `-s`/`-a`, Claude has none. A lane whose tool is no longer in the catalog answers `400 ENOTOOL`.
**`POST /api/run` (start/attach):** Requires `laneId` (the lane this run belongs to). Creates or attaches an existing tmux session named `agenthub-lane-<id>` in the lane's working directory. If that session already exists and its pane is idling at a shell prompt, the appropriate command line (including `--resume` and any initial prompt) is typed into that pane instead of being dropped; if the pane is running a program, the request adopts the session unchanged. Optionally accepts `initialPrompt` to immediately type/send into the session (if empty or omitted, the session is created/attached with no initial input). Returns `{ id, laneId, status, cwd, model, permissionMode, effort, resumeSessionId, sessionId, startedAt, promptPreview }` where `id` is the tmux session name. The dashboard self-heals a lane's `run_id`/`status` on every read if the tmux session has been killed externally.
@@ -2094,6 +2096,16 @@ Every history row carries `lane_id` (the lane whose run it belongs to) and `tool
---
### AI Tools
```
GET /api/ai-tools Catalog + detection — { tools: [...], default }
POST /api/ai-tools/refresh Re-read the login shell's PATH, re-detect, same body
PUT /api/ai-tools/:id Body: { enabled?: boolean, default?: true } — same body
```
Each tool: `{ id, label, command, homepageUrl, installed, path, enabled, source: "builtin"|"custom", launcher, prompt: { mode }, resume: bool, bypass: bool, routerCapable: bool }` — flags only, never the arg arrays. `default` is the effective default: the stored one if installed and enabled, else `claude` if installed and enabled, else the first installed enabled tool, else `null`. `PUT` answers `404 ENOTOOL` for an unknown id, `400 EBADDEFAULT` for a default that is not installed or disabled, `400 EBADFIELD` for a non-boolean `enabled`; disabling the default clears it. Writes are same-origin only. State lives in `<data dir>/ai-tools.json`.
### AI Router profiles
```