@north-light/crouter-api 0.3.156
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -0
- package/dist/__tests__/client.test.d.ts +1 -0
- package/dist/__tests__/client.test.js +274 -0
- package/dist/client.d.ts +246 -0
- package/dist/client.js +611 -0
- package/dist/dto/attach.d.ts +16 -0
- package/dist/dto/attach.js +13 -0
- package/dist/dto/broker.d.ts +45 -0
- package/dist/dto/broker.js +20 -0
- package/dist/dto/canvas.d.ts +253 -0
- package/dist/dto/canvas.js +2 -0
- package/dist/dto/common.d.ts +27 -0
- package/dist/dto/common.js +15 -0
- package/dist/dto/config.d.ts +19 -0
- package/dist/dto/config.js +3 -0
- package/dist/dto/crons.d.ts +124 -0
- package/dist/dto/crons.js +10 -0
- package/dist/dto/files.d.ts +11 -0
- package/dist/dto/files.js +7 -0
- package/dist/dto/focus.d.ts +24 -0
- package/dist/dto/focus.js +10 -0
- package/dist/dto/health.d.ts +41 -0
- package/dist/dto/health.js +2 -0
- package/dist/dto/human.d.ts +57 -0
- package/dist/dto/human.js +4 -0
- package/dist/dto/inbox.d.ts +105 -0
- package/dist/dto/inbox.js +10 -0
- package/dist/dto/lifecycle.d.ts +79 -0
- package/dist/dto/lifecycle.js +3 -0
- package/dist/dto/messages.d.ts +55 -0
- package/dist/dto/messages.js +2 -0
- package/dist/dto/modelauth.d.ts +41 -0
- package/dist/dto/modelauth.js +3 -0
- package/dist/dto/nodes.d.ts +194 -0
- package/dist/dto/nodes.js +3 -0
- package/dist/dto/profiles.d.ts +14 -0
- package/dist/dto/profiles.js +3 -0
- package/dist/dto/reports.d.ts +41 -0
- package/dist/dto/reports.js +2 -0
- package/dist/dto/subscriptions.d.ts +14 -0
- package/dist/dto/subscriptions.js +2 -0
- package/dist/dto/worktree.d.ts +19 -0
- package/dist/dto/worktree.js +6 -0
- package/dist/errors.d.ts +19 -0
- package/dist/errors.js +30 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +25 -0
- package/dist/routes.d.ts +63 -0
- package/dist/routes.js +91 -0
- package/package.json +33 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The crtrd broker `error` control frame — `{ code, message, id? }`. `id` is a
|
|
3
|
+
* correlation token echoed only from the request that failed (a read-op /
|
|
4
|
+
* `dequeue`); absent on uncorrelated errors. There is NO `retryable` field.
|
|
5
|
+
*/
|
|
6
|
+
export interface BrokerErrorFrame {
|
|
7
|
+
type: 'error';
|
|
8
|
+
code: string;
|
|
9
|
+
message: string;
|
|
10
|
+
/** Correlation token, echoed from the failed request; absent on uncorrelated errors. */
|
|
11
|
+
id?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The `welcome` frame's history/state snapshot, narrowed to what a relay consumer
|
|
15
|
+
* replays: the message history and the streaming flag. `M` is the message shape
|
|
16
|
+
* (pi's `AgentMessage` in a pi consumer; `unknown` by default).
|
|
17
|
+
*/
|
|
18
|
+
export interface BrokerWelcomeSnapshot<M = unknown> {
|
|
19
|
+
messages: M[];
|
|
20
|
+
state?: {
|
|
21
|
+
isStreaming?: boolean;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The crtrd broker `welcome` control frame — the history/resume catch-up snapshot
|
|
26
|
+
* delivered on (re)attach.
|
|
27
|
+
*/
|
|
28
|
+
export interface BrokerWelcomeFrame<M = unknown> {
|
|
29
|
+
type: 'welcome';
|
|
30
|
+
snapshot?: BrokerWelcomeSnapshot<M>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The broker-control frames a relay consumer reads: `welcome` (catch-up snapshot)
|
|
34
|
+
* and `error`. The broker interleaves others (display_*, control_changed, ack, …)
|
|
35
|
+
* under non-colliding `type` discriminants; a relay mapper drops those through its
|
|
36
|
+
* `default` arm untyped, so they are not enumerated here.
|
|
37
|
+
*/
|
|
38
|
+
export type BrokerControlFrame<M = unknown> = BrokerWelcomeFrame<M> | BrokerErrorFrame;
|
|
39
|
+
/**
|
|
40
|
+
* What the crtrd broker attach delivers to a relay consumer: the live engine
|
|
41
|
+
* event stream (`E` — pi's `AgentSessionEvent`, relayed verbatim) unioned with the
|
|
42
|
+
* broker's own control frames. Discriminants never collide: an `AgentSessionEvent`
|
|
43
|
+
* owns no `welcome` / `error` `type`.
|
|
44
|
+
*/
|
|
45
|
+
export type BrokerAttachFrame<E, M = unknown> = E | BrokerControlFrame<M>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Broker attach-protocol envelope DTOs — the crtrd-specific control frames the
|
|
2
|
+
// broker interleaves around pi's native agent-session event stream over a node's
|
|
3
|
+
// `view.sock` (and its WS bridge, spec §5). This module owns ONLY that envelope
|
|
4
|
+
// vocabulary: the two broker-control frames a relay consumer reads (`welcome` +
|
|
5
|
+
// `error`), the welcome snapshot they carry, and the relay union that folds them
|
|
6
|
+
// together with the live engine event stream.
|
|
7
|
+
//
|
|
8
|
+
// PURITY (spec §3.1): like every file under `src/api/`, this imports NOTHING —
|
|
9
|
+
// not `core/*`, not pi. The live engine event type (pi's `AgentSessionEvent`)
|
|
10
|
+
// and the snapshot message type (pi's `AgentMessage`) are left as TYPE PARAMETERS
|
|
11
|
+
// so a consumer instantiates them with the exact pi shapes it pins, while this
|
|
12
|
+
// package stays zero-dependency. A pi-free consumer can leave them at their
|
|
13
|
+
// `unknown` defaults.
|
|
14
|
+
//
|
|
15
|
+
// NARROWING NOTE: `BrokerWelcomeSnapshot` is deliberately the fields a live/history
|
|
16
|
+
// relay mapper reads — `messages` plus `state.isStreaming` — not the broker's full
|
|
17
|
+
// authoritative `BrokerSnapshot` (stats + the complete `get_state` mirror). The
|
|
18
|
+
// broker sends the richer object on the wire; consumers that only replay history
|
|
19
|
+
// read this narrowing of it.
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import type { Cursor, IsoTime, NodeIdDTO, NodeStatusDTO } from './common.js';
|
|
2
|
+
import type { NodeSummaryDTO } from './nodes.js';
|
|
3
|
+
/** `GET /v1/nodes` + `GET /v1/status` composed for the dashboard view. */
|
|
4
|
+
export interface DashboardQuery {
|
|
5
|
+
/** Restrict to the subtree under this node. */
|
|
6
|
+
under?: NodeIdDTO;
|
|
7
|
+
}
|
|
8
|
+
export interface DashboardDTO {
|
|
9
|
+
nodes: NodeSummaryDTO[];
|
|
10
|
+
/** Node counts keyed by status. */
|
|
11
|
+
counts: Partial<Record<NodeStatusDTO, number>>;
|
|
12
|
+
generated_at: IsoTime;
|
|
13
|
+
}
|
|
14
|
+
/** A node needing attention (tickets across the canvas). Projected from the
|
|
15
|
+
* `TicketEntry` reader — one entry per node with open human tickets. */
|
|
16
|
+
export interface AttentionItemDTO {
|
|
17
|
+
node_id: NodeIdDTO;
|
|
18
|
+
name: string;
|
|
19
|
+
cwd: string;
|
|
20
|
+
/** Number of open tickets on this node. */
|
|
21
|
+
count: number;
|
|
22
|
+
}
|
|
23
|
+
/** `GET /v1/canvas/attention` result. */
|
|
24
|
+
export interface AttentionDTO {
|
|
25
|
+
items: AttentionItemDTO[];
|
|
26
|
+
}
|
|
27
|
+
/** `POST /v1/canvas/attention/counts` request. Restrict the synchronous inbox
|
|
28
|
+
* scan to the nodes a viewer is actually rendering instead of enriching the
|
|
29
|
+
* entire canvas roster. */
|
|
30
|
+
export interface AttentionCountsRequest {
|
|
31
|
+
node_ids: NodeIdDTO[];
|
|
32
|
+
}
|
|
33
|
+
/** `POST /v1/canvas/attention/counts` result, keyed by requested node id. */
|
|
34
|
+
export interface AttentionCountsDTO {
|
|
35
|
+
counts: Record<NodeIdDTO, number>;
|
|
36
|
+
}
|
|
37
|
+
/** `POST /v1/canvas/history/search` body — the ranked/filtered search over the
|
|
38
|
+
* per-cwd episodic corpus, backing `crtr canvas history search` (optional
|
|
39
|
+
* query: ranked when present, recency browse when omitted). The CLI parses +
|
|
40
|
+
* validates flags (`--type` set, `--since`/`--until` → epoch ms) and sends
|
|
41
|
+
* this normalized query; the server runs `buildCorpus` + ranking + pagination
|
|
42
|
+
* (closures like `loadBody` cannot cross the boundary, so the whole search
|
|
43
|
+
* executes server-side). Sent as POST — the query carries arrays and
|
|
44
|
+
* free-text that do not serialize cleanly as GET params. */
|
|
45
|
+
export interface HistorySearchQuery {
|
|
46
|
+
/** Whitespace-separated terms, ranked; omit to browse by recency. */
|
|
47
|
+
query?: string;
|
|
48
|
+
cwd?: string;
|
|
49
|
+
all_cwds?: boolean;
|
|
50
|
+
under?: string;
|
|
51
|
+
/** Restrict to specific node ids. */
|
|
52
|
+
nodes?: string[];
|
|
53
|
+
/** Corpus types: report | doc | roadmap | meta | inbox. */
|
|
54
|
+
types?: string[];
|
|
55
|
+
report_kind?: string;
|
|
56
|
+
kinds?: string[];
|
|
57
|
+
statuses?: string[];
|
|
58
|
+
/** Lower bound on artifact timestamp (epoch ms), parsed CLI-side. */
|
|
59
|
+
since_ms?: number;
|
|
60
|
+
/** Upper bound on artifact timestamp (epoch ms), parsed CLI-side. */
|
|
61
|
+
until_ms?: number;
|
|
62
|
+
/** Weigh full body text in ranking (`--body`). */
|
|
63
|
+
weigh_body?: boolean;
|
|
64
|
+
/** relevance | recency | oldest — already resolved to the effective sort. */
|
|
65
|
+
sort?: string;
|
|
66
|
+
snippet_lines?: number;
|
|
67
|
+
full?: boolean;
|
|
68
|
+
limit?: number;
|
|
69
|
+
cursor?: Cursor;
|
|
70
|
+
}
|
|
71
|
+
/** `POST /v1/canvas/history/search` result. `hits` is heterogeneous by mode
|
|
72
|
+
* (with/without a relevance score, snippet vs full body), so it stays a
|
|
73
|
+
* loose object list — the CLI's renderer already treats hits as
|
|
74
|
+
* `Record<string, unknown>[]`. The static `follow_up` line is appended
|
|
75
|
+
* CLI-side. */
|
|
76
|
+
export interface HistorySearchResultDTO {
|
|
77
|
+
hits: Record<string, unknown>[];
|
|
78
|
+
next_cursor: Cursor | null;
|
|
79
|
+
total: number;
|
|
80
|
+
}
|
|
81
|
+
/** `POST /v1/canvas/history/grep` body — the required-pattern line-hit search
|
|
82
|
+
* over the per-cwd episodic corpus, backing `crtr canvas history grep`.
|
|
83
|
+
* Shares scope/corpus filters with `HistorySearchQuery` but is a distinct
|
|
84
|
+
* wire contract: `pattern` is mandatory (an ECMAScript regex) and there is no
|
|
85
|
+
* ranking/snippet/full-body shape. */
|
|
86
|
+
export interface HistoryGrepQuery {
|
|
87
|
+
/** Required ECMAScript regex, matched per body line (case-insensitive). */
|
|
88
|
+
pattern: string;
|
|
89
|
+
cwd?: string;
|
|
90
|
+
all_cwds?: boolean;
|
|
91
|
+
under?: string;
|
|
92
|
+
/** Restrict to specific node ids. */
|
|
93
|
+
nodes?: string[];
|
|
94
|
+
/** Corpus types: report | doc | roadmap | meta | inbox. */
|
|
95
|
+
types?: string[];
|
|
96
|
+
report_kind?: string;
|
|
97
|
+
kinds?: string[];
|
|
98
|
+
statuses?: string[];
|
|
99
|
+
/** Lower bound on artifact timestamp (epoch ms), parsed CLI-side. */
|
|
100
|
+
since_ms?: number;
|
|
101
|
+
/** Upper bound on artifact timestamp (epoch ms), parsed CLI-side. */
|
|
102
|
+
until_ms?: number;
|
|
103
|
+
limit?: number;
|
|
104
|
+
cursor?: Cursor;
|
|
105
|
+
}
|
|
106
|
+
/** One matching body line from `crtr canvas history grep`. */
|
|
107
|
+
export interface HistoryGrepHitDTO {
|
|
108
|
+
ref: string;
|
|
109
|
+
/** "name (id)". */
|
|
110
|
+
node: string;
|
|
111
|
+
ts: IsoTime;
|
|
112
|
+
line: number;
|
|
113
|
+
text: string;
|
|
114
|
+
}
|
|
115
|
+
/** `POST /v1/canvas/history/grep` result — a stable line-hit schema, distinct
|
|
116
|
+
* from `HistorySearchResultDTO`'s heterogeneous ranked/browse shape. */
|
|
117
|
+
export interface HistoryGrepResultDTO {
|
|
118
|
+
hits: HistoryGrepHitDTO[];
|
|
119
|
+
next_cursor: Cursor | null;
|
|
120
|
+
total: number;
|
|
121
|
+
}
|
|
122
|
+
/** `GET /v1/canvas/history/read` query — resolve one `<node-id>:<relpath>` ref. */
|
|
123
|
+
export interface HistoryReadQuery {
|
|
124
|
+
ref: string;
|
|
125
|
+
/** Keep the artifact's YAML frontmatter (stripped by default). */
|
|
126
|
+
frontmatter?: boolean;
|
|
127
|
+
}
|
|
128
|
+
/** `GET /v1/canvas/history/read` result — one hit's full body. */
|
|
129
|
+
export interface HistoryReadResultDTO {
|
|
130
|
+
ref: string;
|
|
131
|
+
/** "name (id)". */
|
|
132
|
+
node: string;
|
|
133
|
+
/** report:<kind> | doc | roadmap | meta | inbox. */
|
|
134
|
+
source: string;
|
|
135
|
+
ts: IsoTime;
|
|
136
|
+
content: string;
|
|
137
|
+
}
|
|
138
|
+
/** One node row in the browser canvas roster (`GET /v1/canvas/snapshot`). A
|
|
139
|
+
* richer projection than `NodeSummaryDTO`: it carries the enriched viewer
|
|
140
|
+
* fields (`dashboardRowsAll` + `enrichRows`) the browser canvas renders —
|
|
141
|
+
* attention counts, ctx tokens, streaming/hanging/viewed state, last activity. */
|
|
142
|
+
export interface SnapshotNodeDTO {
|
|
143
|
+
node_id: NodeIdDTO;
|
|
144
|
+
name: string;
|
|
145
|
+
kind: string;
|
|
146
|
+
mode: string;
|
|
147
|
+
lifecycle?: string;
|
|
148
|
+
status: NodeStatusDTO;
|
|
149
|
+
cwd: string;
|
|
150
|
+
parent: NodeIdDTO | null;
|
|
151
|
+
created: IsoTime;
|
|
152
|
+
host_kind: string;
|
|
153
|
+
/** True when the node hosts an enterable broker (`host_kind === 'broker'`). */
|
|
154
|
+
enterable: boolean;
|
|
155
|
+
attention_count: number;
|
|
156
|
+
cycles?: number;
|
|
157
|
+
last_activity?: IsoTime;
|
|
158
|
+
ctx_tokens?: number;
|
|
159
|
+
streaming: boolean;
|
|
160
|
+
/** The node's active fault projection (structurally a `Fault`), or null. Kept
|
|
161
|
+
* as a loose object to keep this DTO free of a `core` type import. */
|
|
162
|
+
hanging: Record<string, unknown> | null;
|
|
163
|
+
viewed: boolean;
|
|
164
|
+
}
|
|
165
|
+
/** `GET /v1/canvas/snapshot` result — the machine-readable browser canvas
|
|
166
|
+
* roster (`crtr canvas snapshot`). Distinct from the per-node `NodeSnapshotDTO`
|
|
167
|
+
* (nodes.ts). `subscriptions` is the authoritative `subscribes_to` adjacency
|
|
168
|
+
* map: each node id → its publisher/child node ids in edge order. */
|
|
169
|
+
export interface SnapshotDTO {
|
|
170
|
+
generated_at: IsoTime;
|
|
171
|
+
nodes: SnapshotNodeDTO[];
|
|
172
|
+
subscriptions: Record<string, NodeIdDTO[]>;
|
|
173
|
+
}
|
|
174
|
+
/** One node row in the lean roster (`GET /v1/canvas/roster`). Carries only
|
|
175
|
+
* columns already native to the `nodes` row — no meta.json read, no
|
|
176
|
+
* process-liveness probe. `attention_count`/`last_activity`/`ctx_tokens`/
|
|
177
|
+
* `streaming`/`hanging`/`viewed` stay on `SnapshotNodeDTO`; a roster consumer
|
|
178
|
+
* that needs attention counts fetches them separately via the existing
|
|
179
|
+
* `POST /v1/canvas/attention/counts` (already scoped to exactly the ids it
|
|
180
|
+
* renders, never the whole canvas). */
|
|
181
|
+
export interface RosterNodeDTO {
|
|
182
|
+
node_id: NodeIdDTO;
|
|
183
|
+
name: string;
|
|
184
|
+
kind: string;
|
|
185
|
+
mode: string;
|
|
186
|
+
lifecycle?: string;
|
|
187
|
+
status: NodeStatusDTO;
|
|
188
|
+
cwd: string;
|
|
189
|
+
host_kind: string;
|
|
190
|
+
/** True when the node hosts an enterable broker (`host_kind === 'broker'`). */
|
|
191
|
+
enterable: boolean;
|
|
192
|
+
parent: NodeIdDTO | null;
|
|
193
|
+
created: IsoTime;
|
|
194
|
+
}
|
|
195
|
+
/** One `subscribes_to` edge in the roster's topology half. Every edge is kept
|
|
196
|
+
* (passive + multiple per node) — never collapsed to one edge per node. */
|
|
197
|
+
export interface RosterEdgeDTO {
|
|
198
|
+
from_id: NodeIdDTO;
|
|
199
|
+
to_id: NodeIdDTO;
|
|
200
|
+
active: boolean;
|
|
201
|
+
created: IsoTime;
|
|
202
|
+
}
|
|
203
|
+
/** `GET /v1/canvas/roster` result — the lean, set-based topology read shared by
|
|
204
|
+
* the attach viewer and the browser canvas: exactly two indexed queries
|
|
205
|
+
* (`rosterNodes()` + `rosterEdges()`), no `enrichRows`, no per-row
|
|
206
|
+
* `getNode`/`subscriptionsOf` loop, no synchronous liveness probe. The
|
|
207
|
+
* recurring poll target that replaces the enriched `/v1/canvas/snapshot`
|
|
208
|
+
* storm; that endpoint remains for genuinely on-demand rich views. */
|
|
209
|
+
export interface RosterDTO {
|
|
210
|
+
generated_at: IsoTime;
|
|
211
|
+
nodes: RosterNodeDTO[];
|
|
212
|
+
edges: RosterEdgeDTO[];
|
|
213
|
+
}
|
|
214
|
+
/** `POST /v1/canvas/prune` body — the three exclusive prune modes of
|
|
215
|
+
* `crtr canvas prune`, precedence COUNT (`limit`) > EMPTY (`empty`) > TTL sweep. */
|
|
216
|
+
export interface PruneRequest {
|
|
217
|
+
/** COUNT mode: keep the N most-recently-active nodes, prune the oldest
|
|
218
|
+
* remaining terminal nodes (protected nodes always survive). */
|
|
219
|
+
limit?: number;
|
|
220
|
+
/** TTL sweep retention window in days (default 14). */
|
|
221
|
+
ttl_days?: number;
|
|
222
|
+
/** TTL sweep: also prune stale active/idle nodes past the TTL whose process
|
|
223
|
+
* is provably gone. */
|
|
224
|
+
include_stale?: boolean;
|
|
225
|
+
/** EMPTY mode: reap nodes whose engine never produced an assistant message. */
|
|
226
|
+
empty?: boolean;
|
|
227
|
+
/** Report what would be pruned without mutating. */
|
|
228
|
+
dry_run?: boolean;
|
|
229
|
+
}
|
|
230
|
+
/** One node in a prune result (pruned, or candidate under `dry_run`). */
|
|
231
|
+
export interface PrunedNodeDTO {
|
|
232
|
+
node_id: NodeIdDTO;
|
|
233
|
+
/** Lifecycle status, or `empty` for EMPTY-mode reaps. */
|
|
234
|
+
status: string;
|
|
235
|
+
/** ISO created timestamp (empty string for EMPTY-mode reaps). */
|
|
236
|
+
created: string;
|
|
237
|
+
}
|
|
238
|
+
/** `POST /v1/canvas/prune` result. `pruned` is the pruned set (or, under
|
|
239
|
+
* `dry_run`, the candidate set — nothing deleted). `ttl_days` echoes the
|
|
240
|
+
* retention window used. */
|
|
241
|
+
export interface PruneResultDTO {
|
|
242
|
+
pruned: PrunedNodeDTO[];
|
|
243
|
+
dry_run: boolean;
|
|
244
|
+
ttl_days: number;
|
|
245
|
+
}
|
|
246
|
+
/** `POST /v1/canvas/rebuild-index` result. Fails loud server-side when metas
|
|
247
|
+
* exist on disk but the rebuild yields zero rows (a corrupt/empty index). */
|
|
248
|
+
export interface RebuildIndexResultDTO {
|
|
249
|
+
/** How many `nodes/<id>/meta.json` files were found on disk. */
|
|
250
|
+
metas: number;
|
|
251
|
+
/** How many node rows exist in canvas.db after the rebuild. */
|
|
252
|
+
rows: number;
|
|
253
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** ISO-8601 timestamp string (matches the runtime's `nowIso()` output). */
|
|
2
|
+
export type IsoTime = string;
|
|
3
|
+
/** A node id — the existing `<slug>-<hash>` string. Typed as a nominal alias so
|
|
4
|
+
* DTOs read as thin projections; validated at the boundary by `isSafeNodeId`. */
|
|
5
|
+
export type NodeIdDTO = string;
|
|
6
|
+
/** Opaque pagination cursor (server-defined; clients pass it back verbatim). */
|
|
7
|
+
export type Cursor = string;
|
|
8
|
+
/** Standard list-pagination inputs shared by paged queries. */
|
|
9
|
+
export interface Pagination {
|
|
10
|
+
limit?: number;
|
|
11
|
+
cursor?: Cursor;
|
|
12
|
+
}
|
|
13
|
+
/** Runtime status of a node (mirrors the runtime `NodeStatus` union; declared
|
|
14
|
+
* locally to keep `/api` free of any `core/*` import). */
|
|
15
|
+
export type NodeStatusDTO = 'active' | 'idle' | 'done' | 'dead' | 'canceled';
|
|
16
|
+
/** Lifecycle class of a node (mirrors the runtime `Lifecycle` union). */
|
|
17
|
+
export type LifecycleDTO = 'terminal' | 'resident';
|
|
18
|
+
/** Execution mode of a node (mirrors the runtime `Mode` union). */
|
|
19
|
+
export type ModeDTO = 'base' | 'orchestrator';
|
|
20
|
+
/** Why a node last stopped (mirrors the runtime `ExitIntent` union). */
|
|
21
|
+
export type ExitIntentDTO = 'done' | 'refresh' | 'idle-release' | null;
|
|
22
|
+
/** Inbox urgency tier for a delivered message (mirrors feed/inbox `InboxTier`). */
|
|
23
|
+
export type InboxTierDTO = 'critical' | 'urgent' | 'normal' | 'deferred';
|
|
24
|
+
/** Whether a node id is a safe single filesystem path segment. Re-declared here
|
|
25
|
+
* (not imported from `core/canvas/paths.ts`) to keep the exported `/api`
|
|
26
|
+
* contract dependency-light; kept byte-for-byte equivalent to the runtime's. */
|
|
27
|
+
export declare function isSafeNodeId(id: string): boolean;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Shared scalars and cross-cutting DTO primitives.
|
|
2
|
+
//
|
|
3
|
+
// PURITY (spec §3.1): this file — like every file under `src/api/` — imports
|
|
4
|
+
// ONLY other `src/api/*` modules and Node built-ins. Never `core/*`,
|
|
5
|
+
// `node:sqlite`, TUI, or any heavy runtime module.
|
|
6
|
+
const MAX_NODE_ID_BYTES = 128;
|
|
7
|
+
/** Whether a node id is a safe single filesystem path segment. Re-declared here
|
|
8
|
+
* (not imported from `core/canvas/paths.ts`) to keep the exported `/api`
|
|
9
|
+
* contract dependency-light; kept byte-for-byte equivalent to the runtime's. */
|
|
10
|
+
export function isSafeNodeId(id) {
|
|
11
|
+
return typeof id === 'string'
|
|
12
|
+
&& id !== '' && id !== '.' && id !== '..'
|
|
13
|
+
&& !/[\\/\0]/u.test(id)
|
|
14
|
+
&& Buffer.byteLength(id, 'utf8') <= MAX_NODE_ID_BYTES;
|
|
15
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { LifecycleDTO, ModeDTO } from './common.js';
|
|
2
|
+
/** `PATCH /v1/nodes/{id}/config` body. All fields optional; a set field is
|
|
3
|
+
* applied, an absent field is untouched. */
|
|
4
|
+
export interface NodeConfigPatch {
|
|
5
|
+
/** Model tier or exact selection override. */
|
|
6
|
+
model?: string;
|
|
7
|
+
/** Thinking/reasoning level override. */
|
|
8
|
+
thinking?: string;
|
|
9
|
+
/** Ambient situational-context text override. */
|
|
10
|
+
situational_context?: string;
|
|
11
|
+
/** Persona kind respecialization — rebuilds the launch spec server-side. */
|
|
12
|
+
kind?: string;
|
|
13
|
+
/** Persona mode; setting `orchestrator` seeds a roadmap scaffold if absent. */
|
|
14
|
+
mode?: ModeDTO;
|
|
15
|
+
/** Lifecycle flip (terminal↔resident), rebuilding the launch spec. */
|
|
16
|
+
lifecycle?: LifecycleDTO;
|
|
17
|
+
/** Rename the node (and, when it has a live viewer window, that window). */
|
|
18
|
+
name?: string;
|
|
19
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { IsoTime, NodeIdDTO } from './common.js';
|
|
2
|
+
export type CronScopeDTO = 'profile' | 'global';
|
|
3
|
+
export type CronOverlapDTO = 'skip' | 'queue' | 'replace';
|
|
4
|
+
export type CronOnOutputDTO = 'silent' | 'on-failure' | 'always' | 'on-change';
|
|
5
|
+
export type CronStateDTO = 'active' | 'paused';
|
|
6
|
+
export type CronRunStateDTO = 'idle' | 'running';
|
|
7
|
+
/** Is this a safe single path segment for `routes.cron*()` interpolation?
|
|
8
|
+
* Cron ids are server-minted UUIDs, but the route builders carry no logic, so
|
|
9
|
+
* every interpolated id is gated here first (the `isSafeNodeId` discipline). */
|
|
10
|
+
export declare function isSafeCronId(id: string): boolean;
|
|
11
|
+
/** The last settled run, carried on every cron projection so `cron list` shows
|
|
12
|
+
* recent health without a `cron show` per row. Null until the first run. */
|
|
13
|
+
export interface CronLastRunDTO {
|
|
14
|
+
/** When the run settled (UTC). */
|
|
15
|
+
finished: IsoTime | null;
|
|
16
|
+
/** -1 = timeout kill; null = no exit observed (process error / skip marker). */
|
|
17
|
+
exit_code: number | null;
|
|
18
|
+
/** What the sink did with the output, or why it didn't. */
|
|
19
|
+
delivered: string | null;
|
|
20
|
+
}
|
|
21
|
+
/** A cron row projection (`GET /v1/crons`). */
|
|
22
|
+
export interface CronDTO {
|
|
23
|
+
cron_id: string;
|
|
24
|
+
/** Agent-legible label; how the cron shows in list. */
|
|
25
|
+
name: string;
|
|
26
|
+
/** Provenance only — the node that armed it, if any; never a cascade anchor. */
|
|
27
|
+
created_by: NodeIdDTO | null;
|
|
28
|
+
/** The bash command each fire runs. */
|
|
29
|
+
command: string;
|
|
30
|
+
/** Next occurrence (UTC). */
|
|
31
|
+
fire_at: IsoTime;
|
|
32
|
+
/** Recur JSON (interval or cron); null for a one-shot. */
|
|
33
|
+
recur: string | null;
|
|
34
|
+
/** IANA zone for a calendar cadence; null otherwise. */
|
|
35
|
+
tz: string | null;
|
|
36
|
+
/** Clock bound; the row is deleted when passed. Null = no bound. */
|
|
37
|
+
expires_at: IsoTime | null;
|
|
38
|
+
/** Opt-in node coupling (cascades on that node's deletion); null = detached. */
|
|
39
|
+
anchor_node: NodeIdDTO | null;
|
|
40
|
+
cancel_on_wake: boolean;
|
|
41
|
+
/** Execution context, snapshotted at arm time. */
|
|
42
|
+
cwd: string;
|
|
43
|
+
profile: string | null;
|
|
44
|
+
scope: CronScopeDTO;
|
|
45
|
+
/** Names of the extra env vars snapshotted onto the row — KEYS ONLY: the
|
|
46
|
+
* values are the caller's to know and may be secrets. */
|
|
47
|
+
env_keys: string[];
|
|
48
|
+
run_timeout_s: number;
|
|
49
|
+
overlap: CronOverlapDTO;
|
|
50
|
+
on_output: CronOnOutputDTO;
|
|
51
|
+
/** Where deliveries go — the raw sink spec (`node:<id>` | `spawn:<kind>` |
|
|
52
|
+
* `human`), or null when the disposition never delivers. */
|
|
53
|
+
sink: string | null;
|
|
54
|
+
tier: string;
|
|
55
|
+
state: CronStateDTO;
|
|
56
|
+
run_state: CronRunStateDTO;
|
|
57
|
+
/** Recent health: the most recent settled run, or null if it never ran. */
|
|
58
|
+
last_run: CronLastRunDTO | null;
|
|
59
|
+
created: IsoTime;
|
|
60
|
+
updated: IsoTime;
|
|
61
|
+
}
|
|
62
|
+
/** `GET /v1/crons` query. `profile` is the CALLER's profile, which is what
|
|
63
|
+
* scoping means here: a profile-scoped cron belongs to its profile, a global
|
|
64
|
+
* one is canvas-home-wide. Omitting `profile` means the caller stands outside
|
|
65
|
+
* every profile (a plain shell, or a provenance read such as "which crons did
|
|
66
|
+
* node X arm") and sees the whole canvas home. This is a namespacing
|
|
67
|
+
* boundary between profiles, not a security boundary. */
|
|
68
|
+
export interface ListCronsQuery {
|
|
69
|
+
profile?: string;
|
|
70
|
+
}
|
|
71
|
+
/** Caller identity for the per-cron routes (show/pause/resume/run/cancel).
|
|
72
|
+
* Same rule as `ListCronsQuery`: a cron outside the caller's scope is 404,
|
|
73
|
+
* and an absent profile sees everything. */
|
|
74
|
+
export interface CronScopeQuery {
|
|
75
|
+
profile?: string | null;
|
|
76
|
+
}
|
|
77
|
+
/** One settled run-log entry (`GET /v1/crons/:cronId`, `POST /v1/crons/:cronId/run`). */
|
|
78
|
+
export interface CronRunDTO {
|
|
79
|
+
run_id: string;
|
|
80
|
+
started: IsoTime;
|
|
81
|
+
finished: IsoTime | null;
|
|
82
|
+
duration_ms: number | null;
|
|
83
|
+
/** null = no exit observed (spawn/process error, or a skip marker); -1 = timeout kill. */
|
|
84
|
+
exit_code: number | null;
|
|
85
|
+
stdout_head: string | null;
|
|
86
|
+
stderr_head: string | null;
|
|
87
|
+
/** What the sink did with this run's output, or why it didn't. */
|
|
88
|
+
delivered: string | null;
|
|
89
|
+
}
|
|
90
|
+
/** `GET /v1/crons/:cronId` — one cron with its run-log ring (most recent first). */
|
|
91
|
+
export interface CronShowDTO {
|
|
92
|
+
cron: CronDTO;
|
|
93
|
+
runs: CronRunDTO[];
|
|
94
|
+
}
|
|
95
|
+
/** `POST /v1/crons` — arm one cron. The CLI resolves timing client-side
|
|
96
|
+
* (parseWhen/parseCadence) and sends the settled `fire_at`/`recur`/`tz`;
|
|
97
|
+
* the server mints the cron_id and applies spec defaults for everything
|
|
98
|
+
* omitted. */
|
|
99
|
+
export interface ArmCronRequest {
|
|
100
|
+
name: string;
|
|
101
|
+
command: string;
|
|
102
|
+
fire_at: IsoTime;
|
|
103
|
+
recur?: string | null;
|
|
104
|
+
tz?: string | null;
|
|
105
|
+
created_by?: NodeIdDTO | null;
|
|
106
|
+
/** Execution context, snapshotted at arm time — chosen by the client, else
|
|
107
|
+
* inherited from the creating node; never re-resolved at run time. */
|
|
108
|
+
cwd: string;
|
|
109
|
+
/** Extra env for every run, as a JSON object of string values. */
|
|
110
|
+
env_json?: string | null;
|
|
111
|
+
profile?: string | null;
|
|
112
|
+
/** `profile` (default when a profile is resolved) requires a profile. */
|
|
113
|
+
scope?: CronScopeDTO;
|
|
114
|
+
on_output?: CronOnOutputDTO;
|
|
115
|
+
/** Raw sink spec: `node:<id>` | `spawn:<kind>` | `human`. Required by the
|
|
116
|
+
* always/on-change dispositions; meaningless otherwise. */
|
|
117
|
+
sink?: string | null;
|
|
118
|
+
tier?: string;
|
|
119
|
+
expires_at?: IsoTime | null;
|
|
120
|
+
anchor_node?: NodeIdDTO | null;
|
|
121
|
+
cancel_on_wake?: boolean;
|
|
122
|
+
run_timeout_s?: number;
|
|
123
|
+
overlap?: CronOverlapDTO;
|
|
124
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Cron DTOs (the cron-spec scheduler — arm/list/show/run/pause/resume/cancel).
|
|
2
|
+
/** Is this a safe single path segment for `routes.cron*()` interpolation?
|
|
3
|
+
* Cron ids are server-minted UUIDs, but the route builders carry no logic, so
|
|
4
|
+
* every interpolated id is gated here first (the `isSafeNodeId` discipline). */
|
|
5
|
+
export function isSafeCronId(id) {
|
|
6
|
+
return typeof id === 'string'
|
|
7
|
+
&& id !== '' && id !== '.' && id !== '..'
|
|
8
|
+
&& !/[\\/\0\s?#%]/u.test(id)
|
|
9
|
+
&& Buffer.byteLength(id, 'utf8') <= 128;
|
|
10
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** `GET /v1/files/peek?path=<abs>` result — the UTF-8 contents of an absolute
|
|
2
|
+
* host path, truncated to a byte cap (the panel shows a partial-content notice
|
|
3
|
+
* when `truncated` is set). */
|
|
4
|
+
export interface FilePeekDTO {
|
|
5
|
+
/** The absolute path read (echoes the request). */
|
|
6
|
+
path: string;
|
|
7
|
+
/** UTF-8 file contents (a prefix when `truncated`). */
|
|
8
|
+
content: string;
|
|
9
|
+
/** True when the file exceeded the read cap and `content` is a prefix. */
|
|
10
|
+
truncated: boolean;
|
|
11
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Host file-read DTO (spec §6). Backs `GET /v1/files/peek` — the browser
|
|
2
|
+
// file-peek panel reads an absolute host path shown in a transcript tool card.
|
|
3
|
+
// A single trusted local user (unix socket); the browser reaches it only
|
|
4
|
+
// through the auth-gated web proxy.
|
|
5
|
+
//
|
|
6
|
+
// PURITY (spec §3.1): Node built-ins + `src/api/*` only.
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { NodeIdDTO } from './common.js';
|
|
2
|
+
/** One viewer row: a node's single on-screen pane in a tmux session. */
|
|
3
|
+
export interface FocusDTO {
|
|
4
|
+
focus_id: string;
|
|
5
|
+
pane: string | null;
|
|
6
|
+
session: string | null;
|
|
7
|
+
node_id: NodeIdDTO;
|
|
8
|
+
}
|
|
9
|
+
/** `POST /v1/focuses` body — register a viewer pane as a node's focus row.
|
|
10
|
+
* UNIQUE(node_id): returns the existing row (no insert) when the pane or node
|
|
11
|
+
* is already registered. `window` is accepted for the call contract but not
|
|
12
|
+
* stored (the table keys on pane + session). */
|
|
13
|
+
export interface RegisterFocusRequest {
|
|
14
|
+
node_id: NodeIdDTO;
|
|
15
|
+
pane: string;
|
|
16
|
+
session: string | null;
|
|
17
|
+
window: string | null;
|
|
18
|
+
}
|
|
19
|
+
/** `PATCH /v1/focuses/{focus_id}` body — re-point a viewer row's pane + its
|
|
20
|
+
* derived session cache after the pane moves. */
|
|
21
|
+
export interface SetFocusPaneRequest {
|
|
22
|
+
pane: string | null;
|
|
23
|
+
session: string | null;
|
|
24
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Focus DTOs (the viewer registry — canvas.db `focuses` table). A FOCUS is one
|
|
2
|
+
// durable on-screen viewport bound to one node (UNIQUE node_id ⇒ ≤1 per node).
|
|
3
|
+
// The wire shape mirrors the internal `FocusRow` 1:1 — the registry is a flat
|
|
4
|
+
// pane↔node binding with no projection to reshape.
|
|
5
|
+
//
|
|
6
|
+
// The reads GC lazily server-side (a row whose pane is gone is pruned on read),
|
|
7
|
+
// so a returned row is always live. The mutations are the tmux-driven CLI
|
|
8
|
+
// surfaces' (surface-tmux-spread, focus placement) writes: the tmux verbs run
|
|
9
|
+
// LOCALLY in the caller's session; only the canvas.db focus rows route here.
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { NodeStatusDTO } from './common.js';
|
|
2
|
+
/** `GET /healthz` — substrate readiness probe. Versions are informational, never
|
|
3
|
+
* a gate. Used by the provisioning ladder. */
|
|
4
|
+
export interface HealthDTO {
|
|
5
|
+
ok: boolean;
|
|
6
|
+
daemon_up: boolean;
|
|
7
|
+
store_accessible: boolean;
|
|
8
|
+
brokers_reconciled: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** `GET /v1/status` — daemon status snapshot. Backs `crtr sys daemon status`
|
|
11
|
+
* state and the `crtr canvas dashboard` header. Versions are informational. */
|
|
12
|
+
export interface StatusDTO {
|
|
13
|
+
daemon_pid: number;
|
|
14
|
+
tick_interval_ms: number;
|
|
15
|
+
/** Node counts keyed by status. */
|
|
16
|
+
node_counts: Partial<Record<NodeStatusDTO, number>>;
|
|
17
|
+
/** Bound listener addresses. `tcp` is null when the TCP listener is off. */
|
|
18
|
+
listeners: {
|
|
19
|
+
socket_path: string;
|
|
20
|
+
tcp: string | null;
|
|
21
|
+
};
|
|
22
|
+
runtime_version: string;
|
|
23
|
+
api_version: string;
|
|
24
|
+
}
|
|
25
|
+
/** `POST /v1/daemon/restart` — the ack for a daemon-owned handover.
|
|
26
|
+
*
|
|
27
|
+
* The daemon answers BEFORE it tears anything down: the caller is normally a
|
|
28
|
+
* node's own bash tool, whose broker this handover will kill, so the ack must
|
|
29
|
+
* be a settled tool result in that node's transcript before the SIGTERM lands.
|
|
30
|
+
* `accepted` is therefore a promise about the future, not a completed action —
|
|
31
|
+
* the successor daemon is spawned after `grace_ms`, and every node the
|
|
32
|
+
* teardown interrupts is resumed by it. */
|
|
33
|
+
export interface DaemonRestartDTO {
|
|
34
|
+
accepted: boolean;
|
|
35
|
+
/** The daemon that took the request and is about to hand over. */
|
|
36
|
+
predecessor_pid: number;
|
|
37
|
+
/** Its epoch id — the successor mints a new one. */
|
|
38
|
+
predecessor_epoch: string;
|
|
39
|
+
/** How long the daemon waits after answering before teardown begins. */
|
|
40
|
+
grace_ms: number;
|
|
41
|
+
}
|