docs: UI-audit fixes in CLAUDE.md, API, OpenAPI, LANES and READMEs

New params and errors (agents session_ids, sessions awaiting/from/to,
stats tool counts, event labels, cleanup dry_run/INVALID_PURGE_DAYS, lane
start slot auto/ESESSIONLIVE/EUNSUPPORTED), terminal and pane shortcuts,
the shared UI primitives, accent vs accent-solid, lazy routes and the
Settings groups. openapi.yaml regenerated.
This commit is contained in:
2026-10-05 13:22:13 +07:00
parent d4961d94ef
commit 442f133930
12 changed files with 230 additions and 31 deletions
+50 -2
View File
@@ -12,6 +12,8 @@ Complete REST API and WebSocket documentation for Agent Dashboard.
- [REST API](#rest-api)
- [Sessions](#sessions)
- [Agents](#agents)
- [Events](#events)
- [Data cleanup](#data-cleanup)
- [Tools](#tools)
- [Metrics](#metrics)
- [Pricing](#pricing)
@@ -279,11 +281,13 @@ When the session's tool is Codex, three fields control Codex-specific behavior:
- **`router: "<name>"`** — the AI Router profile THIS session runs on (omitted = native, or for a resume the router its session last ran on); see § AI Router profiles. `400 EBADROUTER` for an unknown name, `400 EUNSUPPORTED` on a tool other than Claude/Codex.
- **`worktree: true`** (Claude workspaces, no `resumeSessionId`) — a NEW session in a git worktree of its own, Claude Code's way: the pane runs `claude --worktree=ah-<id>-<base36 time>` (the `=` form, since `-w [name]` would take the prompt as the name; the name is made by the server), so Claude itself creates `<repo>/.claude/worktrees/<name>` on branch `worktree-<name>`, copies `.worktreeinclude`, runs the user's `WorktreeCreate` hook and cleans up on exit. `/.claude/worktrees/` is added to the repo root's `info/exclude`; the run is recorded with `worktree: 1` (and the run handle carries `worktree: true`). `400 EUNSUPPORTED` on any other tool. A `resumeSessionId` (or fork) of a session that ran in such a worktree starts the pane IN that worktree; one whose worktree is gone is `400 ENOWORKTREE`.
- **`slot`** — which Workspace tab's session to start, `0`–`3` (default `0`, else `400 EBADSLOT`). Slot 0 is the workspace's own `agenthub-lane-<id>` and is what `run_id` records; slots 1–3 are extra sessions on the same workspace, `agenthub-lane-<id>-<slot>`, and never touch `run_id`/`status`. The response adds `run_id`: the session this start opened. `stop`, `reset` and `remove` kill every slot's session, not only `run_id`.
- **`slot`** — which Workspace tab's session to start, `0`–`3` or `"auto"` (default `0`, else `400 EBADSLOT`). `"auto"` lets the server pick the first slot with no live session, under the workspace lock (a client's own pick may come from a stale list); when all four are busy it answers `409 ERUNLIVE` "every tab of this workspace is busy", while a specific busy slot answers `409 ERUNLIVE` "that tab already has a live session". Slot 0 is the workspace's own `agenthub-lane-<id>` and is what `run_id` records; slots 1–3 are extra sessions on the same workspace, `agenthub-lane-<id>-<slot>`, and never touch `run_id`/`status`. The response adds `run_id`: the session this start opened. `stop`, `reset` and `remove` kill every slot's session, not only `run_id`.
- **`shell`** — `true` opens a plain shell in that slot instead of an AI tool (the Workspace's **New terminal** tab): tmux's default shell in the workspace's folder, recorded as tool `shell`. Every other field is ignored, nothing comes from the request but the slot, and the workspace's `run_id`/`status` stay as they are (no agent is at work). A slot with a live session answers `409 ERUNLIVE`.
Error `503 ETMUXMISSING` is returned when the host has no `tmux` binary. Every run the dashboard starts is a tmux session, so this is a missing dependency rather than a bad request — the message carries the install command, and the same code comes back from `POST /api/run`. Nothing else in the dashboard is affected.
Error `409 ESESSIONLIVE` is returned when `resumeSessionId` (or `resume`) names a session a live pane already holds — two `--resume`s of one session would be two agents writing one transcript. `error.run_id` names that pane; open it instead. A fork (`forkSession: true`) makes a new session and is allowed. A `resumeSessionId` on a catalog tool (anything but Claude/Codex) is `400 EUNSUPPORTED`, since it would silently start fresh.
Error `409 ERUNLIVE` is returned if the workspace already has a live run; stop it first with the `stop` action. The same code and status come back when the workspace's tmux pane is already running an agent while a `prompt` or `resumeSessionId` was sent — that argv cannot be typed over a live agent, so the request is refused instead of answered 200 with nothing done. A start that asks for neither still adopts the existing pane silently.
#### Preflight a destructive workspace action
@@ -1233,7 +1237,9 @@ Returns all sessions, ordered by most recent activity.
|-----------|------|---------|-------------|
| `limit` | integer | 50 | Maximum sessions to return (1-1000) |
| `offset` | integer | 0 | Pagination offset |
| `status` | string | - | Filter by persisted status: `active`, `completed`, `error`, `abandoned`. The UI **Waiting** state is derived from the `awaiting_input_since` column and is not a queryable enum — filter `status=active` and inspect `awaiting_input_since` (non-null = Waiting) |
| `status` | string | - | Filter by persisted status: `active`, `completed`, `error`, `abandoned`. The UI **Waiting** state is derived from the `awaiting_input_since` column and is not a `status` value — use `awaiting=1` |
| `awaiting` | `1` | - | Only sessions blocked on the user: `awaiting_input_since IS NOT NULL AND status = 'active'` (the UI's **Waiting** filter) |
| `from` / `to` | ISO 8601 | - | Inclusive bounds on `started_at`; an unparseable value is ignored. Like every filter here, applied before `limit`/`offset`, so `total` stays exact |
| `cwd` | string | - | Exact working directory |
| `tool` | string | - | One CLI's sessions: `claude`, `codex`, `antigravity` |
| `lane` | integer | - | The sessions a workspace OWNS (`sessions.lane_id`): the ones its panes held. `400 INVALID_INPUT` when not an id |
@@ -1397,6 +1403,8 @@ curl http://localhost:4820/api/sessions/sess_abc123/stats
{ "tool_name": "Bash", "count": 1842 },
{ "tool_name": "Read", "count": 1340 }
],
"tool_call_count": 5210,
"distinct_tool_count": 18,
"error_count": 12,
"first_event_at": "2026-04-26T18:59:00.000Z",
"last_event_at": "2026-04-29T21:30:14.000Z",
@@ -1419,6 +1427,8 @@ curl http://localhost:4820/api/sessions/sess_abc123/stats
}
```
`tools_used` is the top 15 only, so its sum is not the session's tool-call count. `tool_call_count` is exact: the session's `PreToolUse` events with a `tool_name`, or its `PostToolUse` events when it has none (an imported session). `distinct_tool_count` is the number of different tool names.
**Error Responses:**
| Code | Description |
@@ -1482,6 +1492,20 @@ curl http://localhost:4820/api/sessions/sess_abc123/agents
### Agents
#### List Agents
```http
GET /api/agents
```
Returns `{ agents, limit, offset }`, newest `started_at` first, each agent with its own `cost` (see the note above). Query parameters: `limit` (default 10000), `offset`, `status`, `session_id`, `sources`, and:
- **`session_ids`** — comma-separated session ids: every agent of those sessions in ONE call (the Dashboard's active trees). Duplicates and blanks are dropped and at most 200 ids are read; `limit` still applies, `offset` does not. An empty list answers `{ "agents": [] }`. When present it takes precedence over `session_id`, `status` and `sources`.
```bash
curl "http://localhost:4820/api/agents?session_ids=sess_abc123,sess_def456"
```
#### Get Agent
```http
@@ -1595,6 +1619,30 @@ sequenceDiagram
---
### Events
#### List Events
```http
GET /api/events
```
Returns `{ events, limit, offset, total }`, newest first, filtered by `event_type`, `tool_name`, `agent_id`, `session_id` (each comma-separated), `q`, `from`/`to` (on `created_at`) and `sources`. Each row carries its labels, so a list page need not fetch every session and agent to name them: `session_name`, `session_cwd`, `session_lane_id`, `agent_name` and `agent_subagent_type` (each `null` when the session/agent row is missing). These columns are REST-only — the WebSocket `new_event` message is unchanged.
---
### Data cleanup
#### Clean up old data
```http
POST /api/settings/cleanup
```
Body `{ abandon_hours?, purge_days?, dry_run? }`. `abandon_hours` marks active sessions silent that long as `abandoned` (and completes their working agents); `purge_days` deletes ended sessions (with their events and agents) that started more than that many days ago. `purge_days` must be a whole number ≥ 1, else `400 INVALID_PURGE_DAYS` — a fraction would purge almost everything. `dry_run: true` answers the same counts (`abandoned`, `purged_sessions`, `purged_events`, `purged_agents`, plus `dry_run: true`) and changes nothing.
---
### Tools
#### List All Tools