57f02f0d89
- openapi-extra/run.js describes tmux pane runs only: the removed
POST /api/run/{id}/message and stream-json run_stream/run_input_ack are gone.
- 108 operations added to OpenAPI (lanes, run, settings, reports, cc-config,
pets, ai-proxies, tool-profiles, ai-tools, snapshots, locks, ...);
openapi.yaml regenerated.
- docs/API.md: 74 routes added, 4 endpoints that no longer exist removed.
- Role is `developer`, not `member` (CLI.md, DATABASE.md, OVERVIEW.md);
`agenthub update` -> `update-check`; help lists `server install-system|tools`
and `users provision`; ARCHITECTURE.md's stale paths and names fixed.
- cc-config/codex-config writes carry sameOriginGuard (an MCP server or plugin
is code Claude runs later), with a test; `command -v` takes its name as $1.
736 lines
28 KiB
JavaScript
736 lines
28 KiB
JavaScript
/**
|
||
* @file OpenAPI 3.0 path + schema fragments for the dashboard's Run feature
|
||
* (`server/routes/run.js`, mounted at `/api/run`). A run is a tmux pane
|
||
* (`agenthub-lane-<id>` or `agenthub-lane-<id>-<1-9>`) started by
|
||
* `server/lib/pty-run.js` for a lane's AI CLI (`claude`, `codex`, or another
|
||
* AI Tools catalog id) or a plain shell. Its terminal is streamed over the
|
||
* binary `/ws-pty/:runId` WebSocket, not over this REST surface, which only
|
||
* starts, lists, reads and kills panes and reads the persisted run history.
|
||
*
|
||
* Merged into the base spec by `server/openapi-extra.js` -> `createOpenApiSpec()`.
|
||
* Exports exactly `{ tags, schemas, paths }`. Every schema name is prefixed
|
||
* `Run`. The shared error envelope `ErrorResponse` (`{ error: { code, message } }`)
|
||
* is defined in the base spec and only referenced here — never redefined.
|
||
*
|
||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||
*/
|
||
|
||
const tags = [
|
||
{
|
||
name: "Run",
|
||
description:
|
||
"Start, list, read and stop the tmux panes a lane runs its AI CLI (or a plain shell) in; the terminal itself streams over the `/ws-pty/:runId` WebSocket. Also run history, drag-and-drop file handoff and a claude pane's permission mode.",
|
||
},
|
||
];
|
||
|
||
/**
|
||
* Reusable response fragment for the same-origin guard applied to EVERY route
|
||
* in `server/routes/run.js` (router-level `sameOriginGuard`): a browser request
|
||
* whose Origin/Referer is neither a loopback host nor the dashboard's own host
|
||
* is refused before any handler runs. Requests with no Origin (curl) pass.
|
||
*/
|
||
const ebadOrigin403 = {
|
||
description:
|
||
"Cross-origin rejected (EBADORIGIN). Enforced by the router-level same-origin guard on every /api/run route: a browser request whose Origin/Referer is neither a loopback host nor the dashboard's own host is refused. Requests without an Origin header (e.g. curl) are allowed.",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
||
example: {
|
||
error: { code: "EBADORIGIN", message: "cross-origin requests are not allowed" },
|
||
},
|
||
},
|
||
},
|
||
};
|
||
|
||
const errorJson = (description, example) => ({
|
||
description,
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
||
...(example ? { example } : {}),
|
||
},
|
||
},
|
||
});
|
||
|
||
const schemas = {
|
||
RunHandle: {
|
||
type: "object",
|
||
description:
|
||
"A dashboard pane, computed fresh from tmux plus its `dashboard_runs` row (`publicRun` in server/lib/pty-run.js). Returned by GET /api/run (in `items`), POST /api/run (201) and GET /api/run/{id}. There is no in-memory process handle: a pane is live exactly while its tmux session exists.",
|
||
required: ["id", "laneId", "slot", "status", "tool"],
|
||
properties: {
|
||
id: {
|
||
type: "string",
|
||
description:
|
||
"Run id = the tmux session name: `agenthub-lane-<laneId>` for slot 0, `agenthub-lane-<laneId>-<slot>` for slots 1–9.",
|
||
example: "agenthub-lane-5",
|
||
},
|
||
laneId: { type: "integer", description: "Lane (workspace) the pane belongs to.", example: 5 },
|
||
slot: {
|
||
type: "integer",
|
||
minimum: 0,
|
||
maximum: 9,
|
||
description: "Pane slot. Only slot 0 is the lane's run (`run_id`/`status`).",
|
||
example: 0,
|
||
},
|
||
status: {
|
||
type: "string",
|
||
enum: ["running", "gone"],
|
||
description:
|
||
"`running` while the tmux session exists. GET /api/run/{id} answers 404 rather than return a `gone` handle.",
|
||
example: "running",
|
||
},
|
||
tool: {
|
||
type: "string",
|
||
description:
|
||
"AI Tools catalog id the pane runs (`claude`, `codex`, …) or `shell` for a plain terminal tab. Defaults to `claude` when no row is recorded.",
|
||
example: "claude",
|
||
},
|
||
cwd: { type: "string", nullable: true, example: "/home/dev/projects/my-app" },
|
||
model: { type: "string", nullable: true, example: "opus" },
|
||
permissionMode: {
|
||
type: "string",
|
||
nullable: true,
|
||
description: "Permission mode recorded at spawn (claude panes).",
|
||
example: "auto",
|
||
},
|
||
effort: { type: "string", nullable: true, example: "high" },
|
||
resumeSessionId: {
|
||
type: "string",
|
||
nullable: true,
|
||
description: "Session this pane resumed, or null for a fresh session.",
|
||
},
|
||
sessionId: {
|
||
type: "string",
|
||
nullable: true,
|
||
description:
|
||
"Session the pane holds once known (a new claude pane is started with `--session-id`, so it is known at spawn).",
|
||
},
|
||
worktree: {
|
||
type: "boolean",
|
||
description: "The pane works in a git worktree of its own (`claude --worktree`).",
|
||
example: false,
|
||
},
|
||
startedAt: {
|
||
type: "string",
|
||
nullable: true,
|
||
description: "Spawn time as stored in `dashboard_runs.started_at` (ISO-8601).",
|
||
},
|
||
promptPreview: {
|
||
type: "string",
|
||
nullable: true,
|
||
description: "First characters of the initial prompt, or null.",
|
||
},
|
||
},
|
||
},
|
||
|
||
RunListResponse: {
|
||
type: "object",
|
||
description:
|
||
"Every live dashboard pane (tmux sessions named `agenthub-lane-*`). In server mode a developer sees only the panes of their own workspaces.",
|
||
required: ["items"],
|
||
properties: {
|
||
items: { type: "array", items: { $ref: "#/components/schemas/RunHandle" } },
|
||
},
|
||
},
|
||
|
||
RunHistoryItem: {
|
||
type: "object",
|
||
description:
|
||
"A persisted run record from the `dashboard_runs` sqlite table (snake_case DB columns, ISO-8601 timestamps), plus the computed `isLive`. A new run in a pane archives the pane's previous row under `<id>@<started_at>`.",
|
||
required: ["id", "cwd", "status", "started_at", "isLive"],
|
||
properties: {
|
||
id: {
|
||
type: "string",
|
||
description:
|
||
"Run id (the pane's tmux session name; archived rows carry an `@<started_at>` suffix).",
|
||
example: "agenthub-lane-5",
|
||
},
|
||
session_id: {
|
||
type: "string",
|
||
nullable: true,
|
||
description: "Session the pane held, once known.",
|
||
},
|
||
mode: {
|
||
type: "string",
|
||
nullable: true,
|
||
description: "`interactive` for every tmux pane run (older rows may carry a legacy value).",
|
||
example: "interactive",
|
||
},
|
||
cwd: { type: "string", example: "/home/dev/projects/my-app" },
|
||
model: { type: "string", nullable: true, example: "opus" },
|
||
permission_mode: { type: "string", nullable: true, example: "auto" },
|
||
effort: { type: "string", nullable: true, example: "high" },
|
||
resume_session_id: { type: "string", nullable: true },
|
||
prompt_preview: { type: "string", nullable: true },
|
||
status: {
|
||
type: "string",
|
||
description:
|
||
"`running` at spawn, `killed` after DELETE /api/run/{id}, `interrupted` when the pane died under a session (offered by GET /api/run/interrupted), `abandoned` for a replaced or dismissed row. Older rows may carry legacy values.",
|
||
example: "killed",
|
||
},
|
||
exit_code: { type: "integer", nullable: true },
|
||
started_at: { type: "string", format: "date-time" },
|
||
ended_at: { type: "string", format: "date-time", nullable: true },
|
||
lane_id: { type: "integer", nullable: true, example: 5 },
|
||
tool: { type: "string", example: "claude" },
|
||
worktree: {
|
||
type: "integer",
|
||
enum: [0, 1],
|
||
description: "1 when the pane ran in its own `claude --worktree`.",
|
||
},
|
||
proxy: {
|
||
type: "string",
|
||
nullable: true,
|
||
description: "AI Proxy profile name the session ran on, or null (native).",
|
||
},
|
||
account: {
|
||
type: "string",
|
||
nullable: true,
|
||
description: "Pool account the pane was given, or null.",
|
||
},
|
||
isLive: {
|
||
type: "boolean",
|
||
description: "Computed at request time: a live tmux pane with this id exists.",
|
||
example: false,
|
||
},
|
||
},
|
||
},
|
||
|
||
RunHistoryResponse: {
|
||
type: "object",
|
||
description:
|
||
"Persisted run history, most recent first. Empty `items` when the persistence DB is unavailable.",
|
||
required: ["items"],
|
||
properties: {
|
||
items: {
|
||
type: "array",
|
||
items: { $ref: "#/components/schemas/RunHistoryItem" },
|
||
},
|
||
},
|
||
},
|
||
|
||
RunCwdSuggestion: {
|
||
type: "object",
|
||
description:
|
||
"A suggested working directory. Only directories that still exist on disk are returned.",
|
||
required: ["kind", "path", "label"],
|
||
properties: {
|
||
kind: {
|
||
type: "string",
|
||
enum: ["dashboard", "home", "repo", "recent"],
|
||
description:
|
||
"`dashboard`: the dashboard server's own cwd. `home`: $HOME (both omitted for a server-mode developer). `repo`: source repos lanes were provisioned from. `recent`: distinct cwds sessions ran in.",
|
||
example: "recent",
|
||
},
|
||
path: { type: "string", example: "/home/dev/projects/my-app" },
|
||
label: { type: "string", example: "my-app" },
|
||
},
|
||
},
|
||
|
||
RunCwdsResponse: {
|
||
type: "object",
|
||
required: ["items"],
|
||
properties: {
|
||
items: { type: "array", items: { $ref: "#/components/schemas/RunCwdSuggestion" } },
|
||
},
|
||
},
|
||
|
||
RunFilesResponse: {
|
||
type: "object",
|
||
description:
|
||
"File-path autocomplete results for the prompt editor's `@` references — up to 40 paths relative to the given cwd, shortest first.",
|
||
required: ["items"],
|
||
properties: {
|
||
items: {
|
||
type: "array",
|
||
items: { type: "string" },
|
||
description:
|
||
"Paths relative to the resolved cwd (folders end in `/` with `?dirs=1`). Dotfiles (except .env/.gitignore) and heavy build dirs are skipped.",
|
||
example: ["package.json", "src/index.ts", "server/routes/run.js"],
|
||
},
|
||
},
|
||
},
|
||
|
||
RunBinaryResponse: {
|
||
type: "object",
|
||
description:
|
||
"Whether the AI tool's CLI is installed, from a fresh catalog detection (the binary is not invoked).",
|
||
required: ["found", "path"],
|
||
properties: {
|
||
found: { type: "boolean", example: true },
|
||
path: { type: "string", nullable: true, example: "/home/dev/.local/bin/claude" },
|
||
},
|
||
},
|
||
|
||
RunSpawnRequest: {
|
||
type: "object",
|
||
description:
|
||
"Request body for starting the lane's slot-0 pane. SIDE EFFECT: creates (or takes over an idle) tmux session `agenthub-lane-<laneId>` running the lane's AI CLI, and records a `dashboard_runs` row. The tool is always the lane's own (`lanes.tool`); to pick another tool, a slot or a worktree, use POST /api/lanes/{id}/start.",
|
||
required: ["laneId"],
|
||
properties: {
|
||
laneId: { type: "integer", description: "Lane to start the pane for.", example: 5 },
|
||
cwd: {
|
||
type: "string",
|
||
description:
|
||
"Absolute existing directory. Omitted: the dashboard's cwd, or the lane's cwd for a server-mode developer. Must lie inside the caller's own workspaces (EFOLDER).",
|
||
example: "/home/dev/projects/my-app",
|
||
},
|
||
initialPrompt: {
|
||
type: "string",
|
||
description: "First message for the CLI (at most 128 KB, else EBADPROMPT). May be empty.",
|
||
},
|
||
model: { type: "string", nullable: true, example: "opus" },
|
||
permissionMode: {
|
||
type: "string",
|
||
enum: ["auto", "acceptEdits", "default", "plan", "bypassPermissions"],
|
||
default: "auto",
|
||
description: "claude only. An unknown value falls back to `auto`.",
|
||
},
|
||
effort: { type: "string", nullable: true, example: "high" },
|
||
sandbox: {
|
||
type: "string",
|
||
nullable: true,
|
||
description: "codex only, checked against pty-run's fixed allowlist (EBADSANDBOX).",
|
||
},
|
||
approval: {
|
||
type: "string",
|
||
nullable: true,
|
||
description: "codex only, checked against pty-run's fixed allowlist.",
|
||
},
|
||
resumeSessionId: {
|
||
type: "string",
|
||
nullable: true,
|
||
description:
|
||
"Session to resume (EBADSESSION for a malformed id; EUNSUPPORTED for a tool that cannot resume by id).",
|
||
},
|
||
resumeLast: {
|
||
type: "boolean",
|
||
description:
|
||
"Resume the most recent session (codex `resume --last`; another tool's catalog `resume` args). Ignored when resumeSessionId is set.",
|
||
},
|
||
bypass: {
|
||
type: "boolean",
|
||
description:
|
||
"Only when `true`: append the tool catalog file's bypass args for this run (never remembered by the server).",
|
||
},
|
||
proxy: {
|
||
type: "string",
|
||
nullable: true,
|
||
description:
|
||
"AI Proxy profile name (claude/codex only; EBADPROXY / EUNSUPPORTED). Omitted on a resume: the proxy the session last ran on. `router` is the accepted pre-rename alias.",
|
||
},
|
||
},
|
||
},
|
||
|
||
RunKillResponse: {
|
||
type: "object",
|
||
required: ["ok"],
|
||
properties: {
|
||
ok: { type: "boolean", enum: [true], example: true },
|
||
archived: {
|
||
type: "string",
|
||
nullable: true,
|
||
description:
|
||
"Only with `?archive=1`: the id of the session the pane held, now archived, or null when the pane had no known session (a shell).",
|
||
},
|
||
},
|
||
},
|
||
};
|
||
|
||
const paths = {
|
||
"/api/run": {
|
||
get: {
|
||
tags: ["Run"],
|
||
summary: "List live panes",
|
||
description:
|
||
"Every live dashboard pane, computed from tmux on each call. Server mode: a developer sees only the panes of their own workspaces. Read-only. The same-origin guard applies.",
|
||
operationId: "runList",
|
||
responses: {
|
||
200: {
|
||
description: "Live panes",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/RunListResponse" },
|
||
example: {
|
||
items: [
|
||
{
|
||
id: "agenthub-lane-5",
|
||
laneId: 5,
|
||
slot: 0,
|
||
status: "running",
|
||
tool: "claude",
|
||
cwd: "/home/dev/projects/my-app",
|
||
model: "opus",
|
||
permissionMode: "auto",
|
||
effort: null,
|
||
resumeSessionId: null,
|
||
sessionId: "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
|
||
worktree: false,
|
||
startedAt: "2026-10-22T08:00:00.000Z",
|
||
promptPreview: "Refactor the auth module",
|
||
},
|
||
],
|
||
},
|
||
},
|
||
},
|
||
},
|
||
403: ebadOrigin403,
|
||
},
|
||
},
|
||
post: {
|
||
tags: ["Run"],
|
||
summary: "Start a lane's slot-0 pane",
|
||
description:
|
||
"Starts the lane's AI CLI (its own `lanes.tool`) in tmux session `agenthub-lane-<laneId>` via `spawnRun`. Argv comes from the tool's catalog file / launcher, never from the request; workspace instructions and extra folders are added by `launchExtras`. The terminal is reached over `/ws-pty/:runId`; nothing is streamed over the JSON WebSocket. A `dashboard_runs` row is recorded. Same-origin guard; `cwd` must be inside the caller's own workspaces.",
|
||
operationId: "runSpawn",
|
||
requestBody: {
|
||
required: true,
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/RunSpawnRequest" },
|
||
example: { laneId: 5, initialPrompt: "Run the tests", permissionMode: "acceptEdits" },
|
||
},
|
||
},
|
||
},
|
||
responses: {
|
||
201: {
|
||
description: "Pane started",
|
||
content: { "application/json": { schema: { $ref: "#/components/schemas/RunHandle" } } },
|
||
},
|
||
400: errorJson(
|
||
"Invalid input: EBADLANE (laneId missing), EBADCWD, EBADPROMPT, ENOTOOL, EBADMODEL, EBADEFFORT, EBADSESSION, EBADSANDBOX, EBADPROXY, EUNSUPPORTED, or another E-code from spawnRun."
|
||
),
|
||
403: {
|
||
description:
|
||
"EBADORIGIN (cross-origin) or EFOLDER (cwd is not one of the caller's workspaces).",
|
||
content: {
|
||
"application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } },
|
||
},
|
||
},
|
||
409: errorJson(
|
||
"Conflict with live state: ERUNLIVE (the pane is busy), ENOTSHARED, EMEMBERGONE, EOWNERFOLDER or ECWDACCOUNT."
|
||
),
|
||
503: errorJson("tmux is not installed (ETMUXMISSING)."),
|
||
500: errorJson("Unexpected failure (EINTERNAL)."),
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/run/history": {
|
||
get: {
|
||
tags: ["Run"],
|
||
summary: "List persisted run history",
|
||
description:
|
||
"Returns the persistent history of panes started via the dashboard, from the `dashboard_runs` sqlite table (newest first). Unlike GET /api/run it keeps runs whose pane is gone, so past sessions stay visible and resumable. Each item is cross-referenced against live tmux panes to set `isLive`. Server mode: a developer without `?laneId` gets the newest runs of their own workspaces only. Returns `{ items: [] }` if the persistence DB is unavailable. Read-only. The same-origin guard applies.",
|
||
operationId: "runHistory",
|
||
parameters: [
|
||
{
|
||
name: "limit",
|
||
in: "query",
|
||
required: false,
|
||
schema: { type: "integer", minimum: 1, maximum: 500, default: 50 },
|
||
description: "Maximum number of history rows to return (default 50, clamped to 1–500).",
|
||
example: 50,
|
||
},
|
||
{
|
||
name: "laneId",
|
||
in: "query",
|
||
required: false,
|
||
schema: { type: "integer" },
|
||
description: "Return only the runs of this lane. Omit for every run.",
|
||
example: 5,
|
||
},
|
||
{
|
||
name: "tool",
|
||
in: "query",
|
||
required: false,
|
||
schema: { type: "string" },
|
||
description:
|
||
"Return only the runs of this AI Tools id — `shell` for a plain terminal tab (a closed one's row is dropped on the next run-heal beat).",
|
||
example: "shell",
|
||
},
|
||
],
|
||
responses: {
|
||
200: {
|
||
description: "Run history, most recent first",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/RunHistoryResponse" },
|
||
example: {
|
||
items: [
|
||
{
|
||
id: "agenthub-lane-5",
|
||
session_id: "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
|
||
mode: "interactive",
|
||
cwd: "/home/dev/projects/my-app",
|
||
model: "opus",
|
||
permission_mode: "acceptEdits",
|
||
effort: "high",
|
||
resume_session_id: null,
|
||
prompt_preview: "Refactor the auth module and add tests.",
|
||
status: "killed",
|
||
exit_code: null,
|
||
started_at: "2026-10-22T08:00:00.000Z",
|
||
ended_at: "2026-10-22T09:12:42.500Z",
|
||
lane_id: 5,
|
||
tool: "claude",
|
||
worktree: 0,
|
||
proxy: null,
|
||
account: null,
|
||
isLive: false,
|
||
},
|
||
],
|
||
},
|
||
},
|
||
},
|
||
},
|
||
403: ebadOrigin403,
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/run/history/{id}": {
|
||
delete: {
|
||
tags: ["Run"],
|
||
summary: "Forget a closed Terminal tab",
|
||
description:
|
||
"Deletes one `dashboard_runs` row whose tool is `shell` — a closed Terminal tab, which has no session (the run-heal beat drops these rows anyway; this removes one at once). Rows of any other tool are never deleted (404). The loopback same-origin guard applies.",
|
||
operationId: "runHistoryForget",
|
||
parameters: [
|
||
{
|
||
name: "id",
|
||
in: "path",
|
||
required: true,
|
||
schema: { type: "string" },
|
||
description: "The history row's id (RunHistoryItem.id).",
|
||
example: "agenthub-lane-5-2@2026-10-06T08:00:00.000Z",
|
||
},
|
||
],
|
||
responses: {
|
||
200: {
|
||
description: "Removed",
|
||
content: { "application/json": { example: { removed: "agenthub-lane-5-2" } } },
|
||
},
|
||
403: ebadOrigin403,
|
||
404: {
|
||
description: "No closed Terminal tab with that id (ENOTFOUND)",
|
||
content: {
|
||
"application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } },
|
||
},
|
||
},
|
||
409: {
|
||
description: "That terminal is still open (ERUNLIVE)",
|
||
content: {
|
||
"application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/run/cwds": {
|
||
get: {
|
||
tags: ["Run"],
|
||
summary: "Suggest working directories",
|
||
description:
|
||
"Suggests plausible working directories: the dashboard server's cwd and $HOME (not for a server-mode developer), source repos lanes were provisioned from (up to 20), then distinct recent session cwds (up to 30) — the last two scoped to the caller's own rows in server mode. Only directories that still exist are returned; the DB lookups are best-effort. Read-only. The same-origin guard applies.",
|
||
operationId: "runCwds",
|
||
parameters: [
|
||
{
|
||
name: "q",
|
||
in: "query",
|
||
required: false,
|
||
schema: { type: "string" },
|
||
description: "Optional case-insensitive substring filter (also applied client-side).",
|
||
example: "my-app",
|
||
},
|
||
],
|
||
responses: {
|
||
200: {
|
||
description: "Working-directory suggestions",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/RunCwdsResponse" },
|
||
example: {
|
||
items: [
|
||
{
|
||
kind: "dashboard",
|
||
path: "/Users/dev/AgentHub",
|
||
label: "Dashboard server",
|
||
},
|
||
{ kind: "home", path: "/Users/dev", label: "Home" },
|
||
{ kind: "repo", path: "/Users/dev/src/project", label: "project" },
|
||
{ kind: "recent", path: "/Users/dev/projects/my-app", label: "my-app" },
|
||
],
|
||
},
|
||
},
|
||
},
|
||
},
|
||
403: ebadOrigin403,
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/run/files": {
|
||
get: {
|
||
tags: ["Run"],
|
||
summary: "Autocomplete files within a cwd",
|
||
description:
|
||
"Walks the given `cwd` (which must lie inside the caller's own workspaces, else 403 EFOLDER) and returns up to 40 paths (relative to that cwd, shortest first) for the prompt editor's `@` references. The cwd is validated via the same sanitiser as spawning (must be an existing absolute directory). Dotfiles (except .env/.gitignore) and heavy build dirs (node_modules, .git, dist, build, out, .next, coverage, target, .venv, __pycache__, etc.) are skipped; the walk is bounded (≤5000 entries visited). Read-only — no process is spawned. The loopback same-origin guard applies.",
|
||
operationId: "runFiles",
|
||
parameters: [
|
||
{
|
||
name: "cwd",
|
||
in: "query",
|
||
required: false,
|
||
schema: { type: "string" },
|
||
description:
|
||
"Absolute directory to walk. Must exist; defaults to the dashboard server's cwd when omitted. Invalid values yield 400 EBADCWD.",
|
||
example: "/Users/dev/projects/my-app",
|
||
},
|
||
{
|
||
name: "q",
|
||
in: "query",
|
||
required: false,
|
||
schema: { type: "string" },
|
||
description: "Optional case-insensitive substring filter applied to the relative path.",
|
||
example: "route",
|
||
},
|
||
{
|
||
name: "dirs",
|
||
in: "query",
|
||
required: false,
|
||
schema: { type: "string", enum: ["1"] },
|
||
description:
|
||
"`1` also returns matching folders (as `dir/`), for the composer's Add from project.",
|
||
},
|
||
],
|
||
responses: {
|
||
200: {
|
||
description: "Matching file paths relative to cwd",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/RunFilesResponse" },
|
||
example: { items: ["package.json", "src/index.ts", "server/routes/run.js"] },
|
||
},
|
||
},
|
||
},
|
||
400: {
|
||
description: "Invalid cwd (EBADCWD): not a string, not absolute, or does not exist.",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
||
example: { error: { code: "EBADCWD", message: "cwd does not exist: /no/such/dir" } },
|
||
},
|
||
},
|
||
},
|
||
403: ebadOrigin403,
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/run/binary": {
|
||
get: {
|
||
tags: ["Run"],
|
||
summary: "Check whether an AI tool's CLI is installed",
|
||
description:
|
||
"Detects the CLI of an AI Tools catalog id (fresh, not the catalog cache, so a CLI installed while the dashboard is up clears the missing-binary banner). The query only picks a catalog entry — it never names a command. The binary is not invoked. The same-origin guard applies.",
|
||
operationId: "runBinary",
|
||
parameters: [
|
||
{
|
||
name: "tool",
|
||
in: "query",
|
||
required: false,
|
||
schema: { type: "string", default: "claude" },
|
||
description: "AI Tools catalog id. Unknown → 400 EBADTOOL.",
|
||
},
|
||
],
|
||
responses: {
|
||
200: {
|
||
description: "Binary resolution result",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/RunBinaryResponse" },
|
||
example: { found: true, path: "/usr/local/bin/claude" },
|
||
},
|
||
},
|
||
},
|
||
400: errorJson("Unknown tool id (EBADTOOL)."),
|
||
403: ebadOrigin403,
|
||
},
|
||
},
|
||
},
|
||
|
||
"/api/run/{id}": {
|
||
get: {
|
||
tags: ["Run"],
|
||
summary: "Get a single pane",
|
||
description:
|
||
"Returns one live pane, computed from tmux and its `dashboard_runs` row. A pane whose tmux session is gone, an id that is not a dashboard pane, or one the caller may not see (server mode) answers 404 — use GET /api/run/history for past runs. Read-only. The same-origin guard applies.",
|
||
operationId: "runGet",
|
||
parameters: [
|
||
{
|
||
name: "id",
|
||
in: "path",
|
||
required: true,
|
||
schema: { type: "string" },
|
||
description: "Run id (RunHandle.id).",
|
||
example: "agenthub-lane-5",
|
||
},
|
||
],
|
||
responses: {
|
||
200: {
|
||
description: "The pane",
|
||
content: { "application/json": { schema: { $ref: "#/components/schemas/RunHandle" } } },
|
||
},
|
||
403: ebadOrigin403,
|
||
404: errorJson("No live pane with this id (ENOTFOUND).", {
|
||
error: { code: "ENOTFOUND", message: "run not found" },
|
||
}),
|
||
},
|
||
},
|
||
delete: {
|
||
tags: ["Run"],
|
||
summary: "Kill a pane",
|
||
description:
|
||
"Kills the pane's tmux session and marks its `dashboard_runs` row `killed`. Only a dashboard lane pane (`agenthub-lane-<id>[-<slot>]`) can be killed — any other tmux session name answers 404. With `?archive=1` (the UI's Stop) the session the pane held is also archived (`sessions.archived_at`, broadcast as `session_updated`); without it (Sleep, the CLI) the session stays listed to be resumed. The same-origin guard applies.",
|
||
operationId: "runKill",
|
||
parameters: [
|
||
{
|
||
name: "id",
|
||
in: "path",
|
||
required: true,
|
||
schema: { type: "string" },
|
||
description: "Run id (RunHandle.id).",
|
||
example: "agenthub-lane-5",
|
||
},
|
||
{
|
||
name: "archive",
|
||
in: "query",
|
||
required: false,
|
||
schema: { type: "string", enum: ["1"] },
|
||
description: "`1` also archives the session the pane held.",
|
||
},
|
||
],
|
||
responses: {
|
||
200: {
|
||
description: "Killed",
|
||
content: {
|
||
"application/json": {
|
||
schema: { $ref: "#/components/schemas/RunKillResponse" },
|
||
example: { ok: true },
|
||
},
|
||
},
|
||
},
|
||
403: ebadOrigin403,
|
||
404: errorJson("No live dashboard pane with this id (ENOTFOUND).", {
|
||
error: { code: "ENOTFOUND", message: "run not found" },
|
||
}),
|
||
},
|
||
},
|
||
},
|
||
};
|
||
|
||
module.exports = { tags, schemas, paths };
|