feat(run): replace RunHandle/RunStartArgs types and api.run for the tmux backend
This commit is contained in:
+32
-145
@@ -1474,110 +1474,32 @@ export const api = {
|
|||||||
/** Spawn/manage headless or conversational `claude` CLI child processes
|
/** Spawn/manage headless or conversational `claude` CLI child processes
|
||||||
* launched from the dashboard's Run page, and stream their output. */
|
* launched from the dashboard's Run page, and stream their output. */
|
||||||
run: {
|
run: {
|
||||||
/**
|
/** GET /api/run - lanes with a live tmux-backed run, computed fresh from tmux state. */
|
||||||
* GET /api/run - currently tracked runs (in-memory handles) plus
|
|
||||||
* concurrency limits.
|
|
||||||
* @returns {@link RunListResponse} — live handles + `maxConcurrent`/`activeCount`.
|
|
||||||
*/
|
|
||||||
list: () => request<RunListResponse>("/run"),
|
list: () => request<RunListResponse>("/run"),
|
||||||
/**
|
/** GET /api/run/history - persisted run history from `dashboard_runs`. */
|
||||||
* GET /api/run/history - persisted run history from the `dashboard_runs`
|
|
||||||
* table, including runs whose in-memory handle has since been reaped.
|
|
||||||
* Optionally filter by lane.
|
|
||||||
*
|
|
||||||
* `limit` defaults to 50 when the caller omits it and is always sent as a
|
|
||||||
* query param.
|
|
||||||
*
|
|
||||||
* @param limit Max history rows to return (default 50).
|
|
||||||
* @param options Optional filters like laneId.
|
|
||||||
* @returns `{ items }` — {@link DashboardRunHistoryItem} rows, newest-first.
|
|
||||||
*/
|
|
||||||
history: (limit = 50, options?: { laneId?: number }) => {
|
history: (limit = 50, options?: { laneId?: number }) => {
|
||||||
const qs = new URLSearchParams({ limit: String(limit) });
|
const qs = new URLSearchParams({ limit: String(limit) });
|
||||||
if (options?.laneId !== undefined) qs.set("laneId", String(options.laneId));
|
if (options?.laneId !== undefined) qs.set("laneId", String(options.laneId));
|
||||||
return request<{ items: DashboardRunHistoryItem[] }>(`/run/history?${qs.toString()}`);
|
return request<{ items: DashboardRunHistoryItem[] }>(`/run/history?${qs.toString()}`);
|
||||||
},
|
},
|
||||||
/**
|
/** GET /api/run/binary - whether `claude` was found on PATH. */
|
||||||
* GET /api/run/binary - whether a `claude` executable was found on PATH.
|
|
||||||
*
|
|
||||||
* Lets the Run page disable/enable the "start" affordance and show where the
|
|
||||||
* CLI resolved from (or that it's missing).
|
|
||||||
*
|
|
||||||
* @returns `{ found, path }` — whether a binary was located and its path.
|
|
||||||
*/
|
|
||||||
binary: () => request<{ found: boolean; path: string | null }>("/run/binary"),
|
binary: () => request<{ found: boolean; path: string | null }>("/run/binary"),
|
||||||
/**
|
/** GET /api/run/tmux - whether the `tmux` binary was found on PATH. */
|
||||||
* GET /api/run/cwds - suggested working directories for the cwd picker.
|
tmuxAvailable: () => request<{ available: boolean }>("/run/tmux"),
|
||||||
* @returns `{ items }` — {@link CwdSuggestion} entries (dashboard/home/recent).
|
/** GET /api/run/cwds - suggested working directories for the cwd picker. */
|
||||||
*/
|
|
||||||
cwds: () => request<{ items: CwdSuggestion[] }>("/run/cwds"),
|
cwds: () => request<{ items: CwdSuggestion[] }>("/run/cwds"),
|
||||||
/**
|
/** GET /api/run/files - path-completion suggestions under `cwd`. */
|
||||||
* GET /api/run/files - path-completion suggestions under `cwd`, filtered
|
|
||||||
* by an optional query fragment `q`.
|
|
||||||
*
|
|
||||||
* Backs the file/@-mention autocomplete when composing a run prompt: `cwd`
|
|
||||||
* is always sent; `q` is appended only when non-empty to narrow matches.
|
|
||||||
*
|
|
||||||
* @param cwd The directory to complete paths within.
|
|
||||||
* @param q Optional partial fragment to filter suggestions by.
|
|
||||||
* @returns `{ items }` — matching path strings under `cwd`.
|
|
||||||
*/
|
|
||||||
files: (cwd: string, q?: string) => {
|
files: (cwd: string, q?: string) => {
|
||||||
const qs = new URLSearchParams({ cwd });
|
const qs = new URLSearchParams({ cwd });
|
||||||
if (q) qs.set("q", q);
|
if (q) qs.set("q", q);
|
||||||
return request<{ items: string[] }>(`/run/files?${qs.toString()}`);
|
return request<{ items: string[] }>(`/run/files?${qs.toString()}`);
|
||||||
},
|
},
|
||||||
/**
|
/** POST /api/run - start (or adopt, if already live) a lane's terminal run. */
|
||||||
* POST /api/run - spawn a new `claude` child process.
|
|
||||||
*
|
|
||||||
* Sends {@link RunStartArgs} (prompt, mode, and optional cwd/model/
|
|
||||||
* permission-mode/resume/effort). The server spawns the CLI and returns the
|
|
||||||
* initial {@link RunHandle}; subsequent output is streamed over the
|
|
||||||
* `run_stream` WebSocket message rather than this response.
|
|
||||||
*
|
|
||||||
* @param args The spawn parameters.
|
|
||||||
* @returns {@link RunHandle} — the freshly created run's handle.
|
|
||||||
*/
|
|
||||||
start: (args: RunStartArgs) =>
|
start: (args: RunStartArgs) =>
|
||||||
request<RunHandle>("/run", { method: "POST", body: JSON.stringify(args) }),
|
request<RunHandle>("/run", { method: "POST", body: JSON.stringify(args) }),
|
||||||
/**
|
/** GET /api/run/:id - one run's current handle. */
|
||||||
* GET /api/run/:id - one run's current handle; pass `envelopes: true` to
|
get: (id: string) => request<RunHandle>(`/run/${encodeURIComponent(id)}`),
|
||||||
* also include its buffered stream-json envelopes (for a page refresh
|
/** DELETE /api/run/:id - kill the tmux session. */
|
||||||
* mid-run, since the WS `run_stream` history isn't otherwise replayed).
|
|
||||||
*
|
|
||||||
* The `envelopes` flag is translated to `?envelopes=1`. Use it when
|
|
||||||
* re-hydrating the Run page after a reload: the WebSocket only pushes *new*
|
|
||||||
* envelopes, so the buffered ones must be pulled once to backfill the view.
|
|
||||||
*
|
|
||||||
* @param id The run id.
|
|
||||||
* @param opts Optional `{ envelopes }` — include buffered stream-json envelopes.
|
|
||||||
* @returns {@link RunHandle} — the run's handle (with `envelopes` when requested).
|
|
||||||
*/
|
|
||||||
get: (id: string, opts?: { envelopes?: boolean }) =>
|
|
||||||
request<RunHandle>(`/run/${encodeURIComponent(id)}${opts?.envelopes ? "?envelopes=1" : ""}`),
|
|
||||||
/**
|
|
||||||
* POST /api/run/:id/message - write `text` to the run's stdin (conversation
|
|
||||||
* mode only); acked via the `run_input_ack` WS message.
|
|
||||||
*
|
|
||||||
* Only meaningful for a run started in "conversation" mode (stdin left
|
|
||||||
* open). The HTTP response returns just the `messageId`; the actual
|
|
||||||
* delivery/echo is confirmed asynchronously over the WebSocket.
|
|
||||||
*
|
|
||||||
* @param id The run id to send input to.
|
|
||||||
* @param text The user's follow-up message written to the CLI's stdin.
|
|
||||||
* @returns `{ messageId }` — id correlating this input with its `run_input_ack`.
|
|
||||||
*/
|
|
||||||
send: (id: string, text: string) =>
|
|
||||||
request<{ messageId: string }>(`/run/${encodeURIComponent(id)}/message`, {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({ text }),
|
|
||||||
}),
|
|
||||||
/**
|
|
||||||
* DELETE /api/run/:id - forcibly terminate a running process.
|
|
||||||
*
|
|
||||||
* @param id The run id to kill.
|
|
||||||
* @returns `{ ok: true }` — acknowledgement that termination was requested.
|
|
||||||
*/
|
|
||||||
kill: (id: string) =>
|
kill: (id: string) =>
|
||||||
request<{ ok: true }>(`/run/${encodeURIComponent(id)}`, { method: "DELETE" }),
|
request<{ ok: true }>(`/run/${encodeURIComponent(id)}`, { method: "DELETE" }),
|
||||||
},
|
},
|
||||||
@@ -2506,78 +2428,49 @@ export interface CcHookScripts {
|
|||||||
// mirror the CLI's own vocabulary so the dashboard can drive the CLI faithfully.
|
// mirror the CLI's own vocabulary so the dashboard can drive the CLI faithfully.
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** "headless" runs to completion unattended and streams only output;
|
|
||||||
* "conversation" keeps stdin open so the user can send follow-up messages. */
|
|
||||||
export type RunMode = "headless" | "conversation";
|
|
||||||
/** Lifecycle of a spawned `claude` process, mirrored in `RunHandle.status`
|
|
||||||
* and `RunStatusPayload.status`. "abandoned" is applied by server cleanup
|
|
||||||
* when a handle is reaped without a clean exit ever being observed. */
|
|
||||||
export type RunStatus = "spawning" | "running" | "completed" | "error" | "killed" | "abandoned";
|
|
||||||
/** Maps 1:1 to the `claude --permission-mode` CLI flag. */
|
/** Maps 1:1 to the `claude --permission-mode` CLI flag. */
|
||||||
export type PermissionMode = "acceptEdits" | "default" | "plan" | "bypassPermissions";
|
export type PermissionMode = "acceptEdits" | "default" | "plan" | "bypassPermissions";
|
||||||
/** Maps 1:1 to the `claude --effort` CLI flag; "" omits the flag (model default). */
|
/** Maps 1:1 to the `claude --effort` CLI flag; "" omits the flag (model default). */
|
||||||
export type EffortLevel = "" | "low" | "medium" | "high" | "xhigh" | "max";
|
export type EffortLevel = "" | "low" | "medium" | "high" | "xhigh" | "max";
|
||||||
|
/** "running" — a live tmux session exists; "gone" — it doesn't (killed,
|
||||||
|
* crashed, claude exited and closed the pane). Computed fresh from tmux
|
||||||
|
* state on every read, never cached. */
|
||||||
|
export type RunStatus = "running" | "gone";
|
||||||
|
|
||||||
/** Body for POST /api/run - parameters for spawning a new `claude` process. */
|
/** Body for POST /api/run - parameters for starting a lane's terminal run. */
|
||||||
export interface RunStartArgs {
|
export interface RunStartArgs {
|
||||||
/** Initial prompt/task text passed to the CLI. */
|
laneId: number;
|
||||||
prompt: string;
|
|
||||||
mode: RunMode;
|
|
||||||
/** Working directory to launch in; server default applies if omitted. */
|
|
||||||
cwd?: string;
|
cwd?: string;
|
||||||
/** `--model` value; omitted inherits the CLI's own default (settings.json). */
|
|
||||||
model?: string;
|
model?: string;
|
||||||
permissionMode?: PermissionMode;
|
permissionMode?: PermissionMode;
|
||||||
/** Resume an existing Claude Code session id (`--resume`) instead of starting fresh. */
|
/** Resume an existing Claude Code session id (`--resume`) instead of starting fresh. */
|
||||||
resumeSessionId?: string;
|
resumeSessionId?: string;
|
||||||
effort?: EffortLevel;
|
effort?: EffortLevel;
|
||||||
|
/** Sent as `claude`'s first positional message once the pane boots; omit
|
||||||
|
* to just open the pane and let the user type. */
|
||||||
|
initialPrompt?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** In-memory (or freshly-fetched) handle for one spawned `claude` process,
|
/** A lane's tmux-backed terminal run — one per lane, id is the tmux session
|
||||||
* from POST/GET /api/run - the live counterpart to {@link DashboardRunHistoryItem}.
|
* name (`ccam-lane-<laneId>`). */
|
||||||
* Where {@link DashboardRunHistoryItem} is the persisted DB row (snake_case,
|
|
||||||
* survives handle reaping), this is the richer live handle (camelCase, carries
|
|
||||||
* argv/tails/envelope counters) that only exists while the server tracks it. */
|
|
||||||
export interface RunHandle {
|
export interface RunHandle {
|
||||||
id: string;
|
id: string;
|
||||||
/** OS process id; null before the process has actually spawned. */
|
laneId: number | null;
|
||||||
pid: number | null;
|
|
||||||
mode: RunMode;
|
|
||||||
cwd: string;
|
|
||||||
model: string | null;
|
|
||||||
permissionMode: PermissionMode;
|
|
||||||
effort: EffortLevel | null;
|
|
||||||
prompt: string;
|
|
||||||
/** Full argv the server invoked the CLI with, for debugging. */
|
|
||||||
argv: string[];
|
|
||||||
resumeSessionId: string | null;
|
|
||||||
status: RunStatus;
|
status: RunStatus;
|
||||||
/** Epoch-ms timestamp the process was spawned. */
|
cwd: string | null;
|
||||||
startedAt: number;
|
model: string | null;
|
||||||
/** Epoch-ms timestamp the process exited; null while still running. */
|
permissionMode: PermissionMode | null;
|
||||||
endedAt: number | null;
|
effort: EffortLevel | null;
|
||||||
exitCode: number | null;
|
resumeSessionId: string | null;
|
||||||
/** POSIX signal that killed the process (e.g. "SIGTERM"); null otherwise. */
|
/** Claude Code session id this run created/resumed, once known. */
|
||||||
signal: string | null;
|
|
||||||
error: string | null;
|
|
||||||
/** Claude Code session id the run created/resumed, once known. */
|
|
||||||
sessionId: string | null;
|
sessionId: string | null;
|
||||||
/** Count of stream-json envelopes emitted so far. */
|
/** ISO timestamp the tmux session was created. */
|
||||||
envelopeCount: number;
|
startedAt: string | null;
|
||||||
/** Last chunk of captured stdout, for a quick inline preview. */
|
|
||||||
stdoutTail: string;
|
|
||||||
/** Last chunk of captured stderr, for a quick inline preview. */
|
|
||||||
stderrTail: string;
|
|
||||||
envelopes?: unknown[]; // present when fetched with ?envelopes=1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Response shape of GET /api/run. */
|
/** Response shape of GET /api/run. */
|
||||||
export interface RunListResponse {
|
export interface RunListResponse {
|
||||||
items: RunHandle[];
|
items: RunHandle[];
|
||||||
/** Server-configured cap on simultaneously running processes. */
|
|
||||||
maxConcurrent: number;
|
|
||||||
/** Count of runs currently in "spawning"/"running" state. */
|
|
||||||
activeCount: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2591,23 +2484,17 @@ export interface RunListResponse {
|
|||||||
*/
|
*/
|
||||||
export interface DashboardRunHistoryItem {
|
export interface DashboardRunHistoryItem {
|
||||||
id: string;
|
id: string;
|
||||||
/** Claude Code session id the run created/resumed; null if never captured. */
|
|
||||||
session_id: string | null;
|
session_id: string | null;
|
||||||
mode: RunMode;
|
|
||||||
cwd: string;
|
cwd: string;
|
||||||
model: string | null;
|
model: string | null;
|
||||||
permission_mode: PermissionMode | null;
|
permission_mode: PermissionMode | null;
|
||||||
effort: EffortLevel | null;
|
effort: EffortLevel | null;
|
||||||
resume_session_id: string | null;
|
resume_session_id: string | null;
|
||||||
/** Truncated leading excerpt of the original prompt, for the history list. */
|
|
||||||
prompt_preview: string | null;
|
prompt_preview: string | null;
|
||||||
status: RunStatus;
|
status: "running" | "killed" | "abandoned";
|
||||||
exit_code: number | null;
|
exit_code: number | null;
|
||||||
started_at: string;
|
started_at: string;
|
||||||
ended_at: string | null;
|
ended_at: string | null;
|
||||||
/** True when an in-memory {@link RunHandle} for this row still exists (so
|
|
||||||
* the UI can offer live actions like "send message"/"kill"); false once
|
|
||||||
* the handle has been reaped and only the DB row remains. */
|
|
||||||
isLive: boolean;
|
isLive: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-43
@@ -1277,52 +1277,19 @@ export interface UpdateStatusPayload {
|
|||||||
fetch_error?: string;
|
fetch_error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ───── Interactive run streaming ─────
|
// ───── Terminal run status ─────
|
||||||
// Payloads for the "run a `claude` process from the dashboard" feature. A run is
|
// A lane's terminal run is a tmux session; its own live/dead state is polled
|
||||||
// started via POST /api/run and identified by a `RunHandle` id; the server then
|
// via GET /api/run, not pushed over the WS envelope path. This payload only
|
||||||
// streams stdout envelopes, status transitions, and stdin acks back over the WS.
|
// covers the one thing worth pushing live: a run ending (killed or the pane
|
||||||
|
// process exiting) so an open Workspace tab can update its badge/switcher
|
||||||
|
// without polling.
|
||||||
|
|
||||||
/** Payload for the `run_stream` WebSocket message: one streamed JSON envelope
|
/** Payload for the `run_status` WebSocket message. */
|
||||||
* from a headless/conversation `claude` process started via POST /api/run. */
|
|
||||||
export interface RunStreamPayload {
|
|
||||||
/** Id of the `RunHandle` this envelope belongs to. Lets the UI route the chunk
|
|
||||||
* to the right run panel when several runs stream at once. */
|
|
||||||
id: string;
|
|
||||||
/** Raw stream-json envelope emitted by the Claude Code CLI (assistant text
|
|
||||||
* deltas, tool_use/tool_result blocks, etc.) - shape varies by event type.
|
|
||||||
* Typed as `unknown` because it's forwarded verbatim and narrowed at render. */
|
|
||||||
envelope: unknown;
|
|
||||||
}
|
|
||||||
/** Payload for the `run_status` WebSocket message: a lifecycle transition for
|
|
||||||
* a run started via POST /api/run (mirrors `RunHandle.status`). */
|
|
||||||
export interface RunStatusPayload {
|
export interface RunStatusPayload {
|
||||||
/** Id of the `RunHandle` whose status changed. */
|
/** The run id (tmux session name, `ccam-lane-<laneId>`). */
|
||||||
id: string;
|
id: string;
|
||||||
/** New run lifecycle state; terminal states are "completed"/"error"/"killed".
|
status: "running" | "gone";
|
||||||
* "spawning" → the child is being started; "running" → streaming output;
|
/** Epoch-ms timestamp of this transition. */
|
||||||
* "killed" → the run was cancelled by the user. */
|
|
||||||
status: "spawning" | "running" | "completed" | "error" | "killed";
|
|
||||||
/** Epoch-ms timestamp of this status transition (NOT an ISO string, unlike
|
|
||||||
* most timestamps in this file). */
|
|
||||||
at: number;
|
|
||||||
/** Process exit code; present once status reaches "completed"/"error". 0 means
|
|
||||||
* a clean exit. */
|
|
||||||
exitCode?: number;
|
|
||||||
/** Claude Code session id resumed/created by this run, once known. Lets the UI
|
|
||||||
* link a run to the {@link Session} it produced. */
|
|
||||||
sessionId?: string | null;
|
|
||||||
/** Failure message; present when status is "error". Surfaced in the run panel. */
|
|
||||||
error?: string;
|
|
||||||
}
|
|
||||||
/** Payload for the `run_input_ack` WebSocket message: confirms a message sent
|
|
||||||
* via POST /api/run/:id/message was written to the child process's stdin. */
|
|
||||||
export interface RunInputAckPayload {
|
|
||||||
/** Id of the `RunHandle` the input was delivered to. */
|
|
||||||
id: string;
|
|
||||||
/** Echoes the id returned by the `send` call this acks, so the UI can clear
|
|
||||||
* the matching "sending…" pending state. */
|
|
||||||
messageId: string;
|
|
||||||
/** Epoch-ms timestamp the input was delivered (not an ISO string). */
|
|
||||||
at: number;
|
at: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user