@north-light/crouter-api 0.3.312 → 0.3.313

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.
@@ -1,4 +1,4 @@
1
- import type { DaemonRestartDTO, HealthDTO, StatusDTO } from './dto/health.js';
1
+ import type { DaemonAdmitDTO, DaemonRestartDTO, HealthDTO, MigrateStateDTO, MigrateStateRequest, StatusDTO } from './dto/health.js';
2
2
  import type { BashJobStatusDTO, BashJobStopResultDTO } from './dto/bash-jobs.js';
3
3
  import type { ArtifactListDTO, ArtifactsQuery, ContextListDTO, CreateNodeRequest, ListNodesQuery, NodeDetailDTO, NodeMessagesPageDTO, NodeMessagesQuery, NodeSessionDTO, NodeSnapshotDTO, NodeSubjectDTO, NodeSummaryDTO, TranscriptDTO, TranscriptQuery } from './dto/nodes.js';
4
4
  import type { NodeOutcomeResponseDTO, OutcomeDeliveryDTO, RegisterOutcomeDeliveryRequest } from './dto/node-outcomes.js';
@@ -15,14 +15,15 @@ import type { FilePeekDTO } from './dto/files.js';
15
15
  import type { MemoryDocRefDTO } from './dto/memory.js';
16
16
  import type { ChatInventoryDTO, ProspectiveChatInventoryDTO, ProspectiveChatInventoryQuery } from './dto/chat-inventory.js';
17
17
  import type { CredentialRemovalResultDTO, CredentialResultDTO, InstallCredentialRequest, ModelAuthListDTO } from './dto/modelauth.js';
18
- import type { CreateHumanBridgeRequest, HumanBridgeResultDTO, HumanCancelRequest, HumanCancelResultDTO, HumanResolveRequest, HumanResolveResultDTO } from './dto/human.js';
19
18
  import type { CancelReviewRequest, CreateReviewRequest, ListReviewsQuery, ReviewCancelResultDTO, ReviewDocumentBaseDTO, ReviewDTO, ReviewListDTO, ReviewSubmitResultDTO } from './dto/reviews.js';
20
19
  import type { CreateReviewCommentRequest, EditReviewCommentRequest, ListReviewCommentsQuery, ReadReviewCommentEventsQuery, ReviewCommentActionRequest, ReviewCommentDetailDTO, ReviewCommentEventsDTO, ReviewCommentListDTO, ReviewCommentMutationDTO, ReviewCommentRangeBatchRequest, ReviewCommentRangeBatchResultDTO } from './dto/review-comments.js';
21
20
  import type { CancelInboxTicketRequest, CanceledTicketResultDTO, InboxListDTO, InboxPageDTO, InboxPageHistoryDTO, InboxPageResponseDTO, InboxTicketIdDTO, PageFeedbackResolutionDTO, PageResponsesDTO, PageTicketResultDTO, RespondInboxPageRequest } from './dto/inbox.js';
22
21
  import type { CreateHumanRequestDTO, CreateHumanRequestRequest, HumanRequestDTO, HumanRequestIdDTO, ReplaceHumanRequestRequest, RespondHumanRequestRequest, SettleHumanRequestRequest } from './dto/human-requests.js';
23
- import type { AttentionCountsDTO, AttentionDTO, DashboardDTO, DashboardQuery, HistoryGrepQuery, HistoryGrepResultDTO, HistoryReadQuery, HistoryStatsQuery, HistoryStatsResultDTO, HistoryReadResultDTO, HistorySearchQuery, HistorySearchResultDTO, PruneRequest, PruneResultDTO, RebuildIndexResultDTO, RosterDTO, SnapshotDTO } from './dto/canvas.js';
22
+ import type { AttentionCountsDTO, AttentionDTO, DashboardDTO, DashboardQuery, HistoryGrepQuery, HistoryGrepResultDTO, HistoryReadQuery, HistoryStatsQuery, HistoryStatsResultDTO, HistoryReadResultDTO, HistorySearchQuery, HistorySearchResultDTO, PruneRequest, PruneResultDTO, RosterDTO, SnapshotDTO } from './dto/canvas.js';
24
23
  import type { AbandonWorktreeRequest, AbandonWorktreeResultDTO, CloseWorktreeResultDTO, QuarantinedWorktreeDTO } from './dto/worktree.js';
