Files
AgentHub/server/openapi-extra/override-core.js
T
nntrivi2001 442f133930 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.
2026-10-05 13:22:13 +07:00

481 lines
20 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file Enriched OpenAPI OVERRIDE operations for the core read/ingest endpoints
* that already exist in `server/openapi.js`:
*
* GET /api/events
* GET /api/events/facets
* GET /api/stats
* GET /api/analytics
* POST /api/hooks/event
*
* These operations are intentionally CONTRACT-IDENTICAL to the base spec. Every
* `operationId`, `tags` value, parameter name/`in`/schema, request-body `$ref`,
* and response `$ref` is copied verbatim from `server/openapi.js`. The ONLY
* additions here are richer prose `description`s, realistic per-parameter
* `example`s, and realistic media-type `example`s on request/response bodies —
* none of which change the wire contract.
*
* This module exports the override-merge surface expected by the spec builder:
* - `tags`: [] (no new tags — reuse the base Events/Stats/Analytics/Hooks tags)
* - `schemas`: {} (no new schemas — reuse base `$ref`s only)
* - `paths`: the enriched override operations keyed by path
*
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
"use strict";
// No new tags. The base operations already belong to the Events / Stats /
// Analytics / Hooks tags; overriding those tag arrays here would risk drift.
const tags = [];
// No new schemas. Every response and request body below reuses an existing
// `#/components/schemas/...` `$ref` defined in `server/openapi.js`.
const schemas = {};
const paths = {
"/api/events": {
get: {
tags: ["Events"],
summary: "List events with multi-dimensional filtering",
operationId: "listEvents",
description:
"Returns a paginated, reverse-chronological slice of the `events` table " +
"(ordered by `created_at DESC, id DESC`) together with the total row count " +
"matching the active filters, so the UI can drive a paginator without a " +
"second request.\n\n" +
"All four entity filters — `event_type`, `tool_name`, `agent_id`, and " +
"`session_id` — accept a **comma-separated list (CSV)** of values and match " +
"with `IN (...)` semantics: passing `event_type=Stop,PreToolUse` returns rows " +
"whose `event_type` is either `Stop` OR `PreToolUse`. Values are trimmed and " +
"blank entries are dropped. Filters are combined with one another using AND.\n\n" +
"`q` performs a case-insensitive substring (`LIKE %q%`) search across the " +
"`summary`, `tool_name`, and the JSON-encoded `data` columns. `from`/`to` are " +
"inclusive ISO-8601 datetime bounds on `created_at`; unparseable values are " +
"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.\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",
name: "event_type",
description:
"Comma-separated (CSV) list of `event_type` values; matched with IN semantics " +
"(OR within the list). Common values: PreToolUse, PostToolUse, Stop, " +
"SubagentStop, Notification, SessionStart, SessionEnd.",
schema: { type: "string" },
example: "Stop,PreToolUse",
},
{
in: "query",
name: "tool_name",
description:
"Comma-separated (CSV) list of `tool_name` values; matched with IN semantics " +
"(OR within the list). Common values: Bash, Edit, Read, Write, Grep, Glob, Task.",
schema: { type: "string" },
example: "Bash,Edit",
},
{
in: "query",
name: "agent_id",
description:
"Comma-separated (CSV) list of `agent_id` values; matched with IN semantics. " +
"The main agent of a session uses the id `<session_id>-main`.",
schema: { type: "string" },
example: "8f3c2a10-1b2c-4d5e-9f80-112233445566-main",
},
{
in: "query",
name: "session_id",
description:
"Comma-separated (CSV) list of `session_id` values; matched with IN semantics " +
"(OR within the list).",
schema: { type: "string" },
example: "8f3c2a10-1b2c-4d5e-9f80-112233445566,2a7d9e44-3c1f-4a6b-bc20-aabbccddeeff",
},
{
in: "query",
name: "q",
description:
"Case-insensitive substring search (`LIKE %q%`) applied across the `summary`, " +
"`tool_name`, and JSON-encoded `data` columns.",
schema: { type: "string" },
example: "curl",
},
{
in: "query",
name: "from",
description:
"ISO-8601 datetime lower bound (inclusive) on `created_at`. Unparseable values " +
"are ignored.",
schema: { type: "string", format: "date-time" },
example: "2026-06-25T00:00:00.000Z",
},
{
in: "query",
name: "to",
description:
"ISO-8601 datetime upper bound (inclusive) on `created_at`. Unparseable values " +
"are ignored.",
schema: { type: "string", format: "date-time" },
example: "2026-06-26T00:00:00.000Z",
},
{
$ref: "#/components/parameters/SourcesQuery",
example: "local,4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11",
},
{
in: "query",
name: "limit",
description: "Max rows to return; clamped to 1–500 (default 50).",
schema: { type: "integer", minimum: 1, maximum: 500, default: 50 },
example: 50,
},
{ $ref: "#/components/parameters/OffsetQuery" },
],
responses: {
200: {
description: "Event list with total count for pagination",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/EventsListResponse" },
example: {
events: [
{
id: 48213,
session_id: "8f3c2a10-1b2c-4d5e-9f80-112233445566",
agent_id: "8f3c2a10-1b2c-4d5e-9f80-112233445566-main",
event_type: "PreToolUse",
tool_name: "Bash",
summary: "Bash: curl -s https://api.example.com/health",
data: '{"session_id":"8f3c2a10-1b2c-4d5e-9f80-112233445566","hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"curl -s https://api.example.com/health","description":"Check upstream health"},"cwd":"/Users/dev/project"}',
created_at: "2026-06-25T18:42:07.512Z",
},
{
id: 48212,
session_id: "8f3c2a10-1b2c-4d5e-9f80-112233445566",
agent_id: "8f3c2a10-1b2c-4d5e-9f80-112233445566-main",
event_type: "Stop",
tool_name: null,
summary: "Session finished responding",
data: '{"session_id":"8f3c2a10-1b2c-4d5e-9f80-112233445566","hook_event_name":"Stop"}',
created_at: "2026-06-25T18:41:55.004Z",
},
],
limit: 50,
offset: 0,
total: 1342,
},
},
},
},
},
},
},
"/api/events/facets": {
get: {
tags: ["Events"],
summary: "Distinct event_type and tool_name values available in the DB",
operationId: "listEventFacets",
description:
"Returns the distinct, non-null `event_type` and `tool_name` values currently " +
"present in the `events` table, each sorted alphabetically. The UI uses this to " +
"populate the filter dropdowns on the Events screen without hardcoding the set of " +
"tools or hook types — so the lists automatically reflect whatever has actually " +
"been ingested. Both arrays are independent and may be empty when the table holds " +
"no matching rows.",
responses: {
200: {
description: "Facet values for populating filter dropdowns",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/EventsFacetsResponse" },
example: {
event_types: [
"Notification",
"PostToolUse",
"PreToolUse",
"SessionEnd",
"SessionStart",
"Stop",
"SubagentStop",
],
tool_names: ["Bash", "Edit", "Glob", "Grep", "Read", "Task", "Write"],
},
},
},
},
},
},
},
"/api/stats": {
get: {
tags: ["Stats"],
summary: "Get aggregate dashboard stats",
operationId: "getStats",
description:
"Returns the headline counters shown across the top of the dashboard: total and " +
"active session/agent counts, total event count, today's event count, and the " +
"current number of live WebSocket connections.\n\n" +
"The overview counters are spread at the top level of the response object. Two " +
"additional maps, `agents_by_status` and `sessions_by_status`, break the counts " +
"down by lifecycle status (e.g. agents: working/waiting/completed/error; sessions: " +
"active/completed/error/abandoned). **Statuses with a zero count are omitted from " +
"these maps**, so callers must not assume every status key is present.\n\n" +
"`events_today` is computed in the caller's local day. Pass `tz_offset` as the " +
"minutes value from JavaScript's `Date.prototype.getTimezoneOffset()` (for example " +
"`420` for US Pacific Daylight Time, `300` for US Eastern Daylight Time, `0` for " +
"UTC). When omitted or non-numeric, the server falls back to UTC (offset 0).",
parameters: [
{
in: "query",
name: "tz_offset",
description:
"Caller timezone offset in MINUTES, as returned by JS " +
"`Date.prototype.getTimezoneOffset()` (e.g. 420 for PDT, 300 for EDT, 0 for " +
"UTC). Used to bucket `events_today` into the caller's local day. Defaults to " +
"0 (UTC) when omitted or non-numeric.",
schema: { type: "integer" },
example: 420,
},
{
$ref: "#/components/parameters/SourcesQuery",
example: "local,4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11",
},
],
responses: {
200: {
description: "Statistics overview",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/StatsResponse" },
example: {
total_sessions: 184,
active_sessions: 3,
active_agents: 5,
total_agents: 372,
total_events: 28451,
events_today: 612,
ws_connections: 2,
agents_by_status: {
working: 4,
waiting: 1,
completed: 360,
error: 7,
},
sessions_by_status: {
active: 3,
completed: 175,
error: 6,
},
},
},
},
},
},
},
},
"/api/analytics": {
get: {
tags: ["Analytics"],
summary: "Get analytics aggregates",
operationId: "getAnalytics",
description:
"Returns the full analytics rollup powering the Analytics screen: aggregate token " +
"usage (`tokens`), total estimated spend across all sessions (`total_cost`, in USD, " +
"computed from the configured pricing rules), per-tool invocation counts " +
"(`tool_usage`), per-day event and session time series (`daily_events`, " +
"`daily_sessions`), the distribution of subagent types (`agent_types`), per-type " +
"event counts (`event_types`), the mean number of events per session " +
"(`avg_events_per_session`), the total subagent count (`total_subagents`), and a " +
"nested `overview` object mirroring the headline session/agent/event counters.\n\n" +
"As with `/api/stats`, the top-level `agents_by_status` and `sessions_by_status` " +
"maps **omit statuses whose count is zero**. The `agent_types[].subagent_type` field " +
"may be `null` for the main agent / untyped subagents.\n\n" +
"The daily time series are bucketed by the caller's local day. Pass `tz_offset` as " +
"the minutes value from JS `Date.prototype.getTimezoneOffset()` (e.g. `420` for " +
"PDT). When omitted or non-numeric, the server buckets in UTC.",
parameters: [
{
in: "query",
name: "tz_offset",
description:
"Caller timezone offset in MINUTES, as returned by JS " +
"`Date.prototype.getTimezoneOffset()` (e.g. 420 for PDT, 300 for EDT, 0 for " +
"UTC). Used to bucket the `daily_events` / `daily_sessions` time series into " +
"the caller's local day. Defaults to UTC when omitted or non-numeric.",
schema: { type: "integer" },
example: 420,
},
{
$ref: "#/components/parameters/SourcesQuery",
example: "local,4d1f0e2a-7b9c-4c33-8a21-9e0f7b6d4c11",
},
],
responses: {
200: {
description: "Analytics response",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/AnalyticsResponse" },
example: {
tokens: {
total_input: 4821002,
total_output: 1933517,
total_cache_read: 19288440,
total_cache_write: 2044120,
},
total_cost: 42.7183,
tool_usage: [
{ tool_name: "Bash", count: 5821 },
{ tool_name: "Read", count: 4310 },
{ tool_name: "Edit", count: 2980 },
{ tool_name: "Grep", count: 1744 },
],
daily_events: [
{ date: "2026-06-23", count: 488 },
{ date: "2026-06-24", count: 921 },
{ date: "2026-06-25", count: 612 },
],
daily_sessions: [
{ date: "2026-06-23", count: 4 },
{ date: "2026-06-24", count: 9 },
{ date: "2026-06-25", count: 6 },
],
agent_types: [
{ subagent_type: null, count: 184 },
{ subagent_type: "general-purpose", count: 96 },
{ subagent_type: "Explore", count: 71 },
{ subagent_type: "code-reviewer", count: 21 },
],
event_types: [
{ event_type: "PreToolUse", count: 14210 },
{ event_type: "PostToolUse", count: 13988 },
{ event_type: "Stop", count: 168 },
{ event_type: "SubagentStop", count: 85 },
],
avg_events_per_session: 154.6,
total_subagents: 188,
overview: {
total_sessions: 184,
active_sessions: 3,
active_agents: 5,
total_agents: 372,
total_events: 28451,
},
agents_by_status: {
working: 4,
waiting: 1,
completed: 360,
error: 7,
},
sessions_by_status: {
active: 3,
completed: 175,
error: 6,
},
},
},
},
},
},
},
},
"/api/hooks/event": {
post: {
tags: ["Hooks"],
summary: "Ingest Claude Code hook event",
operationId: "ingestHookEvent",
description:
"Primary ingestion endpoint for Claude Code lifecycle hooks. The hook handler posts " +
"an envelope of the form `{ hook_type, data }`, where `hook_type` is the Claude " +
"Code hook name (PreToolUse, PostToolUse, Stop, SubagentStop, Notification, " +
"SessionStart, SessionEnd) and `data` carries the raw hook payload — at minimum a " +
"`session_id`. The server upserts the session and its main agent on first sight, " +
"applies the appropriate lifecycle state transition, extracts token usage and " +
"compaction signals from the transcript when present, persists an `events` row " +
"(storing `data` as a JSON-encoded string), and broadcasts a `new_event` message " +
"over the WebSocket.\n\n" +
"On success the response is `{ ok: true, event: { ... } }`, where `event` echoes " +
"the normalized row that was just inserted (`session_id`, `agent_id`, `event_type`, " +
"`tool_name`, `summary`, `created_at`). Ingestion is designed to be fail-safe and " +
"non-blocking for the hook caller.\n\n" +
"The optional `tool` (`codex`, `antigravity`; anything else is `claude`) names the " +
"CLI that sent the hook. A Codex or agy hook is mapped onto the same hook types " +
"first (a Codex PermissionRequest becomes a Notification, an Interrupt a Stop; an " +
"agy turn's first PreInvocation a UserPromptSubmit), and one that maps to nothing " +
"is answered `{ ok: true, ignored: true }`. A hook delivered twice is answered " +
"`{ ok: true, duplicate: true }` and stored once.\n\n" +
"Validation failures return HTTP 400 with an `ErrorResponse` body " +
"(`{ error: { code, message } }`): `INVALID_INPUT` when `hook_type` or `data` is " +
"missing, and `MISSING_SESSION` when `data.session_id` is absent.",
requestBody: {
required: true,
content: {
"application/json": {
schema: { $ref: "#/components/schemas/HookEventRequest" },
example: {
hook_type: "PreToolUse",
data: {
session_id: "8f3c2a10-1b2c-4d5e-9f80-112233445566",
hook_event_name: "PreToolUse",
tool_name: "Bash",
tool_input: {
command: "curl -s https://api.example.com/health",
description: "Check upstream health",
},
cwd: "/Users/dev/project",
transcript_path:
"/Users/dev/.claude/projects/-Users-dev-project/8f3c2a10-1b2c-4d5e-9f80-112233445566.jsonl",
},
},
},
},
},
responses: {
200: {
description: "Event processed",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/HookEventResponse" },
example: {
ok: true,
event: {
session_id: "8f3c2a10-1b2c-4d5e-9f80-112233445566",
agent_id: "8f3c2a10-1b2c-4d5e-9f80-112233445566-main",
event_type: "PreToolUse",
tool_name: "Bash",
summary: "Bash: curl -s https://api.example.com/health",
created_at: "2026-06-25T18:42:07.512Z",
},
},
},
},
},
400: {
description: "Invalid hook payload",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
example: {
error: {
code: "MISSING_SESSION",
message: "session_id is required in data",
},
},
},
},
},
},
},
},
};
module.exports = { tags, schemas, paths };