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[];
}