25
- import type { BrokerExtensionStateDTO, BrokerGeneratedNameRequest, BrokerGeneratedNameResultDTO, BrokerInboxCursorDirective, BrokerInboxCursorRequest, BrokerModelCommitRequest, BrokerModelCommitResultDTO, BrokerParkActivityResultDTO, BrokerParkCompleteRequest, BrokerPersonaAckRequest, BrokerPersonaAckResultDTO, BrokerSessionBoundRequest, BrokerSessionBoundResultDTO, BrokerSettleDirective, BrokerSettleRequest } from './dto/broker-ops.js';
24
+ import type { BrokerExtensionStateDTO, BrokerExecutionRequest, BrokerGeneratedNameRequest, BrokerGeneratedNameResultDTO, BrokerModelCommitRequest, BrokerModelCommitResultDTO, BrokerParkActivityResultDTO, BrokerParkCompleteRequest, BrokerPersonaAckRequest, BrokerPersonaAckResultDTO, BrokerSessionBoundRequest, BrokerSessionBoundResultDTO, BrokerSettleDirective, BrokerSettleRequest } from './dto/broker-ops.js';
25
+ import type { AcknowledgeMailRequest, AcknowledgeMailResultDTO, ClaimMailRequest, ClaimMailResultDTO } from './dto/mail.js';
26
+ import type { BrokerFaultInputDTO, BrokerFaultRequest, BrokerFaultResultDTO, BrokerProviderRetryRequest, BrokerProviderRetryResultDTO, BrokerTurnRequest, BrokerTurnResultDTO, NodeFaultClearResultDTO, RecoveryStateDTO } from './dto/recovery.js';
26
27
  export interface CrtrClientOptions {
27
28
  /** Unix socket path (default local transport). Exactly one of socketPath|baseUrl. */
28
29
  socketPath?: string;
@@ -51,9 +52,6 @@ export interface CrtrClientOptions {
51
52
  * socket or interrupted response. Each probe is capped to the remaining
52
53
  * budget. Defaults to `HEALTHZ_POLL_WINDOW_MS`. */
53
54
  coldStartPollWindowMs?: number;
54
- /** Injected check for a standing startup block. After an attempted spawn,
55
- * its result supplements the observed transport failure with the known repair. */
56
- coldStartAbort?: () => string | null;
57
55
  }
58
56
  /** One strict wall-clock availability window shared by local API clients and
59
57
  * daemon management. Each probe gets only the budget remaining at its start. */
@@ -75,7 +73,6 @@ export declare class CrtrClient {
75
73
  private readonly onColdSocket?;
76
74
  private readonly coldStartDiagnostic?;
77
75
  private readonly coldStartPollWindowMs;
78
- private readonly coldStartAbort?;
79
76
  /** Guards against invoking the daemon-start hook more than once per client. */
80
77
  private coldStartAttempted;
81
78
  constructor(opts: CrtrClientOptions);
@@ -93,6 +90,8 @@ export declare class CrtrClient {
93
90
  * caller living inside a node the handover will tear down still gets a
94
91
  * settled result. */
95
92
  restartDaemon(): Promise<DaemonRestartDTO>;
93
+ admitDaemon(): Promise<DaemonAdmitDTO>;
94
+ migrateState(req: MigrateStateRequest): Promise<MigrateStateDTO>;
96
95
  createNode(req: CreateNodeRequest): Promise<NodeDetailDTO>;
97
96
  /** List canvas nodes with composable row filters. `include: 'activity'` adds
98
97
  * latest/canonical reports and pending-human counts in the same response. */
@@ -123,8 +122,18 @@ export declare class CrtrClient {
123
122
  bindBrokerSession(id: string, req: BrokerSessionBoundRequest): Promise<BrokerSessionBoundResultDTO>;
124
123
  settleBroker(id: string, req: BrokerSettleRequest): Promise<BrokerSettleDirective>;
125
124
  completeBrokerPark(id: string, req: BrokerParkCompleteRequest): Promise<BrokerSettleDirective>;
126
- recordBrokerParkActivity(id: string): Promise<BrokerParkActivityResultDTO>;
127
- advanceBrokerInboxCursor(id: string, req: BrokerInboxCursorRequest): Promise<BrokerInboxCursorDirective>;
125
+ recordBrokerParkActivity(id: string, req: BrokerExecutionRequest): Promise<BrokerParkActivityResultDTO>;
126
+ claimNodeMail(id: string, req: ClaimMailRequest): Promise<ClaimMailResultDTO>;
127
+ acknowledgeNodeMail(id: string, req: AcknowledgeMailRequest): Promise<AcknowledgeMailResultDTO>;
128
+ recordBrokerTurn(id: string, req: BrokerTurnRequest): Promise<BrokerTurnResultDTO>;
129
+ mutateBrokerProviderRetry(id: string, req: BrokerProviderRetryRequest): Promise<BrokerProviderRetryResultDTO>;
130
+ mutateBrokerFault(id: string, req: BrokerFaultRequest): Promise<BrokerFaultResultDTO>;
131
+ getBrokerRecovery(id: string, expectedExecutionId: string): Promise<RecoveryStateDTO>;
132
+ recordNodeFault(id: string, req: BrokerFaultInputDTO): Promise<BrokerFaultResultDTO>;
133
+ clearNodeFault(id: string, opts?: {
134
+ link?: BrokerFaultInputDTO['link'];
135
+ preserve_episode?: boolean;
136
+ }): Promise<NodeFaultClearResultDTO>;
128
137
  commitBrokerModel(id: string, req: BrokerModelCommitRequest): Promise<BrokerModelCommitResultDTO>;
129
138
  brokerExtensionState(id: string): Promise<BrokerExtensionStateDTO>;
130
139
  commitBrokerGeneratedName(id: string, req: BrokerGeneratedNameRequest): Promise<BrokerGeneratedNameResultDTO>;
@@ -212,20 +221,9 @@ export declare class CrtrClient {
212
221
  listModelAuth(): Promise<ModelAuthListDTO>;
213
222
  installCredential(provider: string, req: InstallCredentialRequest): Promise<CredentialResultDTO>;
214
223
  removeCredential(provider: string): Promise<CredentialRemovalResultDTO>;
215
- /** Create a terminal `kind:'human'` bridge node with NO broker engine
216
- * (`spawnNode` server-side). Distinct from `createNode` (which launches a
217
- * broker) precisely because a human bridge must never have one. */
218
- createHumanBridge(req: CreateHumanBridgeRequest): Promise<HumanBridgeResultDTO>;
219
- /** Resolve a page ticket answer. crtrd claims the ticket (taking over a live
220
- * inbox claim where one exists), publishes the canonical result, and follows
221
- * its optional reply route before this resolves. */
222
- resolveHumanTicket(ticketId: string, body: HumanResolveRequest): Promise<HumanResolveResultDTO>;
223
- /** Cancel a pending ticket and retire its optional reply bridge. */
224
- cancelHumanTicket(ticketId: string, body: HumanCancelRequest): Promise<HumanCancelResultDTO>;
225
224
  createReview(req: CreateReviewRequest): Promise<ReviewDTO>;
226
225
  listReviews(query?: ListReviewsQuery): Promise<ReviewListDTO>;
227
226
  getReview(reviewId: string): Promise<ReviewDTO>;
228
- getReviewByBridge(bridgeNodeId: string): Promise<ReviewDTO>;
229
227
  submitReview(reviewId: string): Promise<ReviewSubmitResultDTO>;
230
228
  cancelReview(reviewId: string, req?: CancelReviewRequest): Promise<ReviewCancelResultDTO>;
231
229
  getReviewDocumentBase(reviewId: string): Promise<ReviewDocumentBaseDTO>;
@@ -255,10 +253,10 @@ export declare class CrtrClient {
255
253
  getInboxResponse(ticketId: InboxTicketIdDTO): Promise<InboxPageResponseDTO>;
256
254
  /** Cancel a ticket (terminal response, never deletion). */
257
255
  cancelHumanInboxTicket(ticketId: InboxTicketIdDTO, request?: CancelInboxTicketRequest): Promise<CanceledTicketResultDTO>;
258
- /** Create one durable human request. Its `request_id` is the same opaque id
259
- * the inbox routes address, so the request and the inbox ticket are one
260
- * record. An unresolvable `action.name` is rejected before the page is
261
- * published, leaving no inbox row behind. */
256
+ /** Create one durable human request. The minted `request_id` identifies the
257
+ * request; inbox presentation has its own ticket id. An unresolvable
258
+ * `action.name` is rejected before the page is published, leaving no inbox
259
+ * row behind. */
262
260
  createHumanRequest(request: CreateHumanRequestRequest): Promise<CreateHumanRequestDTO>;
263
261
  /** Read one request: its current state, its answer when answered, and the
264
262
  * delivery state of its completion action when it bound one. */
@@ -310,7 +308,6 @@ export declare class CrtrClient {
310
308
  * enriched on-demand view. */
311
309
  canvasRoster(): Promise<RosterDTO>;
312
310
  prune(req: PruneRequest): Promise<PruneResultDTO>;
313
- rebuildIndex(): Promise<RebuildIndexResultDTO>;
314
311
  /** Raw request for routes not yet method-wrapped. Applies the same
315
312
  * autostart + error-mapping semantics. */
316
313
  request<T>(method: string, path: string, body?: unknown): Promise<T>;
@@ -318,10 +315,6 @@ export declare class CrtrClient {
318
315
  * JSON parse. Every wrapper goes through here; only non-JSON routes call it directly. */
319
316
  private send;
320
317
  private nodePath;
321
- /** Validate a human interaction id before route construction. Reply-bearing
322
- * tickets use bridge ids and standalone pages use page ids; both obey the
323
- * safe node-id segment contract. */
324
- private interactionPath;
325
318
  /** Validate a background job id before route construction. Job ids arrive
326
319
  * from the daemon's file-backed roster and must remain one path segment. */
327
320
  private jobPath;
@@ -329,6 +322,10 @@ export declare class CrtrClient {
329
322
  * it raw, so a value carrying `/`, whitespace or `?` would corrupt the
330
323
  * request line rather than 404 cleanly. Mirrors `nodePath`. */
331
324
  private cronPath;
325
+ /** Validate a request id before route construction. Requests use the daemon's
326
+ * node-id-shaped identifiers, while inbox presentation uses a separate ticket
327
+ * id. A malformed request id is a server-rejectable request. */
328
+ private requestPath;
332
329
  /** Validate an opaque inbox ticket id before route construction. A local
333
330
  * shape violation is a caller bug, not a server-rejectable request — throws
334
331
  * `TypeError` (matching the existing safe-segment discipline of a local
@@ -72,7 +72,6 @@ export class CrtrClient {
72
72
  onColdSocket;
73
73
  coldStartDiagnostic;
74
74
  coldStartPollWindowMs;
75
- coldStartAbort;
76
75
  /** Guards against invoking the daemon-start hook more than once per client. */
77
76
  coldStartAttempted = false;
78
77
  constructor(opts) {
@@ -92,8 +91,6 @@ export class CrtrClient {
92
91
  this.onColdSocket = opts.onColdSocket;
93
92
  if (opts.coldStartDiagnostic !== undefined)
94
93
  this.coldStartDiagnostic = opts.coldStartDiagnostic;
95
- if (opts.coldStartAbort !== undefined)
96
- this.coldStartAbort = opts.coldStartAbort;
97
94
  this.coldStartPollWindowMs = opts.coldStartPollWindowMs ?? HEALTHZ_POLL_WINDOW_MS;
98
95
  }
99
96
  /** Construct a client bound to the default local socket with autostart on. Pass
@@ -109,7 +106,21 @@ export class CrtrClient {
109
106
  /** One `/healthz` observation without cold-socket recovery. Availability
110
107
  * waiters own retry policy and pass their remaining wall-clock budget here. */
111
108
  async probeHealthz(timeoutMs) {
112
- return parse(await this.transport('GET', routes.healthz(), undefined, undefined, timeoutMs));
109
+ const response = await this.transport('GET', routes.healthz(), undefined, undefined, timeoutMs);
110
+ // A restricted daemon is alive and deliberately answers its health DTO with
111
+ // 503. Preserve that state for daemon startup management instead of treating
112
+ // the expected non-2xx status as a generic API failure.
113
+ if (response.status === 503) {
114
+ try {
115
+ const health = JSON.parse(response.text);
116
+ if (typeof health.startup_blocked === 'string')
117
+ return health;
118
+ }
119
+ catch {
120
+ // `parse` below reports malformed/non-health 503 responses normally.
121
+ }
122
+ }
123
+ return parse(response);
113
124
  }
114
125
  status() {
115
126
  return this.request('GET', routes.status());
@@ -121,6 +132,12 @@ export class CrtrClient {
121
132
  restartDaemon() {
122
133
  return this.request('POST', routes.daemonRestart());
123
134
  }
135
+ admitDaemon() {
136
+ return this.request('POST', routes.daemonAdmit());
137
+ }
138
+ migrateState(req) {
139
+ return this.request('POST', routes.daemonMigrate(), req);
140
+ }
124
141
  // Nodes
125
142
  createNode(req) {
126
143
  return this.request('POST', routes.nodes(), req);
@@ -191,11 +208,38 @@ export class CrtrClient {
191
208
  completeBrokerPark(id, req) {
192
209
  return this.request('POST', routes.nodeBrokerParkComplete(this.nodePath(id)), req);
193
210
  }
194
- recordBrokerParkActivity(id) {
195
- return this.request('POST', routes.nodeBrokerParkActivity(this.nodePath(id)), {});
211
+ recordBrokerParkActivity(id, req) {
212
+ return this.request('POST', routes.nodeBrokerParkActivity(this.nodePath(id)), req);
213
+ }
214
+ claimNodeMail(id, req) {
215
+ return this.request('POST', routes.nodeMailClaim(this.nodePath(id)), req);
216
+ }
217
+ acknowledgeNodeMail(id, req) {
218
+ return this.request('POST', routes.nodeMailAcknowledge(this.nodePath(id)), req);
219
+ }
220
+ recordBrokerTurn(id, req) {
221
+ return this.request('POST', routes.brokerTurn(this.nodePath(id)), req);
222
+ }
223
+ mutateBrokerProviderRetry(id, req) {
224
+ return this.request('POST', routes.brokerProviderRetry(this.nodePath(id)), req);
196
225
  }
197
- advanceBrokerInboxCursor(id, req) {
198
- return this.request('POST', routes.nodeBrokerInboxCursor(this.nodePath(id)), req);
226
+ mutateBrokerFault(id, req) {
227
+ return this.request('POST', routes.brokerFault(this.nodePath(id)), req);
228
+ }
229
+ getBrokerRecovery(id, expectedExecutionId) {
230
+ return this.request('GET', `${routes.brokerRecovery(this.nodePath(id))}?expected_execution_id=${encodeURIComponent(expectedExecutionId)}`);
231
+ }
232
+ recordNodeFault(id, req) {
233
+ return this.request('POST', routes.nodeFault(this.nodePath(id)), req);
234
+ }
235
+ clearNodeFault(id, opts = {}) {
236
+ const query = new URLSearchParams();
237
+ if (opts.link !== undefined)
238
+ query.set('link', opts.link);
239
+ if (opts.preserve_episode !== undefined)
240
+ query.set('preserve_episode', String(opts.preserve_episode));
241
+ const suffix = query.size === 0 ? '' : `?${query.toString()}`;
242
+ return this.request('DELETE', `${routes.nodeFault(this.nodePath(id))}${suffix}`);
199
243
  }
200
244
  commitBrokerModel(id, req) {
201
245
  return this.request('POST', routes.nodeBrokerModel(this.nodePath(id)), req);
@@ -400,23 +444,6 @@ export class CrtrClient {
400
444
  removeCredential(provider) {
401
445
  return this.request('DELETE', routes.modelAuth(provider));
402
446
  }
403
- // Human bridge + completion-handler forwarding (spec §6.5)
404
- /** Create a terminal `kind:'human'` bridge node with NO broker engine
405
- * (`spawnNode` server-side). Distinct from `createNode` (which launches a
406
- * broker) precisely because a human bridge must never have one. */
407
- createHumanBridge(req) {
408
- return this.request('POST', routes.humanBridge(), req);
409
- }
410
- /** Resolve a page ticket answer. crtrd claims the ticket (taking over a live
411
- * inbox claim where one exists), publishes the canonical result, and follows
412
- * its optional reply route before this resolves. */
413
- resolveHumanTicket(ticketId, body) {
414
- return this.request('POST', routes.humanTicketResolve(this.interactionPath(ticketId)), body);
415
- }
416
- /** Cancel a pending ticket and retire its optional reply bridge. */
417
- cancelHumanTicket(ticketId, body) {
418
- return this.request('POST', routes.humanTicketCancel(this.interactionPath(ticketId)), body);
419
- }
420
447
  // Daemon-owned document reviews and comments
421
448
  createReview(req) {
422
449
  return this.request('POST', routes.humanReviews(), req);
@@ -427,9 +454,6 @@ export class CrtrClient {
427
454
  getReview(reviewId) {
428
455
  return this.request('GET', routes.humanReview(this.reviewPath(reviewId)));
429
456
  }
430
- getReviewByBridge(bridgeNodeId) {
431
- return this.request('GET', routes.humanReviewByBridge(this.nodePath(bridgeNodeId)));
432
- }
433
457
  submitReview(reviewId) {
434
458
  return this.request('POST', routes.humanReviewSubmit(this.reviewPath(reviewId)), {});
435
459
  }
@@ -507,35 +531,35 @@ export class CrtrClient {
507
531
  return this.request('POST', routes.humanInboxCancel(this.ticketId(ticketId)), request ?? {});
508
532
  }
509
533
  // Durable programmatic human requests
510
- /** Create one durable human request. Its `request_id` is the same opaque id
511
- * the inbox routes address, so the request and the inbox ticket are one
512
- * record. An unresolvable `action.name` is rejected before the page is
513
- * published, leaving no inbox row behind. */
534
+ /** Create one durable human request. The minted `request_id` identifies the
535
+ * request; inbox presentation has its own ticket id. An unresolvable
536
+ * `action.name` is rejected before the page is published, leaving no inbox
537
+ * row behind. */
514
538
  createHumanRequest(request) {
515
539
  return this.request('POST', routes.humanRequests(), request);
516
540
  }
517
541
  /** Read one request: its current state, its answer when answered, and the
518
542
  * delivery state of its completion action when it bound one. */
519
543
  getHumanRequest(requestId) {
520
- return this.request('GET', routes.humanRequest(this.ticketId(requestId)));
544
+ return this.request('GET', routes.humanRequest(this.requestPath(requestId)));
521
545
  }
522
546
  /** Revise a pending request's page in place. Identity, provenance, and the
523
547
  * frozen action binding are preserved; a settled request refuses. */
524
548
  replaceHumanRequest(requestId, request) {
525
- return this.request('POST', routes.humanRequestReplace(this.ticketId(requestId)), request);
549
+ return this.request('POST', routes.humanRequestReplace(this.requestPath(requestId)), request);
526
550
  }
527
551
  /** Settle a request `answered` programmatically. Races a human answer to the
528
552
  * same first-writer-wins result. */
529
553
  respondHumanRequest(requestId, request) {
530
- return this.request('POST', routes.humanRequestRespond(this.ticketId(requestId)), request);
554
+ return this.request('POST', routes.humanRequestRespond(this.requestPath(requestId)), request);
531
555
  }
532
556
  /** The recipient surface closing a request without answering. */
533
557
  dismissHumanRequest(requestId, request = {}) {
534
- return this.request('POST', routes.humanRequestDismiss(this.ticketId(requestId)), request);
558
+ return this.request('POST', routes.humanRequestDismiss(this.requestPath(requestId)), request);
535
559
  }
536
560
  /** The requester withdrawing its own request. */
537
561
  cancelHumanRequest(requestId, request = {}) {
538
- return this.request('POST', routes.humanRequestCancel(this.ticketId(requestId)), request);
562
+ return this.request('POST', routes.humanRequestCancel(this.requestPath(requestId)), request);
539
563
  }
540
564
  /** Resolve one page feedback comment — the bound companion's report that it
541
565
  * has been dealt with. `nodeId` names the caller; the daemon refuses any
@@ -603,9 +627,6 @@ export class CrtrClient {
603
627
  prune(req) {
604
628
  return this.request('POST', routes.canvasPrune(), req);
605
629
  }
606
- rebuildIndex() {
607
- return this.request('POST', routes.canvasRebuildIndex(), {});
608
- }
609
630
  // Escape hatch
610
631
  /** Raw request for routes not yet method-wrapped. Applies the same
611
632
  * autostart + error-mapping semantics. */
@@ -640,15 +661,6 @@ export class CrtrClient {
640
661
  }
641
662
  return id;
642
663
  }
643
- /** Validate a human interaction id before route construction. Reply-bearing
644
- * tickets use bridge ids and standalone pages use page ids; both obey the
645
- * safe node-id segment contract. */
646
- interactionPath(id) {
647
- if (!isSafeNodeId(id)) {
648
- throw new ApiError(400, 'invalid_ticket_id', `invalid interaction ticket id: ${JSON.stringify(id)}`);
649
- }
650
- return id;
651
- }
652
664
  /** Validate a background job id before route construction. Job ids arrive
653
665
  * from the daemon's file-backed roster and must remain one path segment. */
654
666
  jobPath(id) {
@@ -666,6 +678,15 @@ export class CrtrClient {
666
678
  }
667
679
  return id;
668
680
  }
681
+ /** Validate a request id before route construction. Requests use the daemon's
682
+ * node-id-shaped identifiers, while inbox presentation uses a separate ticket
683
+ * id. A malformed request id is a server-rejectable request. */
684
+ requestPath(id) {
685
+ if (!isSafeNodeId(id)) {
686
+ throw new ApiError(400, 'invalid_request_id', `invalid request id: ${JSON.stringify(id)}`);
687
+ }
688
+ return id;
689
+ }
669
690
  /** Validate an opaque inbox ticket id before route construction. A local
670
691
  * shape violation is a caller bug, not a server-rejectable request — throws
671
692
  * `TypeError` (matching the existing safe-segment discipline of a local
@@ -784,9 +805,17 @@ export class CrtrClient {
784
805
  initialError,
785
806
  probe: async (timeoutMs) => {
786
807
  const response = await this.transport('GET', routes.healthz(), undefined, undefined, timeoutMs);
787
- if (response.status < 200 || response.status >= 300) {
788
- throw new ApiError(response.status, 'daemon_health_unavailable', `crtrd health check returned HTTP ${response.status}: ${response.text.slice(0, 500)}`);
808
+ if (response.status >= 200 && response.status < 300)
809
+ return;
810
+ try {
811
+ const health = JSON.parse(response.text);
812
+ if (typeof health.startup_blocked === 'string')
813
+ return;
789
814
  }
815
+ catch {
816
+ // The normal unavailable error below carries the response body.
817
+ }
818
+ throw new ApiError(response.status, 'daemon_health_unavailable', `crtrd health check returned HTTP ${response.status}: ${response.text.slice(0, 500)}`);
790
819
  },
791
820
  });
792
821
  }
@@ -804,9 +833,6 @@ export class CrtrClient {
804
833
  }
805
834
  catch (error) {
806
835
  if (startedHere) {
807
- const standingBlock = safeColdStartAbort(this.coldStartAbort);
808
- if (standingBlock !== null)
809
- throw new ApiError(503, 'daemon_unavailable', `crtrd cannot start: ${standingBlock}`);
810
836
  const diagnostic = safeColdStartDiagnostic(this.coldStartDiagnostic);
811
837
  if (diagnostic !== undefined) {
812
838
  const transportError = toTransportApiError(error);
@@ -874,19 +900,6 @@ function toTransportApiError(err) {
874
900
  function sleepMs(ms) {
875
901
  return new Promise((resolve) => setTimeout(resolve, ms));
876
902
  }
877
- /** Invoke the injected `coldStartDiagnostic` hook, treating a THROW the same
878
- * as an absent/undefined result — the contract `CrtrClientOptions` documents
879
- * ("a thrown/undefined result is treated as 'no diagnostic'"). */
880
- function safeColdStartAbort(hook) {
881
- if (hook === undefined)
882
- return null;
883
- try {
884
- return hook();
885
- }
886
- catch {
887
- return null;
888
- }
889
- }
890
903
  export function safeColdStartDiagnostic(hook) {
891
904
  if (hook === undefined)
892
905
  return undefined;
@@ -6,6 +6,7 @@ import type { NodeSubjectDTO } from './nodes.js';
6
6
  * review markers visible in Pi's current branch; crtrd owns the node binding
7
7
  * used to decide whether that branch is valid. */
8
8
  export interface BrokerSessionBoundRequest {
9
+ executionId: string;
9
10
  piSessionId: string;
10
11
  sessionFile: string | null;
11
12
  pid: number;
@@ -20,7 +21,10 @@ export interface BrokerSessionBoundResultDTO {
20
21
  /** `POST /v1/nodes/{id}/broker/settle` body. These are the facts only Pi can
21
22
  * know at its settlement boundary; crtrd reads all current canvas state and
22
23
  * selects the durable consequence. */
23
- export interface BrokerSettleRequest {
24
+ export interface BrokerExecutionRequest {
25
+ expected_execution_id: string;
26
+ }
27
+ export interface BrokerSettleRequest extends BrokerExecutionRequest {
24
28
  stopReason: string;
25
29
  backgroundJobsRunning: boolean;
26
30
  pushedFinal: boolean;
@@ -44,25 +48,11 @@ export interface BrokerParkActivityResultDTO {
44
48
  }
45
49
  /** `POST /v1/nodes/{id}/broker/park-complete` body. The isolated parking turn
46
50
  * has ended; crtrd alone decides whether its pending park still applies. */
47
- export interface BrokerParkCompleteRequest {
51
+ export interface BrokerParkCompleteRequest extends BrokerExecutionRequest {
48
52
  outcome: 'completed' | 'failed';
49
53
  }
50
- /** `POST /v1/nodes/{id}/broker/inbox-cursor` body. The watcher advances this
51
- * only after Pi has settled every handoff through the supplied physical entry.
52
- * `brokerPid` binds the commit to the broker generation crtrd currently owns. */
53
- export interface BrokerInboxCursorRequest {
54
- throughEntryId: string;
55
- brokerPid: number;
56
- }
57
- /** crtrd either commits the durable cursor or preserves it while a refresh
58
- * discards the current Pi conversation, so the next broker replays the entry. */
59
- export type BrokerInboxCursorDirective = {
60
- action: 'advanced';
61
- } | {
62
- action: 'hold_refresh';
63
- };
64
54
  /** Durable model recipe selected by the live broker after Pi accepts a model or thinking change. */
65
- export interface BrokerModelCommitRequest {
55
+ export interface BrokerModelCommitRequest extends BrokerExecutionRequest {
66
56
  spec: string;
67
57
  pinnedOverride?: boolean;
68
58
  userSelected: boolean;
@@ -133,7 +123,7 @@ export interface BrokerExtensionStateDTO {
133
123
  /** Guarded generated-label update. `initial` can only fill a blank generated
134
124
  * description; `recap` additionally compares the exact automatic-name snapshot
135
125
  * captured before the headless naming call. */
136
- export type BrokerGeneratedNameRequest = {
126
+ export type BrokerGeneratedNameRequest = BrokerExecutionRequest & ({
137
127
  kind: 'initial';
138
128
  description: string;
139
129
  title: string;
@@ -150,7 +140,7 @@ export type BrokerGeneratedNameRequest = {
150
140
  icon: string;
151
141
  kind: string;
152
142
  };
153
- };
143
+ });
154
144
  /** A daemon-selected Pi editor-label directive. No handler calls a broker.
155
145
  * `description`/`title`/`icon` are the values as STORED (an empty title or icon
156
146
  * is left unset), so the broker announces what the node now carries rather than
@@ -162,7 +152,7 @@ export interface BrokerGeneratedNameResultDTO {
162
152
  title?: string;
163
153
  icon?: string;
164
154
  }
165
- export interface BrokerPersonaAckRequest {
155
+ export interface BrokerPersonaAckRequest extends BrokerExecutionRequest {
166
156
  from: {
167
157
  kind: string;
168
158
  mode: 'base' | 'orchestrator';
@@ -242,8 +242,7 @@ export interface SnapshotDTO {
242
242
  subscriptions: Record<string, NodeIdDTO[]>;
243
243
  }
244
244
  /** One node row in the lean roster (`GET /v1/canvas/roster`). Carries only
245
- * columns already native to the `nodes` row — no meta.json read, no
246
- * process-liveness probe. `attention_count`/`last_activity`/`ctx_tokens`/
245
+ * stored node fields, without a process-liveness probe. `attention_count`/`last_activity`/`ctx_tokens`/
247
246
  * `streaming`/`hanging`/`viewed` stay on `SnapshotNodeDTO`; a roster consumer
248
247
  * that needs attention counts fetches them separately via the existing
249
248
  * `POST /v1/canvas/attention/counts` (already scoped to exactly the ids it
@@ -313,11 +312,3 @@ export interface PruneResultDTO {
313
312
  dry_run: boolean;
314
313
  ttl_days: number;
315
314
  }
316
- /** `POST /v1/canvas/rebuild-index` result. Fails loud server-side when metas
317
- * exist on disk but the rebuild yields zero rows (a corrupt/empty index). */
318
- export interface RebuildIndexResultDTO {
319
- /** How many `nodes/<id>/meta.json` files were found on disk. */
320
- metas: number;
321
- /** How many node rows exist in canvas.db after the rebuild. */
322
- rows: number;
323
- }
@@ -1,4 +1,6 @@
1
1
  import type { NodeStatusDTO } from './common.js';
2
+ /** Ephemeral daemon lifecycle phase reported by health and status. */
3
+ export type StartupPhaseDTO = 'initializing' | 'blocked' | 'prepared' | 'recovering' | 'ready';
2
4
  /** `GET /healthz` — substrate readiness probe. Versions are informational, never
3
5
  * a gate. Used by the provisioning ladder. */
4
6
  export interface HealthDTO {
@@ -6,6 +8,10 @@ export interface HealthDTO {
6
8
  daemon_up: boolean;
7
9
  store_accessible: boolean;
8
10
  brokers_reconciled: boolean;
11
+ /** Present while corpus migration or normal daemon startup is incomplete. */
12
+ startup_blocked?: string;
13
+ /** Always present; only `ready` admits normal daemon work. */
14
+ startup_phase: StartupPhaseDTO;
9
15
  }
10
16
  /** `GET /v1/status` — daemon status snapshot. Backs `crtr sys daemon status`
11
17
  * state and the `crtr canvas dashboard` header. `runtime_version` is also
@@ -24,6 +30,8 @@ export interface StatusDTO {
24
30
  };
25
31
  runtime_version: string;
26
32
  api_version: string;
33
+ /** Always present; only `ready` admits normal daemon work. */
34
+ startup_phase: StartupPhaseDTO;
27
35
  }
28
36
  /** `POST /v1/daemon/restart` — the ack for a daemon-owned handover.
29
37
  *
@@ -33,6 +41,111 @@ export interface StatusDTO {
33
41
  * `accepted` is therefore a promise about the future, not a completed action —
34
42
  * the successor daemon is spawned after `grace_ms`, and every node the
35
43
  * teardown interrupts is resumed by it. */
44
+ export interface MigrateStateRequest {
45
+ dry_run?: boolean;
46
+ dirs?: string[];
47
+ }
48
+ export interface MigrateStateDTO {
49
+ profiles: number;
50
+ stores: number;
51
+ changed: number;
52
+ applied: {
53
+ store: string;
54
+ lane: string;
55
+ migration: string;
56
+ files: string[];
57
+ }[];
58
+ skipped: {
59
+ store: string;
60
+ relPath: string;
61
+ error: string;
62
+ }[];
63
+ blockers: {
64
+ kind: string;
65
+ message: string;
66
+ store?: string;
67
+ target?: string;
68
+ path?: string;
69
+ name?: string;
70
+ paths?: readonly string[];
71
+ }[];
72
+ discovery_roots: {
73
+ reason: string;
74
+ root: string;
75
+ boundary: string;
76
+ detail?: string;
77
+ }[];
78
+ discovered_stores: {
79
+ store: string;
80
+ scope: string;
81
+ namespace: string;
82
+ mount_status: string;
83
+ writable: boolean;
84
+ reasons: string[];
85
+ targets: readonly string[];
86
+ docs: number;
87
+ }[];
88
+ discovered_targets: {
89
+ id: string;
90
+ kind: string;
91
+ origin: string;
92
+ cwd: string;
93
+ profile_id: string | null;
94
+ node_id: string | null;
95
+ stores: readonly string[];
96
+ }[];
97
+ repository_namespaces: {
98
+ repository_root: string;
99
+ repository_key: string;
100
+ declaration_path: string;
101
+ namespace: string;
102
+ source: string;
103
+ action: string;
104
+ }[];
105
+ store_prefixes: {
106
+ store_root: string;
107
+ owner_dir: string;
108
+ repository_root: string;
109
+ owner_relative_path: string;
110
+ old_prefix: string;
111
+ old_rule: string;
112
+ effective_prefix: string;
113
+ nested_declaration_action: string;
114
+ front_door_action: string;
115
+ }[];
116
+ renames: {
117
+ store_root: string;
118
+ path: string;
119
+ from: string;
120
+ to: string;
121
+ }[];
122
+ references: {
123
+ store_root: string;
124
+ path: string;
125
+ kind: string;
126
+ from: string;
127
+ to: string;
128
+ }[];
129
+ dryRun: boolean;
130
+ blocked: boolean;
131
+ corpus_phase: 'not-started' | 'planned' | 'written';
132
+ wrote: {
133
+ profile_manifests: string[];
134
+ documents: string[];
135
+ corpus_consumers: string[];
136
+ corpus_identities: string[];
137
+ };
138
+ resumed: boolean;
139
+ resume_error?: string;
140
+ }
141
+ /** `POST /v1/daemon/admit` — opens normal daemon work after preparation. */
142
+ export interface DaemonAdmitDTO {
143
+ /** True only for the request that began admission. */
144
+ admitted: boolean;
145
+ /** True when another request had already begun or completed admission. */
146
+ already_admitted: boolean;
147
+ startup_phase: StartupPhaseDTO;
148
+ }
36
149
  export interface DaemonRestartDTO {
37
150
  accepted: boolean;
38
151
  /** The daemon that took the request and is about to hand over. */
@@ -1,7 +1,7 @@
1
1
  import type { IsoTime } from './common.js';
2
- import type { InboxTicketIdDTO, PageResponsesDTO, TicketSourceDTO } from './inbox.js';
3
- /** A request id IS an opaque inbox ticket id: lowercase 64-hex. */
4
- export type HumanRequestIdDTO = InboxTicketIdDTO;
2
+ import type { InboxPageDTO, PageResponsesDTO, TicketSourceDTO } from './inbox.js';
3
+ /** Daemon-minted request identity. Inbox presentation has a separate opaque ticket id. */
4
+ export type HumanRequestIdDTO = string;
5
5
  /** The complete authored page text, inline — never a path. */
6
6
  export interface HumanRequestPageDTO {
7
7
  dialect: 'jsx' | 'html';
@@ -22,6 +22,8 @@ export interface CreateHumanRequestRequest {
22
22
  page: HumanRequestPageDTO;
23
23
  delivery?: HumanRequestDeliveryDTO;
24
24
  source?: TicketSourceDTO;
25
+ /** The one node that receives the terminal result. Required for reply-bearing pages. */
26
+ requester_node_id?: string | null;
25
27
  creator_cwd: string;
26
28
  action?: HumanRequestActionDTO;
27
29
  }
@@ -29,6 +31,8 @@ export type HumanRequestState = 'pending' | 'answered' | 'dismissed' | 'canceled
29
31
  export type HumanRequestDeliveryState = 'none' | 'pending' | 'running' | 'accepted' | 'permanent_failed';
30
32
  export interface CreateHumanRequestDTO {
31
33
  request_id: HumanRequestIdDTO;
34
+ /** Daemon-owned directory where the request's rendered page is projected. */
35
+ request_dir: string;
32
36
  state: 'pending';
33
37
  /** Omitted entirely when the request carries no action binding. */
34
38
  action?: {
@@ -56,6 +60,11 @@ export interface HumanRequestDeliveryDetailDTO {
56
60
  }
57
61
  export interface HumanRequestDTO {
58
62
  request_id: HumanRequestIdDTO;
63
+ /** Opaque inbox identity for inbox-only routes. */
64
+ inbox_ticket_id: string;
65
+ /** Daemon-owned directory where the request's rendered page is projected. */
66
+ request_dir: string;
67
+ kind: 'page' | 'review';
59
68
  state: HumanRequestState;
60
69
  title: string;
61
70
  subtitle?: string;
@@ -73,6 +82,8 @@ export interface HumanRequestDTO {
73
82
  payload: unknown;
74
83
  };
75
84
  delivery?: HumanRequestDeliveryDetailDTO;
85
+ /** Present exactly when this request is a page. */
86
+ page?: InboxPageDTO;
76
87
  }
77
88
  export interface ReplaceHumanRequestRequest {
78
89
  page: HumanRequestPageDTO;
@@ -1,26 +1,16 @@
1
- import type { NodeIdDTO } from './common.js';
2
- /** One anchored review comment, mirroring crouter's ticket-store shape as a
3
- * plain structural wire type (no store import). */
1
+ /** One anchored review comment, mirroring crouter's canonical review result. */
4
2
  export interface FeedbackCommentDTO {
5
3
  id: string;
6
- /** 1-based source line where the comment is anchored (start). */
7
4
  line: number;
8
- /** 1-based source line where the anchored range ends (== line for one line). */
9
5
  endLine: number;
10
- /** Exact selected substring when the human made a visual selection. */
11
6
  quote?: string;
12
- /** 0-based byte column where a partial (charwise) selection starts on `line`. */
13
7
  colStart?: number;
14
- /** 0-based exclusive byte column where the selection ends on `endLine`. */
15
8
  colEnd?: number;
16
- /** Full source text of the anchored line(s) — context for the agent. */
17
9
  lineText: string;
18
10
  comment: string;
19
11
  createdAt: string;
20
12
  }
21
- /** Immutable daemon-projected approval result, mirroring crouter's canonical
22
- * review result as a plain structural wire type — `src/api/**` never imports
23
- * the store's own type. */
13
+ /** Immutable daemon-projected review approval result. */
24
14
  export interface FeedbackResultDTO {
25
15
  file: string;
26
16
  submitted: true;
@@ -36,39 +26,3 @@ export interface FeedbackResultDTO {
36
26
  commentsTotal: number;
37
27
  commentsUnresolved: number;
38
28
  }
39
- /** `POST /v1/human/tickets/{ticket_id}/resolve` body — a page answer keyed by slot id. */
40
- export interface HumanResolveRequest {
41
- responses: Record<string, Record<string, unknown>>;
42
- }
43
- /** `POST /v1/human/tickets/{ticket_id}/cancel` body. */
44
- export interface HumanCancelRequest {
45
- reason?: string;
46
- actor?: string;
47
- /** The recipient surface closing it sends `dismissed`; a requester retracting
48
- * it sends `canceled` or omits the field. */
49
- disposition?: 'canceled' | 'dismissed';
50
- }
51
- /** `POST /v1/human/tickets/{ticket_id}/resolve` result. */
52
- export interface HumanResolveResultDTO {
53
- completed: true;
54
- reply_delivered: boolean;
55
- kind: 'page';
56
- }
57
- /** `POST /v1/human/tickets/{ticket_id}/cancel` result. */
58
- export interface HumanCancelResultDTO {
59
- canceled: true;
60
- }
61
- /** `POST /v1/human/bridge` body — create a terminal `kind:'human'` bridge node
62
- * via `spawnNode` (NO broker engine). Distinct from `POST /v1/nodes`, whose
63
- * `spawnChild` always launches a broker the bridge must never have. */
64
- export interface CreateHumanBridgeRequest {
65
- kind: 'human';
66
- parent: NodeIdDTO | null;
67
- cwd: string;
68
- name: string;
69
- }
70
- /** `POST /v1/human/bridge` result — the minted bridge node id + its name. */
71
- export interface HumanBridgeResultDTO {
72
- node_id: NodeIdDTO;
73
- name: string;
74
- }
@@ -1,3 +1,4 @@
1
- // Human-bridge and crouter ticket-store DTOs the canvas-facing wire shapes
2
- // for bridge work and ticket resolution through crtrd.
1
+ // Shared human-review result DTOs. Page and review lifecycle contracts live in
2
+ // their request-specific modules; bridge and duplicate ticket settlement types
3
+ // are intentionally absent.
3
4
  export {};
@@ -10,6 +10,10 @@ export interface TicketSourceDTO {
10
10
  nodeId?: string;
11
11
  }
12
12
  export interface ReviewTicketSummaryDTO {
13
+ /** Request identity for direct request/review routes. */
14
+ request_id: string;
15
+ /** Daemon-owned projection directory. */
16
+ request_dir: string;
13
17
  ticket_id: InboxTicketIdDTO;
14
18
  kind: 'review';
15
19
  title: string;
@@ -33,6 +37,10 @@ export interface PageFastActionDTO {
33
37
  responses: PageResponsesDTO;
34
38
  }
35
39
  export interface PageTicketSummaryDTO {
40
+ /** Request identity for direct request routes. */
41
+ request_id: string;
42
+ /** Daemon-owned projection directory. */
43
+ request_dir: string;
36
44
  ticket_id: InboxTicketIdDTO;
37
45
  kind: 'page';
38
46
  title: string;
@@ -118,13 +126,19 @@ export interface InboxPageDTO {
118
126
  ticket_id: InboxTicketIdDTO;
119
127
  kind: 'page';
120
128
  state: 'pending' | 'resolved' | 'canceled' | 'passive';
129
+ /** Opaque SQL-selected immutable presentation revision. */
130
+ revision: string;
121
131
  page: PageManifestDTO;
122
132
  /** JSX is compiled for the host; HTML is the authored document verbatim. */
123
133
  document: string;
134
+ /** Authored JSX or HTML, distinct from the compiled JSX host document. */
135
+ source_document: string;
124
136
  document_media_type: 'text/jsx' | 'text/html';
125
137
  progress: {
126
138
  responses: PageResponsesDTO;
127
139
  } | null;
140
+ /** Canonical stored terminal result, or null while the ticket remains open. */
141
+ result: PageTicketResultDTO | CanceledTicketResultDTO | null;
128
142
  /** Ticket-attached feedback comments and the companion conversation they
129
143
  * deliver to. Comments are immutable once sent — open until the companion
130
144
  * resolves them. */
@@ -0,0 +1,43 @@
1
+ export interface MailEntryDTO {
2
+ entry_id: string;
3
+ operation_id: string;
4
+ ts: string;
5
+ from: string | null;
6
+ from_name?: string;
7
+ tier: 'critical' | 'urgent' | 'normal' | 'deferred';
8
+ kind: 'update' | 'urgent' | 'final' | 'message' | 'completed';
9
+ ref?: string;
10
+ label: string;
11
+ data?: Record<string, unknown>;
12
+ disposition?: 'human-answer' | 'human-canceled';
13
+ }
14
+ export interface ClaimedMailDTO {
15
+ delivery_id: string;
16
+ attempts: number;
17
+ handoff: 'fresh' | 'inherited';
18
+ entry: MailEntryDTO;
19
+ }
20
+ export interface ClaimMailRequest {
21
+ execution_id: string;
22
+ channel: 'wake' | 'passive';
23
+ }
24
+ export interface ClaimMailResultDTO {
25
+ action: 'claimed' | 'hold';
26
+ deliveries: ClaimedMailDTO[];
27
+ }
28
+ export interface MailReceiptDTO {
29
+ delivery_id: string;
30
+ attempts: number;
31
+ disposition: 'acknowledged' | 'released';
32
+ }
33
+ export interface AcknowledgeMailRequest {
34
+ execution_id: string;
35
+ deliveries: MailReceiptDTO[];
36
+ }
37
+ export interface AcknowledgeMailResultDTO {
38
+ action: 'acknowledged' | 'hold';
39
+ results: Array<{
40
+ delivery_id: string;
41
+ outcome: 'acknowledged' | 'released' | 'stale';
42
+ }>;
43
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,7 @@
1
1
  import type { Cursor, ExitIntentDTO, IsoTime, LifecycleDTO, ModeDTO, NodeIdDTO, NodeStatusDTO, TerminalReasonDTO } from './common.js';
2
2
  import type { ReportDTO } from './reports.js';
3
3
  import type { NodeOutcomeDTO, RegisterOutcomeDeliveryRequest } from './node-outcomes.js';
4
+ import type { FaultLink, FaultKind, FaultRetry, FaultProviderError } from './recovery.js';
4
5
  /** `GET /v1/nodes/{id}/subject` — the node-config subject substrate gate
5
6
  * predicates evaluate against. Mirrors `NodeConfigSubject`; this narrow
6
7
  * endpoint exists so a CLI process (the `memory read` leaf) can gate-check
@@ -105,6 +106,8 @@ export interface NodeSummaryDTO {
105
106
  finalized_at: IsoTime | null;
106
107
  deadline_at: IsoTime | null;
107
108
  outcome: NodeOutcomeDTO | null;
109
+ fault?: NodeFaultDTO | null;
110
+ streaming?: boolean;
108
111
  /** Present only when requested with `include=activity`. */
109
112
  activity?: NodeActivityDTO;
110
113
  }
@@ -135,7 +138,6 @@ export interface NodePathsDTO {
135
138
  context_dir: string;
136
139
  reports_dir: string;
137
140
  meta_path: string;
138
- inbox_path: string;
139
141
  transcript_path: string;
140
142
  view_socket: string;
141
143
  }
@@ -172,12 +174,15 @@ export interface NodeReviewBindingDTO {
172
174
  }
173
175
  /** The active fault projection shown by node inspection. */
174
176
  export interface NodeFaultDTO {
175
- link: string;
176
- kind: string;
177
- retry: {
178
- disposition: string;
179
- };
177
+ link: FaultLink;
178
+ op: string;
179
+ kind: FaultKind;
180
+ retry: FaultRetry;
181
+ message: string;
180
182
  since: IsoTime;
183
+ operation_id: string;
184
+ anchorEntryId?: string;
185
+ providerError?: FaultProviderError;
181
186
  }
182
187
  /** The full node view — summary ∪ identity extras ∪ edges ∪ paths. Returned by
183
188
  * `GET /v1/nodes/{id}` and by the create/lifecycle actions that yield a node. */
@@ -212,8 +217,6 @@ export interface NodeDetailDTO extends NodeSummaryDTO {
212
217
  edges: NodeEdgesDTO;
213
218
  paths: NodePathsDTO;
214
219
  worktree?: NodeWorktreeDTO | null;
215
- /** The currently active runtime fault, when one is present. */
216
- fault?: NodeFaultDTO | null;
217
220
  /** Present only on a `POST /promote` response — the roadmap/goal facts the
218
221
  * promote primitive returns beyond the node meta (spec §6.2). A plain detail
219
222
  * read omits them. */
@@ -0,0 +1,96 @@
1
+ export type FaultLink = 'pi→provider' | 'viewer↔broker' | 'relay↔broker' | 'viewer↔crtrd' | 'daemon→node' | 'crtr→pi';
2
+ export type FaultKind = 'rate-limit' | 'overloaded' | 'connection' | 'auth' | 'protocol' | 'context-overflow' | 'other' | 'wedged' | 'model-not-found';
3
+ export interface FaultRetry {
4
+ disposition: 'auto' | 'manual' | 'fatal';
5
+ by?: 'sdk' | 'daemon' | 'client';
6
+ attempt?: number;
7
+ max?: number;
8
+ nextAt?: string;
9
+ }
10
+ export interface FaultProviderError {
11
+ provider: string;
12
+ model: string;
13
+ status?: number;
14
+ code?: string | number;
15
+ errno?: string;
16
+ body?: string;
17
+ causes?: Array<{
18
+ message?: string;
19
+ status?: number;
20
+ code?: string | number;
21
+ errno?: string;
22
+ body?: string;
23
+ }>;
24
+ }
25
+ export interface RecoveryFaultDTO {
26
+ fault_id: string;
27
+ link: FaultLink;
28
+ op: string;
29
+ kind: FaultKind;
30
+ retry: FaultRetry;
31
+ message: string;
32
+ since: string;
33
+ operation_id: string;
34
+ anchor_entry_id?: string;
35
+ provider_error?: FaultProviderError;
36
+ }
37
+ export interface ExecutionExitDiagnosticDTO {
38
+ schema: 'crtr.execution-exit-diagnostic/v1';
39
+ transport_poison: {
40
+ consecutive_passes: number;
41
+ routes: string[];
42
+ };
43
+ }
44
+ export interface BrokerTurnResultDTO {
45
+ applied: boolean;
46
+ }
47
+ export interface BrokerTurnRequest {
48
+ expected_execution_id: string;
49
+ event: 'started' | 'progress' | 'closed' | 'interrupted' | 'exit-diagnostic';
50
+ end_reason?: 'settled' | 'teardown-aborted';
51
+ exit_diagnostic?: ExecutionExitDiagnosticDTO;
52
+ }
53
+ export interface BrokerFaultInputDTO {
54
+ link: FaultLink;
55
+ op: string;
56
+ kind: FaultKind;
57
+ retry: FaultRetry;
58
+ message: string;
59
+ operation_id?: string;
60
+ since?: string;
61
+ anchor_entry_id?: string;
62
+ provider_error?: FaultProviderError;
63
+ }
64
+ export interface BrokerFaultResultDTO {
65
+ fault: RecoveryFaultDTO | null;
66
+ }
67
+ export interface BrokerFaultRequest {
68
+ expected_execution_id: string;
69
+ op: 'record' | 'clear';
70
+ link?: FaultLink;
71
+ preserve_episode?: boolean;
72
+ input?: BrokerFaultInputDTO;
73
+ }
74
+ export interface BrokerProviderRetryResultDTO {
75
+ applied: boolean;
76
+ episode: RecoveryStateDTO['episode'];
77
+ }
78
+ export interface BrokerProviderRetryRequest {
79
+ expected_execution_id: string;
80
+ op: 'record-pending' | 'admit' | 'reopen' | 'invalidate' | 'clear';
81
+ session_id: string;
82
+ fault_id?: string;
83
+ retry?: FaultRetry;
84
+ input?: BrokerFaultInputDTO;
85
+ }
86
+ export interface RecoveryStateDTO {
87
+ fault: RecoveryFaultDTO | null;
88
+ episode: {
89
+ state: 'pending' | 'admitted' | 'invalidated';
90
+ session_id: string | null;
91
+ fault: RecoveryFaultDTO;
92
+ } | null;
93
+ }
94
+ export interface NodeFaultClearResultDTO {
95
+ cleared: boolean;
96
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -26,8 +26,8 @@ export interface ReviewDTO {
26
26
  origin_kind: ReviewOriginKindDTO;
27
27
  /** Daemon-recorded originating node. */
28
28
  origin_node_id: NodeIdDTO;
29
- /** Daemon-recorded ticket bridge, when this review came from a ticket. */
30
- bridge_node_id: NodeIdDTO | null;
29
+ /** Daemon-owned directory containing the review branch and terminal result. */
30
+ review_dir: string;
31
31
  /** Daemon-preallocated companion node. */
32
32
  companion_node_id: NodeIdDTO;
33
33
  /** Daemon-canonicalized reviewed file path. */
@@ -5,6 +5,8 @@ export declare const routes: {
5
5
  readonly healthz: () => string;
6
6
  readonly status: () => string;
7
7
  readonly daemonRestart: () => string;
8
+ readonly daemonMigrate: () => string;
9
+ readonly daemonAdmit: () => string;
8
10
  readonly nodes: () => string;
9
11
  readonly reviveAll: () => string;
10
12
  readonly node: (id: string) => string;
@@ -31,11 +33,17 @@ export declare const routes: {
31
33
  readonly nodeBrokerSettle: (id: string) => string;
32
34
  readonly nodeBrokerParkComplete: (id: string) => string;
33
35
  readonly nodeBrokerParkActivity: (id: string) => string;
34
- readonly nodeBrokerInboxCursor: (id: string) => string;
36
+ readonly nodeMailClaim: (id: string) => string;
37
+ readonly nodeMailAcknowledge: (id: string) => string;
35
38
  readonly nodeBrokerModel: (id: string) => string;
36
39
  readonly nodeBrokerExtensionState: (id: string) => string;
37
40
  readonly nodeBrokerGeneratedName: (id: string) => string;
38
41
  readonly nodeBrokerPersonaAck: (id: string) => string;
42
+ readonly brokerTurn: (id: string) => string;
43
+ readonly brokerProviderRetry: (id: string) => string;
44
+ readonly brokerFault: (id: string) => string;
45
+ readonly brokerRecovery: (id: string) => string;
46
+ readonly nodeFault: (id: string) => string;
39
47
  readonly nodeClose: (id: string) => string;
40
48
  readonly nodeRecycle: (id: string) => string;
41
49
  readonly nodeDemote: (id: string) => string;
@@ -68,13 +76,8 @@ export declare const routes: {
68
76
  readonly canvasSnapshot: () => string;
69
77
  readonly canvasRoster: () => string;
70
78
  readonly canvasPrune: () => string;
71
- readonly canvasRebuildIndex: () => string;
72
- readonly humanBridge: () => string;
73
- readonly humanTicketResolve: (ticketId: string) => string;
74
- readonly humanTicketCancel: (ticketId: string) => string;
75
79
  readonly humanReviews: () => string;
76
80
  readonly humanReview: (reviewId: string) => string;
77
- readonly humanReviewByBridge: (bridgeNodeId: string) => string;
78
81
  readonly humanReviewSubmit: (reviewId: string) => string;
79
82
  readonly humanReviewCancel: (reviewId: string) => string;
80
83
  readonly humanReviewDocument: (reviewId: string) => string;
@@ -19,6 +19,8 @@ export const routes = {
19
19
  healthz: () => '/healthz',
20
20
  status: () => `${V}/status`,
21
21
  daemonRestart: () => `${V}/daemon/restart`,
22
+ daemonMigrate: () => `${V}/daemon/migrate`,
23
+ daemonAdmit: () => `${V}/daemon/admit`,
22
24
  // Nodes — collection + item
23
25
  nodes: () => `${V}/nodes`,
24
26
  reviveAll: () => `${V}/nodes/revive-all`,
@@ -49,11 +51,17 @@ export const routes = {
49
51
  nodeBrokerSettle: (id) => `${V}/nodes/${id}/broker/settle`,
50
52
  nodeBrokerParkComplete: (id) => `${V}/nodes/${id}/broker/park-complete`,
51
53
  nodeBrokerParkActivity: (id) => `${V}/nodes/${id}/broker/park-activity`,
52
- nodeBrokerInboxCursor: (id) => `${V}/nodes/${id}/broker/inbox-cursor`,
54
+ nodeMailClaim: (id) => `${V}/nodes/${id}/mail/claim`,
55
+ nodeMailAcknowledge: (id) => `${V}/nodes/${id}/mail/acknowledge`,
53
56
  nodeBrokerModel: (id) => `${V}/nodes/${id}/broker/model`,
54
57
  nodeBrokerExtensionState: (id) => `${V}/nodes/${id}/broker/extension-state`,
55
58
  nodeBrokerGeneratedName: (id) => `${V}/nodes/${id}/broker/generated-name`,
56
59
  nodeBrokerPersonaAck: (id) => `${V}/nodes/${id}/broker/persona-ack`,
60
+ brokerTurn: (id) => `${V}/broker/${id}/turn`,
61
+ brokerProviderRetry: (id) => `${V}/broker/${id}/provider-retry`,
62
+ brokerFault: (id) => `${V}/broker/${id}/fault`,
63
+ brokerRecovery: (id) => `${V}/broker/${id}/recovery`,
64
+ nodeFault: (id) => `${V}/nodes/${id}/fault`,
57
65
  nodeClose: (id) => `${V}/nodes/${id}/close`,
58
66
  nodeRecycle: (id) => `${V}/nodes/${id}/recycle`,
59
67
  nodeDemote: (id) => `${V}/nodes/${id}/demote`,
@@ -90,17 +98,10 @@ export const routes = {
90
98
  canvasSnapshot: () => `${V}/canvas/snapshot`,
91
99
  canvasRoster: () => `${V}/canvas/roster`,
92
100
  canvasPrune: () => `${V}/canvas/prune`,
93
- canvasRebuildIndex: () => `${V}/canvas/rebuild-index`,
94
- // Human bridge creation + the pinned ticket route table (design §"The `/v1`
95
- // surface") — every ticket route is addressed by node id only.
96
- humanBridge: () => `${V}/human/bridge`,
97
- humanTicketResolve: (ticketId) => `${V}/human/tickets/${ticketId}/resolve`,
98
- humanTicketCancel: (ticketId) => `${V}/human/tickets/${ticketId}/cancel`,
99
101
  // Daemon-owned document reviews and comments. All interpolated ids are
100
102
  // guarded by `CrtrClient` before they reach these pure builders.
101
103
  humanReviews: () => `${V}/human/reviews`,
102
104
  humanReview: (reviewId) => `${V}/human/reviews/${reviewId}`,
103
- humanReviewByBridge: (bridgeNodeId) => `${V}/human/reviews/by-bridge/${bridgeNodeId}`,
104
105
  humanReviewSubmit: (reviewId) => `${V}/human/reviews/${reviewId}/submit`,
105
106
  humanReviewCancel: (reviewId) => `${V}/human/reviews/${reviewId}/cancel`,
106
107
  humanReviewDocument: (reviewId) => `${V}/human/reviews/${reviewId}/document`,
@@ -120,9 +121,8 @@ export const routes = {
120
121
  humanInboxResponse: (ticketId) => `${V}/human/inbox/${ticketId}/response`,
121
122
  humanInboxCancel: (ticketId) => `${V}/human/inbox/${ticketId}/cancel`,
122
123
  humanInboxFeedbackResolve: (ticketId, commentId) => `${V}/human/inbox/${ticketId}/feedback-comments/${commentId}/resolve`,
123
- // Durable programmatic human requests. `request_id` is the same opaque
124
- // inbox ticket id the `/v1/human/inbox` routes address, so one request is one
125
- // record across both surfaces.
124
+ // Durable programmatic human requests. A request id is separate from the
125
+ // opaque inbox ticket id used by `/v1/human/inbox`.
126
126
  humanRequests: () => `${V}/human/requests`,
127
127
  humanRequest: (requestId) => `${V}/human/requests/${requestId}`,
128
128
  humanRequestReplace: (requestId) => `${V}/human/requests/${requestId}/replace`,
@@ -1,5 +1,6 @@
1
1
  /** The current node's id (`CRTR_NODE_ID`), or undefined outside a node. */
2
2
  export declare function envNodeId(): string | undefined;
3
+ export declare function envExecutionId(): string | undefined;
3
4
  /** The current node's target profile id (`CRTR_PROFILE_ID`), or undefined. */
4
5
  export declare function envProfileId(): string | undefined;
5
6
  /** The current node's working directory (`CRTR_NODE_CWD`), or undefined. */
@@ -26,6 +26,9 @@
26
26
  export function envNodeId() {
27
27
  return process.env['CRTR_NODE_ID'];
28
28
  }
29
+ export function envExecutionId() {
30
+ return process.env['CRTR_EXECUTION_ID'];
31
+ }
29
32
  /** The current node's target profile id (`CRTR_PROFILE_ID`), or undefined. */
30
33
  export function envProfileId() {
31
34
  return process.env['CRTR_PROFILE_ID'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/crouter-api",
3
- "version": "0.3.312",
3
+ "version": "0.3.313",
4
4
  "description": "Typed crtrd /v1 API contract — DTOs, route builders, the error contract, the CrtrClient, and the command-plugin manifest format. Zero runtime dependencies.",
5
5
  "type": "module",
6
6
  "main": "./dist/api/index.js",