import type { Cursor, IsoTime, NodeIdDTO, NodeStatusDTO } from './common.js'; import type { NodeFaultDTO, NodeSummaryDTO } from './nodes.js'; /** `GET /v1/nodes` + `GET /v1/status` composed for the dashboard view. */ export interface DashboardQuery { /** Restrict to the subtree under this node. */ under?: NodeIdDTO; } export interface DashboardDTO { nodes: NodeSummaryDTO[]; /** Node counts keyed by status. */ counts: Partial>; generated_at: IsoTime; } /** A node needing attention (tickets across the canvas). Projected from the * `TicketEntry` reader — one entry per node with open human tickets. */ export interface AttentionItemDTO { node_id: NodeIdDTO; name: string; cwd: string; /** Number of open tickets on this node. */ count: number; /** Open ticket titles for this node, newest first — one per counted ticket. */ subjects: string[]; } /** `GET /v1/canvas/attention` result. */ export interface AttentionDTO { items: AttentionItemDTO[]; } /** `POST /v1/canvas/attention/counts` request. Restrict the synchronous inbox * scan to the nodes a viewer is actually rendering instead of enriching the * entire canvas roster. */ export interface AttentionCountsRequest { node_ids: NodeIdDTO[]; } /** `POST /v1/canvas/attention/counts` result, keyed by requested node id. */ export interface AttentionCountsDTO { counts: Record; } /** `POST /v1/canvas/history/search` body — the ranked/filtered search over the * per-cwd episodic corpus, backing `crtr canvas history search` (optional * query: ranked when present, recency browse when omitted). The CLI parses + * validates flags (`--type` set, `--since`/`--until` → epoch ms) and sends * this normalized query; the server runs `buildCorpus` + ranking + pagination * (closures like `loadBody` cannot cross the boundary, so the whole search * executes server-side). Sent as POST — the query carries arrays and * free-text that do not serialize cleanly as GET params. */ export interface HistorySearchQuery { /** Whitespace-separated terms, ranked; omit to browse by recency. */ query?: string; cwd?: string; all_cwds?: boolean; under?: string; /** Restrict to specific node ids. */ nodes?: string[]; /** Corpus types: report | doc | roadmap | meta | inbox | transcript. */ types?: string[]; report_kind?: string; kinds?: string[]; statuses?: string[]; /** Lower bound on artifact timestamp (epoch ms), parsed CLI-side. */ since_ms?: number; /** Upper bound on artifact timestamp (epoch ms), parsed CLI-side. */ until_ms?: number; /** Inbound-message narrowing (transcript + inbox): what drove arrival. */ origins?: string[]; /** Transcript-message roles: user | assistant | toolResult. */ roles?: string[]; /** Case-insensitive substring against sender attribution. */ from?: string; /** Weigh full body text in ranking (`--body`). */ weigh_body?: boolean; /** relevance | recency | oldest — already resolved to the effective sort. */ sort?: string; snippet_lines?: number; full?: boolean; limit?: number; cursor?: Cursor; } /** `POST /v1/canvas/history/search` result. `hits` is heterogeneous by mode * (with/without a relevance score, snippet vs full body), so it stays a * loose object list — the CLI's renderer already treats hits as * `Record[]`. The static `follow_up` line is appended * CLI-side. */ export interface HistorySearchResultDTO { hits: Record[]; next_cursor: Cursor | null; total: number; } /** `POST /v1/canvas/history/grep` body — the required-pattern line-hit search * over the per-cwd episodic corpus, backing `crtr canvas history grep`. * Shares scope/corpus filters with `HistorySearchQuery` but is a distinct * wire contract: `pattern` is mandatory (an ECMAScript regex) and there is no * ranking/snippet/full-body shape. */ export interface HistoryGrepQuery { /** Required ECMAScript regex, matched per body line (case-insensitive). */ pattern: string; cwd?: string; all_cwds?: boolean; under?: string; /** Restrict to specific node ids. */ nodes?: string[]; /** Corpus types: report | doc | roadmap | meta | inbox | transcript. */ types?: string[]; report_kind?: string; kinds?: string[]; statuses?: string[]; /** Lower bound on artifact timestamp (epoch ms), parsed CLI-side. */ since_ms?: number; /** Upper bound on artifact timestamp (epoch ms), parsed CLI-side. */ until_ms?: number; /** Inbound-message narrowing (transcript + inbox): what drove arrival. */ origins?: string[]; /** Transcript-message roles: user | assistant | toolResult. */ roles?: string[]; /** Case-insensitive substring against sender attribution. */ from?: string; limit?: number; cursor?: Cursor; } /** `POST /v1/canvas/history/stats` body — the grouped-count projection over * the same corpus search and grep scan, backing `crtr canvas history stats`. * Shares every scope/corpus/message filter; carries no query, pattern, or * pagination because the result is an aggregate, not a hit list. */ export interface HistoryStatsQuery { /** origin | node | kind | day | type | role. Defaults to type. */ group_by?: string; cwd?: string; all_cwds?: boolean; under?: string; nodes?: string[]; types?: string[]; report_kind?: string; kinds?: string[]; statuses?: string[]; since_ms?: number; until_ms?: number; origins?: string[]; roles?: string[]; from?: string; } /** `POST /v1/canvas/history/stats` result — counts by the grouped dimension, * descending. `total` counts the artifacts that carried the dimension, so it * can be lower than the corpus size when grouping by one not every artifact * has (origin, role). */ export interface HistoryStatsResultDTO { group_by: string; groups: Array<{ key: string; count: number; }>; total: number; /** Artifacts scanned before the grouping dimension was applied. */ scanned: number; } /** One matching body line from `crtr canvas history grep`. */ export interface HistoryGrepHitDTO { ref: string; /** "name (id)". */ node: string; ts: IsoTime; line: number; text: string; } /** `POST /v1/canvas/history/grep` result — a stable line-hit schema, distinct * from `HistorySearchResultDTO`'s heterogeneous ranked/browse shape. */ export interface HistoryGrepResultDTO { hits: HistoryGrepHitDTO[]; next_cursor: Cursor | null; total: number; } /** `GET /v1/canvas/history/read` query — resolve one `:` ref. */ export interface HistoryReadQuery { ref: string; /** Keep the artifact's YAML frontmatter (stripped by default). */ frontmatter?: boolean; /** `rendered` (default) or `raw` — the verbatim bytes behind the ref: the * session file for a bare `session` ref, the one jsonl line for a message, * the unparsed file for a report or doc. */ format?: string; } /** `GET /v1/canvas/history/read` result — one hit's full body. */ export interface HistoryReadResultDTO { ref: string; /** "name (id)". */ node: string; /** report: | doc | roadmap | meta | inbox. */ source: string; ts: IsoTime; content: string; } /** One node row in the browser canvas roster (`GET /v1/canvas/snapshot`). A * richer projection than `NodeSummaryDTO`: it carries the enriched viewer * fields (`dashboardRowsAll` + `enrichRows`) the browser canvas renders — * attention counts, ctx tokens, streaming/hanging/viewed state, last activity. */ export interface SnapshotNodeDTO { node_id: NodeIdDTO; name: string; /** Caller-supplied node title, kept separate from the enriched full label. */ title: string; /** Caller-supplied node brief, or null when absent. */ description: string | null; kind: string; mode: string; lifecycle?: string; status: NodeStatusDTO; cwd: string; /** The profile this node runs under; null for a historical no-profile row. * Carried here because it is the only field that says WHOSE work a node is — * a snapshot consumer grouping nodes by agent has nothing else to key on. */ profile_id: string | null; parent: NodeIdDTO | null; created: IsoTime; host_kind: string; /** True when the node hosts an enterable broker (`host_kind === 'broker'`). */ enterable: boolean; attention_count: number; cycles?: number; last_activity?: IsoTime; ctx_tokens?: number; streaming: boolean; /** Number of active background bash jobs, reported only for a live broker. */ jobs_active: number; /** Controller node this node is durably waiting for, or null. */ waiting_for: string | null; /** The node's active fault projection (structurally a `Fault`), or null. Kept * as a loose object to keep this DTO free of a `core` type import. */ hanging: Record | null; viewed: boolean; /** Basename of the node's final report, or null when it never pushed one. * Presence — not `status === 'done'` — is what distinguishes finished work * from clean parking. */ final_report: string | null; finalized_at: IsoTime | null; } /** `GET /v1/canvas/snapshot` result — the machine-readable browser canvas * roster (`crtr canvas snapshot`). Distinct from the per-node `NodeSnapshotDTO` * (nodes.ts). `subscriptions` is the authoritative `subscribes_to` adjacency * map: each node id → its publisher/child node ids in edge order. */ export interface SnapshotDTO { generated_at: IsoTime; nodes: SnapshotNodeDTO[]; subscriptions: Record; } /** One node row in the lean roster (`GET /v1/canvas/roster`). Carries only * stored node fields, without a process-liveness probe. `attention_count`/`last_activity`/`ctx_tokens`/ * `streaming`/`hanging`/`viewed` stay on `SnapshotNodeDTO`; a roster consumer * that needs attention counts fetches them separately via the existing * `POST /v1/canvas/attention/counts` (already scoped to exactly the ids it * renders, never the whole canvas). */ export interface RosterNodeDTO { node_id: NodeIdDTO; name: string; kind: string; mode: string; lifecycle?: string; status: NodeStatusDTO; cwd: string; host_kind: string; /** True when the node hosts an enterable broker (`host_kind === 'broker'`). */ enterable: boolean; parent: NodeIdDTO | null; created: IsoTime; } /** One `subscribes_to` edge in the roster's topology half. Every edge is kept * (passive + multiple per node) — never collapsed to one edge per node. */ export interface RosterEdgeDTO { from_id: NodeIdDTO; to_id: NodeIdDTO; active: boolean; created: IsoTime; } /** `GET /v1/canvas/roster` result — the lean, set-based topology read shared by * the attach viewer and the browser canvas: exactly two indexed queries * (`rosterNodes()` + `rosterEdges()`), no `enrichRows`, no per-row * `getNode`/`subscriptionsOf` loop, no synchronous liveness probe. The * recurring poll target that replaces the enriched `/v1/canvas/snapshot` * storm; that endpoint remains for genuinely on-demand rich views. */ export interface RosterDTO { generated_at: IsoTime; nodes: RosterNodeDTO[]; edges: RosterEdgeDTO[]; } /** One row in `GET /v1/canvas/graph`: exactly the node fields the attach graph displays. */ export interface GraphNodeDTO { node_id: NodeIdDTO; name: string; description: string | null; cycles: number | null; kind: string; status: NodeStatusDTO; frozen_at: IsoTime | null; pi_pid: number | null; telemetry_context_tokens: number | null; telemetry_last_activity: string | null; fault: NodeFaultDTO | null; streaming: boolean; } /** `GET /v1/canvas/graph` — the attach graph's one-read projection: display rows, subscription topology, and the current local viewer-focus set. */ export interface GraphDTO { generated_at: IsoTime; nodes: GraphNodeDTO[]; edges: RosterEdgeDTO[]; focused_node_ids: NodeIdDTO[]; } /** `POST /v1/canvas/prune` body — the three exclusive prune modes of * `crtr canvas prune`, precedence COUNT (`limit`) > EMPTY (`empty`) > TTL sweep. */ export interface PruneRequest { /** COUNT mode: keep the N most-recently-active nodes, prune the oldest * remaining terminal nodes (protected nodes always survive). */ limit?: number; /** TTL sweep retention window in days (default 14). */ ttl_days?: number; /** TTL sweep: also prune stale active/idle nodes past the TTL whose process * is provably gone. */ include_stale?: boolean; /** EMPTY mode: reap nodes whose engine never produced an assistant message. */ empty?: boolean; /** Report what would be pruned without mutating. */ dry_run?: boolean; } /** One node in a prune result (pruned, or candidate under `dry_run`). */ export interface PrunedNodeDTO { node_id: NodeIdDTO; /** Lifecycle status, or `empty` for EMPTY-mode reaps. */ status: string; /** ISO created timestamp (empty string for EMPTY-mode reaps). */ created: string; } /** `POST /v1/canvas/prune` result. `pruned` is the pruned set (or, under * `dry_run`, the candidate set — nothing deleted). `ttl_days` echoes the * retention window used. */ export interface PruneResultDTO { pruned: PrunedNodeDTO[]; dry_run: boolean; ttl_days: number; }