import type { Cursor, ExitIntentDTO, IsoTime, LifecycleDTO, ModeDTO, NodeIdDTO, NodeStatusDTO, TerminalReasonDTO } from './common.js'; import type { ReportDTO } from './reports.js'; import type { NodeOutcomeDTO, NodeOutcomeSummaryDTO, RegisterOutcomeDeliveryRequest } from './node-outcomes.js'; import type { FaultLink, FaultKind, FaultRetry, FaultProviderError } from './recovery.js'; /** `GET /v1/nodes/{id}/subject` — the node-config subject substrate gate * predicates evaluate against. Mirrors `NodeConfigSubject`; this narrow * endpoint exists so a CLI process (the `memory read` leaf) can gate-check * docs without reaching canvas state. */ export interface NodeSubjectDTO { kind: string; mode: ModeDTO; lifecycle: LifecycleDTO; hasManager: boolean; cwd: string; scope: 'user' | 'project'; orchestration: { depth: number; }; profile: string | null; /** Effective run scopes; a null row resolves to the full runtime vocabulary. */ scopes: readonly string[]; } /** `POST /v1/nodes` body. Carries the full immediate spawn recipe. */ export interface CreateNodeRequest { /** Persona kind. Omit to resolve the selected profile's `default_kind`, then `general`. */ kind?: string; prompt?: string; profile?: string; mode?: ModeDTO; /** The directory the create came from. It is what the daemon selects a * profile from when the request neither names one nor inherits one from a * parent, and — for a node with no `pin_cwd` and no spawner to inherit from — * where the node lands. */ cwd?: string; /** Pin the node to this exact directory, overriding both the spawner's * directory and the launch cwd. See `crtr node new --cwd`. */ pin_cwd?: string; /** Display name (tmux window + resume picker). Defaults to the kind. */ name?: string; /** Caller-supplied short description, preserved against automatic naming. */ description?: string; parent?: NodeIdDTO | null; /** Node that created this request; absent for an external process. */ creator?: NodeIdDTO; /** Per-run allow-list. Absent inherits everything. Some scopes are recorded but not enforced in beta: llm, files:, net, provider groups, and peers. */ scopes?: string[]; root?: boolean; /** Lifecycle of the new node. Roots default to `resident`; managed children * default to `terminal`. Use `resident` for a child that remains wakeable * without owing a final, or `terminal` for a bounded worker that must * finalize. */ root_lifecycle?: LifecycleDTO; /** Worktree branch name, or true for an auto-named managed worktree. */ worktree?: string | boolean; fork_from?: string; model?: string; situational_context?: string; no_kickoff?: boolean; output_schema?: string; /** Wall-clock bound from spawn, e.g. "45m", "2h", "1h30m". Expiry cancels the node and synthesizes a failure outcome with reason 'deadline_exceeded'. */ deadline?: string; /** Arm outcome delivery atomically with node birth. */ outcome_delivery?: RegisterOutcomeDeliveryRequest; /** Spawn AT this exact node id instead of a runtime-minted one — format- * validated and duplicate-rejected server-side (`NodeIdConflictError` → * HTTP 409 `node_id_exists`). See `crtr node new --node-id`. */ node_id?: string; /** Serve this create from the warm pool when a pre-booted spare matches the * request's frozen launch tuple (kind, mode, resolved cwd, profile, model, * situational_context) — answering in milliseconds instead of waiting out a * full engine boot. Only a bare root with no kickoff qualifies; anything the * pool cannot honor (or an empty pool) falls back to an ordinary cold spawn, * so the flag never fails a create, it only ever makes it faster. */ prefer_warm?: boolean; } /** The list/queryable projection of a node — the indexed row columns. */ export interface NodeSummaryDTO { node_id: NodeIdDTO; name: string; /** Generated first-task description, absent when the node is not yet named. * Paired with `name` it forms the full display label, so a roster consumer * builds it without a per-node metadata fetch. */ description?: string; /** How many times the node has been (re)launched; absent on a pre-v38 row. */ cycles?: number; kind: string; mode: ModeDTO; lifecycle: LifecycleDTO; status: NodeStatusDTO; cwd: string; host_kind: 'tmux' | 'broker' | null; profile_id: string | null; /** Per-run allow-list; null inherits everything. */ scopes: string[] | null; /** Whether the node's profile is currently paused. */ profile_paused: boolean; parent: NodeIdDTO | null; created: IsoTime; intent: ExitIntentDTO; waiting_for: NodeIdDTO | null; frozen_at: IsoTime | null; terminal_reason: TerminalReasonDTO | null; pi_pid: number | null; /** Launch-time process identity paired with `pi_pid`, or null when absent. */ pi_pid_identity: string | null; /** Current tmux placement cache. Local tmux callers re-check the pane itself before acting. */ window: string | null; tmux_session: string | null; pane: string | null; /** Basename of the canonical final report, or null when this node has not finalized. */ final_report: string | null; finalized_at: IsoTime | null; deadline_at: IsoTime | null; /** Cumulative input tokens reported by the broker telemetry producer. */ telemetry_tokens_in?: number | null; /** Latest context-window token gauge reported by the broker. */ telemetry_context_tokens?: number | null; /** Latest tool summary reported by the broker. */ telemetry_last_activity?: string | null; /** Timestamp of the latest broker telemetry update. */ telemetry_updated_at?: IsoTime | null; outcome: NodeOutcomeSummaryDTO | null; fault?: NodeFaultDTO | null; streaming?: boolean; /** Present only when requested with `include=activity`. */ activity?: NodeActivityDTO; } /** Optional per-node activity payload for a list response. Reports are the * stored push entries, newest first; `final_report` follows the row's canonical * final-report basename rather than merely selecting the newest final-tier file. */ export interface NodeActivityDTO { latest_report: ReportDTO | null; final_report: ReportDTO | null; pending_human_count: number; } /** The spine + subscription edges of a node (absorbs `managers`/`paths` reads). */ export interface NodeEdgesDTO { /** Spine parent (my manager); null for a root. */ parent: NodeIdDTO | null; /** Provenance — who spawned me. */ spawned_by: NodeIdDTO | null; /** Publishers I subscribe to. */ subscribes_to: NodeIdDTO[]; /** Subscribers to my output (my managers). */ subscribers: NodeIdDTO[]; /** Children I spawned. */ children: NodeIdDTO[]; } /** Absolute filesystem paths for a node (absorbs the `paths` read). */ export interface NodePathsDTO { node_dir: string; context_dir: string; reports_dir: string; meta_path: string; transcript_path: string; view_socket: string; } /** A node's managed git worktree, if any. */ export interface NodeWorktreeDTO { state: 'open' | 'closed' | 'abandoned'; /** Pending cleanup must be run manually after the owner exits. */ cleanup?: 'pending' | 'complete'; path: string; branch: string; repo_root: string; base_ref: string; base_sha: string; created: IsoTime; closed?: IsoTime; abandoned?: { branch: string; tip_sha: string; at: IsoTime; by: string; }; } /** Immutable companion provenance and fork coordinates. Present only on a * daemon-spawned companion node: a `review/companion` node for a human review * (`kind` absent or `review`), or — for a page ticket's feedback conversation * (`kind: 'page_feedback'`, review_id = ticket id, target_file = the page * source) — a fork of the sending node itself, spawned with its kind. */ export interface NodeReviewBindingDTO { kind?: 'review' | 'page_feedback'; review_id: string; origin_node_id: string; branch_file: string; target_file: string; } /** The active fault projection shown by node inspection. */ export interface NodeFaultDTO { link: FaultLink; op: string; kind: FaultKind; retry: FaultRetry; message: string; since: IsoTime; operation_id: string; anchorEntryId?: string; providerError?: FaultProviderError; } /** The full node view — summary ∪ identity extras ∪ edges ∪ paths. Returned by * `GET /v1/nodes/{id}` and by the create/lifecycle actions that yield a node. */ export interface NodeDetailDTO extends Omit { outcome: NodeOutcomeDTO | null; /** Node that created this node, or null when an external process did. */ creator: NodeIdDTO | null; /** The namer's prose form of `description` — sentence case, punctuation intact * (`NodeMeta.title`). What a surface showing this node to a person reads; * absent on a node named before titles existed. */ title?: string; /** The Nerd Font glyph the namer chose for this node's work, when it has one * (`NodeMeta.icon`). Rendered ahead of the label by surfaces that want it. */ icon?: string; /** Approximate context-window token load of the node's live/last session, * used by the orchestrator yield-nudge (`childFollowUp`). Null when unknown * (never launched, or no token accounting yet). */ context_tokens?: number | null; pi_session_id?: string | null; /** The node's durable model override (`NodeMeta.model_override`), or null when * it runs on the kind/profile default. Surfaced so `node config --model` * can report the resolved model after a patch. */ model_override?: string | null; /** Absolute path to pi's session `.jsonl`, captured at session_start * (`NodeMeta.pi_session_file`). Distinct from `paths.transcript_path` (the * crtr-owned transcript mirror). Consumed by `memory origin` to deref a doc * back to the conversation that authored it. */ pi_session_file?: string | null; /** Immutable review-companion provenance — present only on nodes of kind * `review/companion`. Four fields capture the invocation-time fork * coordinates without duplicating the full review record. */ review_binding?: NodeReviewBindingDTO | null; edges: NodeEdgesDTO; paths: NodePathsDTO; worktree?: NodeWorktreeDTO | null; /** Present only on a `POST /promote` response — the roadmap/goal facts the * promote primitive returns beyond the node meta (spec §6.2). A plain detail * read omits them. */ roadmap_written?: boolean; roadmap_path?: string; goal_path?: string; } /** `GET /v1/nodes` optional payloads. */ export type NodeListInclude = 'activity'; /** `GET /v1/nodes` query filters. Filters compose; `parent` selects direct * children while `under` selects a whole subtree. */ export interface ListNodesQuery { status?: NodeStatusDTO; lifecycle?: LifecycleDTO; kind?: string; mode?: ModeDTO; /** Exact profile id. */ profile_id?: string; /** Profile-id prefix, for callers that own a namespaced profile family. */ profile_prefix?: string; /** Restrict to direct children of this node. Mutually exclusive with `top_level`. */ parent?: NodeIdDTO; /** Restrict to nodes without a parent. */ top_level?: boolean; /** Restrict to the subtree under this node. */ under?: NodeIdDTO; /** Only nodes with a dangling/hanging manager edge. */ hanging?: boolean; /** Opt into richer per-node payloads that require filesystem reads. */ include?: NodeListInclude; } /** `GET /v1/nodes/{id}/snapshot` — the node's reconstructed broker snapshot * (`readNodeSnapshot`): the message log, aggregate stats, and current engine * state, plus the node's registered command set. */ export interface NodeSnapshotDTO { node_id: NodeIdDTO; snapshot: { messages: unknown[]; messageIds?: string[]; messageVisibility: Array<'visible' | 'internal'>; turnVisibility: 'visible' | 'internal'; stats: unknown; state: Record; display: { statuses: Record; widgets: Record; title?: string; }; queued?: { steering: string[]; followUp: string[]; }; toolGroupSummaries?: Record; workingActivity?: string; }; commands: { name: string; description: string; source: string; }[]; captured_at: IsoTime; } /** `GET /v1/nodes/{id}/messages` query. `cursor` walks backward from the * newest messages; each returned page remains chronological. */ export interface NodeMessagesQuery { cursor?: Cursor; limit?: number; } /** `GET /v1/nodes/{id}/messages` — a backward-paged window over the node's * cycle-flattened visible history. */ export interface NodeMessagesPageDTO { node_id: NodeIdDTO; /** pi AgentMessage[] JSON, chronological ascending within this page. */ messages: unknown[]; /** Stable session-entry ids aligned 1:1 with `messages`. */ message_ids?: string[]; /** Presentation visibility aligned 1:1 with `messages`. */ message_visibility: Array<'visible' | 'internal'>; /** Opaque cursor toward older messages; null at the start of the session. */ next_cursor: Cursor | null; captured_at: IsoTime; } /** `GET /v1/nodes/{id}/session` — the node's conversation exactly as it ran: * the raw session `.jsonl` bytes plus the assembled system prompt. Unlike * `NodeSnapshotDTO` nothing is reconstructed or flattened, so every * session-tree entry, cycle, branch, and custom message role survives. This is * what an export/download wants; `/snapshot` is what a renderer wants. */ export interface NodeSessionDTO { node_id: NodeIdDTO; /** Absolute path (inside the node's host) of the file the bytes came from. */ session_file: string; /** Verbatim `.jsonl` contents. */ session_jsonl: string; /** Assembled system prompt as last captured, or `null` if never written. */ system_prompt: string | null; captured_at: IsoTime; } /** `GET /v1/nodes/{id}/transcript` query. */ export interface TranscriptQuery { limit?: number; cursor?: Cursor; } /** `GET /v1/nodes/{id}/transcript` result. The reader (`transcriptMarkdown`) * renders the whole conversation as a single markdown document. */ export interface TranscriptDTO { node_id: NodeIdDTO; markdown: string; } /** A canvas artifact under a node (`nodeArtifacts` → `HistoryArtifact`): a * pushed report, a context doc, or the node roadmap. */ export interface ArtifactDTO { /** Stable `:` handle. */ ref: string; /** Artifact source — `report:` | `doc` | `roadmap` | `meta`. */ source: string; ts: IsoTime; title: string; } /** `GET /v1/nodes/{id}/artifacts` query. Narrow to one corpus; absent is the * default report/doc/roadmap set (`inbox` is opt-in only). */ export interface ArtifactsQuery { type?: 'report' | 'doc' | 'roadmap' | 'inbox'; } /** `GET /v1/nodes/{id}/artifacts` result. */ export interface ArtifactListDTO { node_id: NodeIdDTO; artifacts: ArtifactDTO[]; } /** One context root visible to a node — its own dir plus each publisher it * subscribes to (the shared-document roster). */ export interface ContextRootDTO { node_id: NodeIdDTO; label: string; dir: string; /** True for the node's own context root. */ self: boolean; /** Count of context + report files under the root. */ files: number; } /** `GET /v1/nodes/{id}/context` result (the listing; the nvim popup stays local). */ export interface ContextListDTO { node_id: NodeIdDTO; roots: ContextRootDTO[]; }