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
+4 -4
View File
@@ -448,17 +448,17 @@ The OpenAPI spec is generated from `server/openapi.js` (`createOpenApiSpec()`),
| Method | Path | Description |
| ------- | ------------------- | ------------------------------------------------ |
| `GET` | `/api/health` | Server health check (`status`, `version`, `timestamp`) |
| `GET` | `/api/sessions` | List sessions (`status`, `limit`, `offset`; `tool=claude\|codex\|antigravity` filters by CLI before the limit) |
| `GET` | `/api/sessions` | List sessions (`status`, `limit`, `offset`; `tool=claude\|codex\|antigravity`, `awaiting=1` and `from`/`to` on `started_at` filter before the limit) |
| `GET` | `/api/sessions/:id` | Session detail (includes `agents` + `events`) |
| `POST` | `/api/sessions` | Create session (idempotent by `id`) |
| `PATCH` | `/api/sessions/:id` | Update session |
| `GET` | `/api/sessions/:id/transcripts` | List the session's transcript files (main + sub-agents) |
| `GET` | `/api/sessions/:id/transcript` | Cursor-paginated message stream for one transcript |
| `GET` | `/api/agents` | List agents (`status`, `session_id`, pagination)|
| `GET` | `/api/agents` | List agents (`status`, `session_id`, pagination; `session_ids=a,b` — ≤ 200 — wins over the rest)|
| `GET` | `/api/agents/:id` | Agent detail |
| `POST` | `/api/agents` | Create agent (idempotent by `id`) |
| `PATCH` | `/api/agents/:id` | Update agent |
| `GET` | `/api/events` | List events (`session_id`, `limit`, `offset`) |
| `GET` | `/api/events` | List events (`session_id`, `limit`, `offset`); each row also carries `session_name`/`session_cwd`/`session_lane_id`/`agent_name`/`agent_subagent_type` (REST only, `new_event` unchanged) |
| `GET` | `/api/stats` | Dashboard aggregate counters |
| `GET` | `/api/analytics` | Analytics aggregates for charts/trends |
| `GET` | `/api/metrics` | Prometheus / OpenMetrics exposition (text; v0.0.4) |
@@ -693,7 +693,7 @@ Because sync runs non-interactively (`ssh -o BatchMode=yes`), the connection mus
| `DELETE` | `/api/settings/antigravity-hooks` | Remove the `agenthub` hook only (`sameOriginGuard`) |
| `GET` | `/api/settings/export` | Export all data (sessions, agents, events, token_usage, workflows, dashboard_runs, alert_rules, model_pricing) as one versioned JSON attachment |
| `POST` | `/api/settings/import` | Restore a bundle from `/export`. Multipart `file`, or JSON `{ path }` (server reads it). Idempotent + non-destructive: sessions already present are skipped whole |
| `POST` | `/api/settings/cleanup` | Abandon stale sessions and purge old data |
| `POST` | `/api/settings/cleanup` | Abandon stale sessions and purge old data (`purge_days` whole ≥ 1, else `400 INVALID_PURGE_DAYS`; `dry_run: true` counts only) |
### Claude Config Explorer (`/api/cc-config`)
+6 -1
View File
@@ -55,7 +55,12 @@ const paths = {
"ignored rather than rejected. `limit` is clamped to 1–500 (default 50) and " +
"`offset` is clamped to >= 0 (default 0).\n\n" +
"Note: each returned event's `data` field is a **JSON-encoded string**, not a " +
"nested object — callers must `JSON.parse` it to inspect the payload.",
"nested object — callers must `JSON.parse` it to inspect the payload.\n\n" +
"Each row also 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` (nullable; `null` when the session or " +
"agent row is missing). They are REST-only — the WebSocket `new_event` frame " +
"is unchanged.",
parameters: [
{
in: "query",
+19 -1
View File
@@ -1205,7 +1205,7 @@ const paths = {
tags: ["Settings"],
summary: "Abandon stale sessions and optionally purge old history",
description:
"Two-phase maintenance. Phase 1 (`abandon_hours`, non-destructive): marks any still-`active` session with no events newer than that many hours as `abandoned`, and completes its lingering agents — a tidy-up of crashed/orphaned sessions. Phase 2 (`purge_days`) is ⚠ DESTRUCTIVE and IRREVERSIBLE: it permanently DELETES completed/error/abandoned sessions (and their events, agents, and token_usage) whose `started_at` is older than that many days. Active sessions are NEVER purged. Both fields are optional and independent — send only `abandon_hours` for a safe tidy-up, or include `purge_days` to also reclaim disk. Export first if the purged history matters. The response reports counts for each phase.",
"Two-phase maintenance. Phase 1 (`abandon_hours`, non-destructive): marks any still-`active` session with no events newer than that many hours as `abandoned`, and completes its lingering agents — a tidy-up of crashed/orphaned sessions. Phase 2 (`purge_days`) is ⚠ DESTRUCTIVE and IRREVERSIBLE: it permanently DELETES completed/error/abandoned sessions (and their events, agents, and token_usage) whose `started_at` is older than that many days. Active sessions are NEVER purged. Both fields are optional and independent — send only `abandon_hours` for a safe tidy-up, or include `purge_days` to also reclaim disk. Export first if the purged history matters. `purge_days` must be a whole number ≥ 1, else 400 `INVALID_PURGE_DAYS` (a fraction like 0.1 would purge almost everything). `dry_run: true` answers the same counts (plus `dry_run: true`) and changes nothing — preview a purge before running it. The response reports counts for each phase.",
operationId: "cleanupData",
requestBody: {
required: true,
@@ -1222,6 +1222,10 @@ const paths = {
"Abandon stale sessions, then purge history older than 90 days (destructive)",
value: { abandon_hours: 12, purge_days: 90 },
},
preview: {
summary: "Dry run: count what a 90-day purge would delete, change nothing",
value: { purge_days: 90, dry_run: true },
},
},
},
},
@@ -1236,6 +1240,20 @@ const paths = {
},
},
},
400: {
description: "purge_days is not a whole number of days, 1 or more",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
example: {
error: {
code: "INVALID_PURGE_DAYS",
message: "purge_days must be a whole number of days, 1 or more",
},
},
},
},
},
},
},
},
@@ -146,6 +146,30 @@ const paths = {
"Only the sessions of one CLI. Applied before `limit`, so a busy Codex lane cannot push Claude sessions off a page.",
example: "claude",
},
{
name: "awaiting",
in: "query",
schema: { type: "string", enum: ["1"] },
description:
"`1` = only sessions blocked on the user (`awaiting_input_since IS NOT NULL AND status = 'active'`), the UI's Waiting filter. Applied before `limit`/`offset`, so `total` stays exact.",
example: "1",
},
{
name: "from",
in: "query",
schema: { type: "string", format: "date-time" },
description:
"Inclusive lower bound on `started_at` (ISO 8601). An unparseable value is ignored. Applied before `limit`/`offset`.",
example: "2026-10-01T00:00:00.000Z",
},
{
name: "to",
in: "query",
schema: { type: "string", format: "date-time" },
description:
"Inclusive upper bound on `started_at` (ISO 8601). An unparseable value is ignored. Applied before `limit`/`offset`.",
example: "2026-10-05T23:59:59.999Z",
},
{
$ref: "#/components/parameters/SourcesQuery",
example: "local,4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11",
@@ -334,7 +358,7 @@ const paths = {
tags: ["Sessions"],
summary: "Get aggregated session stats",
description:
"Returns aggregated counts for the SessionOverview panel: total events, events-by-type, the top 15 tools by usage, an error count (events whose `event_type`/`summary` match /error/i or /failed/i), the event time range, agent type/status counts, the subagent-type breakdown (excluding the special `compaction` type, which is surfaced under `agents.compaction`), and token totals. All aggregation runs in SQL, so it stays cheap even for sessions with tens of thousands of events; the endpoint is read-only with no side effects. The frontend debounces calls on `new_event` / `agent_*` / `session_updated` websocket frames so the counters track a running session. Returns 404 with code `NOT_FOUND` when the session does not exist.",
"Returns aggregated counts for the SessionOverview panel: total events, events-by-type, the top 15 tools by usage, the exact `tool_call_count` (PreToolUse events with a `tool_name`, or PostToolUse when the session has none, e.g. an imported one) and `distinct_tool_count` (different tool names) — `tools_used` is a top-15 list, so its sum is not the count —, an error count (events whose `event_type`/`summary` match /error/i or /failed/i), the event time range, agent type/status counts, the subagent-type breakdown (excluding the special `compaction` type, which is surfaced under `agents.compaction`), and token totals. All aggregation runs in SQL, so it stays cheap even for sessions with tens of thousands of events; the endpoint is read-only with no side effects. The frontend debounces calls on `new_event` / `agent_*` / `session_updated` websocket frames so the counters track a running session. Returns 404 with code `NOT_FOUND` when the session does not exist.",
operationId: "getSessionStats",
parameters: [
{
@@ -363,6 +387,8 @@ const paths = {
{ tool_name: "Read", count: 121 },
{ tool_name: "Grep", count: 77 },
],
tool_call_count: 612,
distinct_tool_count: 9,
error_count: 6,
first_event_at: "2026-06-25T14:02:11.052Z",
last_event_at: "2026-06-25T14:31:50.119Z",
@@ -580,7 +606,7 @@ const paths = {
tags: ["Agents"],
summary: "List agents",
description:
"Returns agents, most recent first. Filters are applied with precedence rather than composition: when `session_id` is supplied it wins and returns every agent for that session (ignoring `status` and pagination); otherwise a `status` filter returns paginated agents in that lifecycle state; otherwise all agents are returned paginated. `limit` defaults to 10000 when not a positive integer. Read-only, no side effects. Each agent's `metadata` is returned as a raw JSON-encoded string, not a parsed object.",
"Returns agents, most recent first. Filters are applied with precedence rather than composition: `session_ids` (comma-separated, duplicates and blanks dropped, at most 200 ids, `limit` applied but not `offset`, newest `started_at` first; an empty list answers no agents) wins over everything else and returns every agent of those sessions in one call; next, when `session_id` is supplied it wins and returns every agent for that session (ignoring `status` and pagination); otherwise a `status` filter returns paginated agents in that lifecycle state; otherwise all agents are returned paginated. `limit` defaults to 10000 when not a positive integer. Read-only, no side effects. Each agent's `metadata` is returned as a raw JSON-encoded string, not a parsed object.",
operationId: "listAgents",
parameters: [
{ $ref: "#/components/parameters/AgentStatusQuery", example: "working" },
@@ -588,6 +614,14 @@ const paths = {
$ref: "#/components/parameters/SessionFilterQuery",
example: "b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d",
},
{
name: "session_ids",
in: "query",
schema: { type: "string" },
description:
"Comma-separated session ids: every agent of those sessions in one call (the Dashboard's active trees). Duplicates and blanks are dropped, at most 200 ids are read, `limit` still applies and `offset` does not; ordered by `started_at` DESC. Takes precedence over `session_id`, `status` and `sources`. An empty list answers `{agents: []}`.",
example: "b7f3a2c1-4e5d-4a8b-9c2f-1d6e8a0b3c4d,0c4e9a1b-2d3f-4a5b-8c6d-7e8f9a0b1c2d",
},
{
$ref: "#/components/parameters/SourcesQuery",
example: "local,4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11",
+23 -3
View File
@@ -554,6 +554,17 @@ function createOpenApiSpec() {
},
},
},
tool_call_count: {
type: "integer",
minimum: 0,
description:
"Exact tool calls: PreToolUse events with a tool_name, or PostToolUse ones when the session has no PreToolUse (an imported session). tools_used is a top-15 list, so its sum is not this.",
},
distinct_tool_count: {
type: "integer",
minimum: 0,
description: "Number of different tool names this session called.",
},
error_count: {
type: "integer",
minimum: 0,
@@ -1363,10 +1374,14 @@ function createOpenApiSpec() {
description: "Mark active sessions abandoned if stale for this many hours",
},
purge_days: {
type: "number",
minimum: 0,
type: "integer",
minimum: 1,
description:
"Delete old completed/error/abandoned sessions older than this many days",
"Delete old completed/error/abandoned sessions older than this many days. A whole number ≥ 1, else 400 INVALID_PURGE_DAYS (a fraction would purge almost everything).",
},
dry_run: {
type: "boolean",
description: "true = answer the same counts and change nothing.",
},
},
},
@@ -1379,6 +1394,11 @@ function createOpenApiSpec() {
purged_sessions: { type: "integer" },
purged_events: { type: "integer" },
purged_agents: { type: "integer" },
dry_run: {
type: "boolean",
enum: [true],
description: "Present (true) only on a dry run: the counts were not applied.",
},
},
},
},