@north-light/crouter-api 0.3.192 → 0.3.194

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,5 @@
1
1
  import type { DaemonRestartDTO, HealthDTO, StatusDTO } from './dto/health.js';
2
+ import type { BashJobStatusDTO, BashJobStopResultDTO } from './dto/bash-jobs.js';
2
3
  import type { ArtifactListDTO, ArtifactsQuery, ContextListDTO, CreateNodeRequest, ListNodesQuery, NodeDetailDTO, NodeSessionDTO, NodeSnapshotDTO, NodeSummaryDTO, TranscriptDTO, TranscriptQuery } from './dto/nodes.js';
3
4
  import type { InterruptResultDTO, MessageResultDTO, SendMessageRequest } from './dto/messages.js';
4
5
  import type { PushReportRequest, PushReportResultDTO, ReportDTO, ReportsQuery } from './dto/reports.js';
@@ -15,7 +16,7 @@ import type { CredentialRemovalResultDTO, CredentialResultDTO, InstallCredential
15
16
  import type { CreateHumanBridgeRequest, HumanBridgeResultDTO, HumanCancelRequest, HumanCancelResultDTO, HumanResolveRequest, HumanResolveResultDTO } from './dto/human.js';
16
17
  import type { CancelReviewRequest, CreateReviewRequest, ListReviewsQuery, ReviewCancelResultDTO, ReviewDocumentBaseDTO, ReviewDTO, ReviewListDTO, ReviewSubmitResultDTO } from './dto/reviews.js';
17
18
  import type { CreateReviewCommentRequest, EditReviewCommentRequest, ListReviewCommentsQuery, ReadReviewCommentEventsQuery, ReviewCommentActionRequest, ReviewCommentDetailDTO, ReviewCommentEventsDTO, ReviewCommentListDTO, ReviewCommentMutationDTO, ReviewCommentRangeBatchRequest, ReviewCommentRangeBatchResultDTO } from './dto/review-comments.js';
18
- import type { CancelInboxTicketRequest, CanceledTicketResultDTO, DeckTicketResultDTO, InboxDeckDTO, InboxListDTO, InboxTicketIdDTO, RespondInboxDeckRequest } from './dto/inbox.js';
19
+ import type { CancelInboxTicketRequest, CanceledTicketResultDTO, InboxListDTO, InboxPageDTO, InboxPageHistoryDTO, InboxPageRenderDTO, PagesBundleAssetDTO, InboxTicketIdDTO, PageResponsesDTO, PageTicketResultDTO, RespondInboxPageRequest } from './dto/inbox.js';
19
20
  import type { AttentionCountsDTO, AttentionDTO, DashboardDTO, DashboardQuery, HistoryGrepQuery, HistoryGrepResultDTO, HistoryReadQuery, HistoryReadResultDTO, HistorySearchQuery, HistorySearchResultDTO, PruneRequest, PruneResultDTO, RebuildIndexResultDTO, RosterDTO, SnapshotDTO } from './dto/canvas.js';
20
21
  import type { CloseWorktreeResultDTO } from './dto/worktree.js';
21
22
  import type { BrokerExtensionStateDTO, BrokerGeneratedNameRequest, BrokerGeneratedNameResultDTO, BrokerInboxCursorDirective, BrokerInboxCursorRequest, BrokerModelCommitRequest, BrokerModelCommitResultDTO, BrokerPersonaAckRequest, BrokerSessionBoundRequest, BrokerSessionBoundResultDTO, BrokerSettleDirective, BrokerSettleRequest } from './dto/broker-ops.js';
@@ -79,6 +80,8 @@ export declare class CrtrClient {
79
80
  createNode(req: CreateNodeRequest): Promise<NodeDetailDTO>;
80
81
  listNodes(q?: ListNodesQuery): Promise<NodeSummaryDTO[]>;
81
82
  getNode(id: string): Promise<NodeDetailDTO>;
83
+ listBashJobs(id: string): Promise<BashJobStatusDTO[]>;
84
+ stopBashJob(id: string, jobId: string): Promise<BashJobStopResultDTO>;
82
85
  sendMessage(id: string, req: SendMessageRequest): Promise<MessageResultDTO>;
83
86
  /** First-class interrupt (the human Esc): cancels pending undelivered
84
87
  * human-send inbox entries, then aborts a live in-flight turn. NEVER
@@ -165,7 +168,7 @@ export declare class CrtrClient {
165
168
  * (`spawnNode` server-side). Distinct from `createNode` (which launches a
166
169
  * broker) precisely because a human bridge must never have one. */
167
170
  createHumanBridge(req: CreateHumanBridgeRequest): Promise<HumanBridgeResultDTO>;
168
- /** Resolve a deck ticket answer. crtrd claims the ticket (taking over a live
171
+ /** Resolve a page ticket answer. crtrd claims the ticket (taking over a live
169
172
  * inbox claim where one exists), publishes the canonical result, delivers
170
173
  * to the asking node, and retires the bridge before this resolves. */
171
174
  resolveHumanTicket(nodeId: string, body: HumanResolveRequest): Promise<HumanResolveResultDTO>;
@@ -188,16 +191,28 @@ export declare class CrtrClient {
188
191
  resolveReviewComment(commentId: string, req?: ReviewCommentActionRequest): Promise<ReviewCommentMutationDTO>;
189
192
  reopenReviewComment(commentId: string, req?: ReviewCommentActionRequest): Promise<ReviewCommentMutationDTO>;
190
193
  deleteReviewComment(commentId: string, req?: ReviewCommentActionRequest): Promise<ReviewCommentMutationDTO>;
191
- /** Pending deck/review tickets across every available crouter-owned
194
+ /** Pending page/review tickets across every available crouter-owned
192
195
  * humanloop root. */
193
196
  listHumanInbox(): Promise<InboxListDTO>;
194
- /** Read one pending deck by its opaque ticket id, with Markdown bodies
195
- * resolved inline. */
196
- getHumanInboxDeck(ticketId: InboxTicketIdDTO): Promise<InboxDeckDTO>;
197
- /** Submit ordered interaction responses for a pending deck. Single-assignment
198
- * server-side: a competing resolution races to `ticket_already_resolved`. */
199
- respondHumanInboxDeck(ticketId: InboxTicketIdDTO, request: RespondInboxDeckRequest): Promise<DeckTicketResultDTO>;
200
- /** Cancel a pending deck (terminal response, never deletion). */
197
+ /** Read one page ticket by its opaque ticket id (pending, resolved, or canceled). */
198
+ getHumanInboxPage(ticketId: InboxTicketIdDTO): Promise<InboxPageDTO>;
199
+ /** All page tickets raised by one node, oldest first (pending, resolved, or canceled). */
200
+ getInboxHistory(nodeId: string): Promise<InboxPageHistoryDTO>;
201
+ /** Submit responses for a page ticket. Single-assignment server-side: a competing
202
+ * resolution races to `ticket_already_resolved`. */
203
+ respondHumanInboxPage(ticketId: InboxTicketIdDTO, request: RespondInboxPageRequest): Promise<PageTicketResultDTO>;
204
+ /** Autosave partial page work. Omitted slots are permitted in progress updates. */
205
+ postInboxProgress(ticketId: InboxTicketIdDTO, responses: PageResponsesDTO): Promise<void>;
206
+ /** Get the published response for a resolved page ticket. 404 if pending or canceled. */
207
+ getInboxResponse(ticketId: InboxTicketIdDTO): Promise<PageTicketResultDTO>;
208
+ /** The page's body-level HTML payload, for pending, resolved, and canceled
209
+ * tickets alike. The host wraps it and injects the page runtime bundle. */
210
+ renderHumanInboxPage(ticketId: InboxTicketIdDTO): Promise<InboxPageRenderDTO>;
211
+ /** One built page runtime asset. Pass the previously returned `etag` as
212
+ * `ifNoneMatch` to get a 304 (and no bytes) while the cached copy is
213
+ * current. The one non-JSON route on this surface. */
214
+ getPagesBundleAsset(asset: 'js' | 'css', ifNoneMatch?: string): Promise<PagesBundleAssetDTO>;
215
+ /** Cancel a ticket (terminal response, never deletion). */
201
216
  cancelHumanInboxTicket(ticketId: InboxTicketIdDTO, request?: CancelInboxTicketRequest): Promise<CanceledTicketResultDTO>;
202
217
  /** Composed client-side from `GET /v1/nodes` + `GET /v1/status` (spec §6.3 —
203
218
  * the dashboard is absorbed into those two reads; there is no single route).
@@ -231,7 +246,13 @@ export declare class CrtrClient {
231
246
  /** Raw request for routes not yet method-wrapped. Applies the same
232
247
  * autostart + error-mapping semantics. */
233
248
  request<T>(method: string, path: string, body?: unknown): Promise<T>;
249
+ /** The unparsed request: autostart + handover recovery, no JSON parse. Every
250
+ * wrapper goes through here; only the non-JSON routes call it directly. */
251
+ private send;
234
252
  private nodePath;
253
+ /** Validate a background job id before route construction. Job ids arrive
254
+ * from the daemon's file-backed roster and must remain one path segment. */
255
+ private jobPath;
235
256
  /** Validate a cron id before route construction — `routes.ts` interpolates
236
257
  * it raw, so a value carrying `/`, whitespace or `?` would corrupt the
237
258
  * request line rather than 404 cleanly. Mirrors `nodePath`. */
@@ -16,6 +16,7 @@ import { join } from 'node:path';
16
16
  import { ApiError, isErrorBody } from './errors.js';
17
17
  import { routes } from './routes.js';
18
18
  import { isSafeNodeId } from './dto/common.js';
19
+ import { isSafeBashJobId } from './dto/bash-jobs.js';
19
20
  import { isSafeCronId, } from './dto/crons.js';
20
21
  /** Filesystem/scope constants mirrored from `core/types.ts` (`CRTR_DIR_NAME`)
21
22
  * and `core/canvas/paths.ts` (`crtrHome`/`apiSocketPath`). Duplicated — not
@@ -97,6 +98,12 @@ export class CrtrClient {
97
98
  getNode(id) {
98
99
  return this.request('GET', routes.node(this.nodePath(id)));
99
100
  }
101
+ listBashJobs(id) {
102
+ return this.request('GET', routes.nodeJobs(this.nodePath(id)));
103
+ }
104
+ stopBashJob(id, jobId) {
105
+ return this.request('DELETE', routes.nodeJob(this.nodePath(id), this.jobPath(jobId)));
106
+ }
100
107
  sendMessage(id, req) {
101
108
  return this.request('POST', routes.nodeMessages(this.nodePath(id)), req);
102
109
  }
@@ -302,7 +309,7 @@ export class CrtrClient {
302
309
  createHumanBridge(req) {
303
310
  return this.request('POST', routes.humanBridge(), req);
304
311
  }
305
- /** Resolve a deck ticket answer. crtrd claims the ticket (taking over a live
312
+ /** Resolve a page ticket answer. crtrd claims the ticket (taking over a live
306
313
  * inbox claim where one exists), publishes the canonical result, delivers
307
314
  * to the asking node, and retires the bridge before this resolves. */
308
315
  resolveHumanTicket(nodeId, body) {
@@ -365,22 +372,61 @@ export class CrtrClient {
365
372
  return this.request('POST', routes.humanCommentDelete(this.commentPath(commentId)), req);
366
373
  }
367
374
  // ---- Humanloop inbox (Northlight crouter-inbox v1, inbox-contract.md §A) --
368
- /** Pending deck/review tickets across every available crouter-owned
375
+ /** Pending page/review tickets across every available crouter-owned
369
376
  * humanloop root. */
370
377
  listHumanInbox() {
371
378
  return this.request('GET', routes.humanInbox());
372
379
  }
373
- /** Read one pending deck by its opaque ticket id, with Markdown bodies
374
- * resolved inline. */
375
- getHumanInboxDeck(ticketId) {
380
+ /** Read one page ticket by its opaque ticket id (pending, resolved, or canceled). */
381
+ getHumanInboxPage(ticketId) {
376
382
  return this.request('GET', routes.humanInboxTicket(this.ticketId(ticketId)));
377
383
  }
378
- /** Submit ordered interaction responses for a pending deck. Single-assignment
379
- * server-side: a competing resolution races to `ticket_already_resolved`. */
380
- respondHumanInboxDeck(ticketId, request) {
384
+ /** All page tickets raised by one node, oldest first (pending, resolved, or canceled). */
385
+ getInboxHistory(nodeId) {
386
+ if (typeof nodeId !== 'string' || nodeId === '') {
387
+ throw new TypeError(`invalid node id: ${JSON.stringify(nodeId)}`);
388
+ }
389
+ return this.request('GET', withQuery(routes.humanInbox() + '/history', { node_id: nodeId }));
390
+ }
391
+ /** Submit responses for a page ticket. Single-assignment server-side: a competing
392
+ * resolution races to `ticket_already_resolved`. */
393
+ respondHumanInboxPage(ticketId, request) {
381
394
  return this.request('POST', routes.humanInboxRespond(this.ticketId(ticketId)), request);
382
395
  }
383
- /** Cancel a pending deck (terminal response, never deletion). */
396
+ /** Autosave partial page work. Omitted slots are permitted in progress updates. */
397
+ postInboxProgress(ticketId, responses) {
398
+ return this.request('POST', routes.humanInboxProgress(this.ticketId(ticketId)), { responses });
399
+ }
400
+ /** Get the published response for a resolved page ticket. 404 if pending or canceled. */
401
+ getInboxResponse(ticketId) {
402
+ return this.request('GET', routes.humanInboxResponse(this.ticketId(ticketId)));
403
+ }
404
+ /** The page's body-level HTML payload, for pending, resolved, and canceled
405
+ * tickets alike. The host wraps it and injects the page runtime bundle. */
406
+ renderHumanInboxPage(ticketId) {
407
+ return this.request('GET', routes.humanInboxRender(this.ticketId(ticketId)));
408
+ }
409
+ /** One built page runtime asset. Pass the previously returned `etag` as
410
+ * `ifNoneMatch` to get a 304 (and no bytes) while the cached copy is
411
+ * current. The one non-JSON route on this surface. */
412
+ async getPagesBundleAsset(asset, ifNoneMatch) {
413
+ if (asset !== 'js' && asset !== 'css') {
414
+ throw new TypeError(`invalid page bundle asset: ${JSON.stringify(asset)}`);
415
+ }
416
+ const res = await this.send('GET', routes.humanPagesBundle(asset), undefined, ifNoneMatch === undefined ? undefined : { 'if-none-match': ifNoneMatch });
417
+ if (res.status === 304) {
418
+ return { status: 304, etag: res.headers['etag'] ?? '', content_type: res.headers['content-type'] ?? '', content: null };
419
+ }
420
+ if (res.status < 200 || res.status >= 300)
421
+ parse(res); // throws the mapped ApiError
422
+ return {
423
+ status: 200,
424
+ etag: res.headers['etag'] ?? '',
425
+ content_type: res.headers['content-type'] ?? '',
426
+ content: res.text,
427
+ };
428
+ }
429
+ /** Cancel a ticket (terminal response, never deletion). */
384
430
  cancelHumanInboxTicket(ticketId, request) {
385
431
  return this.request('POST', routes.humanInboxCancel(this.ticketId(ticketId)), request ?? {});
386
432
  }
@@ -441,23 +487,23 @@ export class CrtrClient {
441
487
  /** Raw request for routes not yet method-wrapped. Applies the same
442
488
  * autostart + error-mapping semantics. */
443
489
  async request(method, path, body) {
444
- let res;
490
+ return parse(await this.send(method, path, body));
491
+ }
492
+ /** The unparsed request: autostart + handover recovery, no JSON parse. Every
493
+ * wrapper goes through here; only the non-JSON routes call it directly. */
494
+ async send(method, path, body, extraHeaders) {
445
495
  try {
446
- res = await this.transport(method, path, body);
496
+ return await this.transport(method, path, body, extraHeaders);
447
497
  }
448
498
  catch (err) {
449
499
  if (this.isColdSocketError(err)) {
450
500
  await this.handleColdSocket();
451
- res = await this.transport(method, path, body);
452
- }
453
- else if (this.isHandoverHangup(err)) {
454
- res = await this.rideOutHandover(method, path, body);
455
- }
456
- else {
457
- throw toTransportApiError(err);
501
+ return await this.transport(method, path, body, extraHeaders);
458
502
  }
503
+ if (this.isHandoverHangup(err))
504
+ return await this.rideOutHandover(method, path, body, extraHeaders);
505
+ throw toTransportApiError(err);
459
506
  }
460
- return parse(res);
461
507
  }
462
508
  // ---- internals ---------------------------------------------------------
463
509
  nodePath(id) {
@@ -466,6 +512,14 @@ export class CrtrClient {
466
512
  }
467
513
  return id;
468
514
  }
515
+ /** Validate a background job id before route construction. Job ids arrive
516
+ * from the daemon's file-backed roster and must remain one path segment. */
517
+ jobPath(id) {
518
+ if (!isSafeBashJobId(id)) {
519
+ throw new ApiError(400, 'invalid_job_id', `invalid background job id: ${JSON.stringify(id)}`);
520
+ }
521
+ return id;
522
+ }
469
523
  /** Validate a cron id before route construction — `routes.ts` interpolates
470
524
  * it raw, so a value carrying `/`, whitespace or `?` would corrupt the
471
525
  * request line rather than 404 cleanly. Mirrors `nodePath`. */
@@ -506,11 +560,11 @@ export class CrtrClient {
506
560
  }
507
561
  return id;
508
562
  }
509
- transport(method, path, body) {
563
+ transport(method, path, body, extraHeaders) {
510
564
  const usingHttps = this.baseUrl?.protocol === 'https:';
511
565
  const doRequest = usingHttps ? httpsRequest : httpRequest;
512
566
  const payload = body === undefined ? undefined : JSON.stringify(body);
513
- const headers = { accept: 'application/json', ...this.headers };
567
+ const headers = { accept: 'application/json', ...this.headers, ...extraHeaders };
514
568
  if (payload !== undefined) {
515
569
  headers['content-type'] = 'application/json';
516
570
  headers['content-length'] = String(Buffer.byteLength(payload));
@@ -535,7 +589,12 @@ export class CrtrClient {
535
589
  const chunks = [];
536
590
  res.on('data', (chunk) => chunks.push(chunk));
537
591
  res.on('end', () => {
538
- resolve({ status: res.statusCode ?? 0, text: Buffer.concat(chunks).toString('utf8') });
592
+ const received = {};
593
+ for (const [name, value] of Object.entries(res.headers)) {
594
+ if (value !== undefined)
595
+ received[name] = Array.isArray(value) ? value.join(', ') : value;
596
+ }
597
+ resolve({ status: res.statusCode ?? 0, text: Buffer.concat(chunks).toString('utf8'), headers: received });
539
598
  });
540
599
  res.on('error', reject);
541
600
  });
@@ -572,7 +631,7 @@ export class CrtrClient {
572
631
  * before the socket dropped, so it fails with `daemon_restarting` (retry),
573
632
  * never `daemon_unavailable` ("start the daemon" is the wrong advice for a
574
633
  * daemon that is mid-handover). */
575
- async rideOutHandover(method, path, body) {
634
+ async rideOutHandover(method, path, body, extraHeaders) {
576
635
  if (!(await this.awaitHandover())) {
577
636
  throw new ApiError(503, 'daemon_unavailable', `crtrd went away mid-request and did not come back within ${this.coldStartPollWindowMs}ms.`);
578
637
  }
@@ -580,7 +639,7 @@ export class CrtrClient {
580
639
  throw new ApiError(503, 'daemon_restarting', `crtrd handed over to a new runtime generation mid-request; this ${method} may or may not have been applied.`);
581
640
  }
582
641
  try {
583
- return await this.transport(method, path, body);
642
+ return await this.transport(method, path, body, extraHeaders);
584
643
  }
585
644
  catch (err) {
586
645
  throw toTransportApiError(err);
@@ -0,0 +1,24 @@
1
+ import type { IsoTime, NodeIdDTO } from './common.js';
2
+ /** A still-live background bash job owned by one node. */
3
+ export interface BashJobStatusDTO {
4
+ job_id: string;
5
+ command: string;
6
+ /** Human-readable label supplied with the bash call, or null when absent. */
7
+ purpose: string | null;
8
+ started_at: IsoTime;
9
+ elapsed_ms: number;
10
+ /** Persisted supervisor process group, or null for legacy jobs. */
11
+ pgid: number | null;
12
+ /** Whether the persisted process group currently exists. */
13
+ pgid_alive: boolean;
14
+ }
15
+ /** Result of stopping one background bash job. `signaled` is false when the
16
+ * process group had already exited, but the job is still retired via its exit
17
+ * sentinel. */
18
+ export interface BashJobStopResultDTO {
19
+ node_id: NodeIdDTO;
20
+ job_id: string;
21
+ signaled: boolean;
22
+ }
23
+ /** Whether a job id is safe to interpolate as one API/filesystem segment. */
24
+ export declare function isSafeBashJobId(jobId: string): boolean;
@@ -0,0 +1,9 @@
1
+ // Background bash job API shapes. The daemon projects the file-backed job
2
+ // control plane into these dependency-light DTOs for remote presenters.
3
+ /** Whether a job id is safe to interpolate as one API/filesystem segment. */
4
+ export function isSafeBashJobId(jobId) {
5
+ return typeof jobId === 'string'
6
+ && jobId !== '' && jobId !== '.' && jobId !== '..'
7
+ && /^[A-Za-z0-9._~-]+$/u.test(jobId)
8
+ && Buffer.byteLength(jobId, 'utf8') <= 128;
9
+ }
@@ -1,5 +1,4 @@
1
1
  import type { NodeIdDTO } from './common.js';
2
- import type { InteractionResponseDTO } from './inbox.js';
3
2
  /** One anchored review comment, mirroring crouter's ticket-store shape as a
4
3
  * plain structural wire type (no store import). */
5
4
  export interface FeedbackCommentDTO {
@@ -37,9 +36,9 @@ export interface FeedbackResultDTO {
37
36
  commentsTotal: number;
38
37
  commentsUnresolved: number;
39
38
  }
40
- /** `POST /v1/human/tickets/{node_id}/resolve` body — a deck answer. */
39
+ /** `POST /v1/human/tickets/{node_id}/resolve` body — a page answer keyed by slot id. */
41
40
  export interface HumanResolveRequest {
42
- responses: InteractionResponseDTO[];
41
+ responses: Record<string, Record<string, unknown>>;
43
42
  }
44
43
  /** `POST /v1/human/tickets/{node_id}/cancel` body. */
45
44
  export interface HumanCancelRequest {
@@ -49,7 +48,7 @@ export interface HumanCancelRequest {
49
48
  /** `POST /v1/human/tickets/{node_id}/resolve` result. */
50
49
  export interface HumanResolveResultDTO {
51
50
  delivered: true;
52
- kind: 'deck';
51
+ kind: 'page';
53
52
  }
54
53
  /** `POST /v1/human/tickets/{node_id}/cancel` result. */
55
54
  export interface HumanCancelResultDTO {
@@ -1,100 +1,141 @@
1
1
  import type { IsoTime } from './common.js';
2
- /** Opaque, stable, URL-safe ticket id: lowercase SHA-256 hex of
3
- * `canonicalRoot + "\0" + ticketBasename`. Clients must treat it as opaque —
4
- * it discloses no home filesystem path. */
2
+ /** Opaque, stable, URL-safe ticket id: lowercase SHA-256 hex. */
5
3
  export type InboxTicketIdDTO = string;
6
- export type InteractionKindDTO = 'notify' | 'decision' | 'context' | 'error' | 'review';
7
- export interface DeckSourceDTO {
4
+ export interface TicketSourceDTO {
8
5
  sessionName?: string;
9
6
  askedBy?: string;
10
7
  blockedSince?: IsoTime;
11
8
  nodeId?: string;
12
9
  }
13
- export interface DeckTicketSummaryDTO {
10
+ export interface ReviewTicketSummaryDTO {
14
11
  ticket_id: InboxTicketIdDTO;
15
- kind: 'deck';
12
+ kind: 'review';
16
13
  title: string;
17
14
  subtitle: string;
18
15
  blocked_since: IsoTime;
19
- source: DeckSourceDTO;
20
- interaction_kind?: InteractionKindDTO;
16
+ source: TicketSourceDTO;
21
17
  }
22
- export interface ReviewTicketSummaryDTO {
18
+ export interface PageTicketSummaryDTO {
23
19
  ticket_id: InboxTicketIdDTO;
24
- kind: 'review';
20
+ kind: 'page';
25
21
  title: string;
26
- subtitle: string;
22
+ subtitle?: string;
23
+ placement: 'inline' | 'panel';
24
+ dialect: 'md' | 'html';
25
+ steps: number;
27
26
  blocked_since: IsoTime;
28
- source: DeckSourceDTO;
27
+ source: TicketSourceDTO;
28
+ slot_kinds: string[];
29
+ awaits_response: boolean;
30
+ state?: 'pending' | 'resolved' | 'canceled';
29
31
  }
30
- export type InboxTicketSummaryDTO = DeckTicketSummaryDTO | ReviewTicketSummaryDTO;
31
- /** `GET /v1/human/inbox` result. `tickets` is sorted newest `blocked_since`
32
- * first, then `ticket_id` ascending. */
32
+ export type InboxTicketSummaryDTO = PageTicketSummaryDTO | ReviewTicketSummaryDTO;
33
33
  export interface InboxListDTO {
34
34
  tickets: InboxTicketSummaryDTO[];
35
35
  }
36
- export interface InteractionOptionDTO {
36
+ export interface PageSlotDTO {
37
+ id?: string;
38
+ kind: string;
39
+ step: number;
40
+ config: Record<string, unknown>;
41
+ unvalidated?: true;
42
+ }
43
+ /** The nested protocol object is deliberately camelCase on the wire. */
44
+ export interface PageManifestDTO {
45
+ schema: 'crtr.page/v1';
46
+ dialect: 'md' | 'html';
47
+ title: string;
48
+ subtitle?: string;
49
+ placement: 'inline' | 'panel';
50
+ document: 'page.md' | 'page.html';
51
+ steps: number;
52
+ slots: PageSlotDTO[];
53
+ source?: TicketSourceDTO;
54
+ }
55
+ export interface PageCommentAnchorDTO {
56
+ kind: 'whole' | 'option' | 'row' | 'column' | 'card' | 'range';
57
+ optionId?: string;
58
+ rowId?: string;
59
+ columnId?: string;
60
+ cardId?: string;
61
+ start?: number;
62
+ end?: number;
63
+ quote?: string;
64
+ }
65
+ export interface PageCommentDTO {
37
66
  id: string;
38
- label: string;
39
- description?: string;
67
+ anchor: PageCommentAnchorDTO;
68
+ text: string;
40
69
  }
41
- export interface InteractionPreAnswerDTO {
42
- selectedOptionId?: string;
43
- selectedOptionIds?: string[];
70
+ export interface PageOptionsResponseDTO {
71
+ selectedOptionIds: string[];
72
+ comments: PageCommentDTO[];
44
73
  freetext?: string;
45
- label?: string;
46
74
  }
47
- export interface InteractionDTO {
48
- id: string;
49
- title: string;
50
- subtitle: string;
51
- /** Resolved source Markdown — `bodyPath` is deliberately impossible here. */
52
- body?: string;
53
- options: InteractionOptionDTO[];
54
- multiSelect?: boolean;
55
- allowFreetext?: boolean;
56
- freetextLabel?: string;
57
- kind?: InteractionKindDTO;
58
- preAnswered?: InteractionPreAnswerDTO;
59
- }
60
- export interface DeckDTO {
61
- title: string;
62
- source?: DeckSourceDTO;
63
- interactions: InteractionDTO[];
75
+ export interface PageTextResponseDTO {
76
+ text: string;
77
+ edited: boolean;
78
+ comments: PageCommentDTO[];
79
+ }
80
+ export interface PageTableResponseDTO {
81
+ selectedRowIds: string[];
82
+ selectedColumnIds: string[];
83
+ comments: PageCommentDTO[];
64
84
  }
65
- /** `GET /v1/human/inbox/:ticket_id` result for a pending deck. */
66
- export interface InboxDeckDTO {
85
+ export interface PageCardsResponseDTO {
86
+ selectedCardIds: string[];
87
+ comments: PageCommentDTO[];
88
+ }
89
+ export type SlotResponseDTO = PageOptionsResponseDTO | PageTextResponseDTO | PageTableResponseDTO | PageCardsResponseDTO | Record<string, unknown>;
90
+ export type PageResponsesDTO = Record<string, SlotResponseDTO>;
91
+ export interface InboxPageDTO {
67
92
  ticket_id: InboxTicketIdDTO;
68
- kind: 'deck';
69
- deck: DeckDTO;
93
+ kind: 'page';
94
+ state: 'pending' | 'resolved' | 'canceled';
95
+ page: PageManifestDTO;
96
+ document: string;
97
+ document_media_type: 'text/markdown' | 'text/html';
98
+ progress: {
99
+ responses: PageResponsesDTO;
100
+ } | null;
70
101
  }
71
- export interface InteractionResponseDTO {
72
- id: string;
73
- selectedOptionId?: string;
74
- selectedOptionIds?: string[];
75
- freetext?: string;
76
- optionComments?: Record<string, string>;
77
- }
78
- /** `POST /v1/human/inbox/:ticket_id/respond` body. */
79
- export interface RespondInboxDeckRequest {
80
- responses: InteractionResponseDTO[];
81
- }
82
- /** `POST /v1/human/inbox/:ticket_id/respond` result the canonical humanloop
83
- * `humanloop.response/v2` result, unchanged. */
84
- export interface DeckTicketResultDTO {
85
- schema: 'humanloop.response/v2';
86
- kind: 'deck';
87
- responses: InteractionResponseDTO[];
102
+ export interface InboxPageHistoryDTO {
103
+ tickets: PageTicketSummaryDTO[];
104
+ }
105
+ /** `GET /v1/human/inbox/:ticket_id/render` — the page's body-level HTML
106
+ * payload. It carries the page's own prose and `crtr-*` slot elements only;
107
+ * the host supplies the surrounding document and injects the page runtime
108
+ * bundle it chooses to serve. */
109
+ export interface InboxPageRenderDTO {
110
+ html: string;
111
+ }
112
+ /** A client-side projection of `GET /v1/human/pages/bundle.{js,css}` — the one
113
+ * non-JSON route on this surface. `content` is null on a 304, where the
114
+ * caller's cached copy is still current. */
115
+ export interface PagesBundleAssetDTO {
116
+ status: 200 | 304;
117
+ etag: string;
118
+ content_type: string;
119
+ content: string | null;
120
+ }
121
+ export interface RespondInboxPageRequest {
122
+ responses: PageResponsesDTO;
123
+ }
124
+ export interface InboxPageProgressDTO {
125
+ responses: PageResponsesDTO;
126
+ }
127
+ export interface PageTicketResultDTO {
128
+ schema: 'crtr.page-response/v1';
129
+ kind: 'page';
130
+ responses: PageResponsesDTO;
88
131
  summary: string;
89
132
  completedAt: IsoTime;
90
133
  }
91
- /** `POST /v1/human/inbox/:ticket_id/cancel` body. `reason`, when present, must
92
- * be nonempty after trim and at most 1000 characters. */
134
+ /** `POST /v1/human/inbox/:ticket_id/cancel` body. */
93
135
  export interface CancelInboxTicketRequest {
94
136
  reason?: string;
95
137
  }
96
- /** `POST /v1/human/inbox/:ticket_id/cancel` result — the canonical humanloop
97
- * `humanloop.cancel/v1` result, unchanged. `actor` is always `"human"`. */
138
+ /** `POST /v1/human/inbox/:ticket_id/cancel` result. */
98
139
  export interface CanceledTicketResultDTO {
99
140
  schema: 'humanloop.cancel/v1';
100
141
  kind: 'canceled';
@@ -1,10 +1,4 @@
1
1
  // Humanloop inbox DTOs — crtrd `/v1/human/inbox` (Northlight crouter-inbox v1,
2
- // inbox-contract.md §A). Crouter API envelope fields use the existing
3
- // snake_case convention; nested humanloop protocol objects retain their
4
- // canonical camelCase field names so they cross the wire without translation
5
- // or loss. Optional fields are omitted when absent, never serialized as
6
- // `null`. `bodyPath` is deliberately impossible on this wire — crtrd resolves
7
- // it server-side via humanloop's `parseDeck` and returns inline `body`.
8
- //
9
- // PURITY (spec §3.1): Node built-ins + `src/api/*` only.
2
+ // inbox-contract.md §A). API envelope fields use snake_case; nested page
3
+ // protocol objects retain their canonical camelCase field names.
10
4
  export {};
@@ -6,6 +6,7 @@ export { API_VERSION, routes } from './routes.js';
6
6
  export * from './dto/common.js';
7
7
  export * from './dto/health.js';
8
8
  export * from './dto/nodes.js';
9
+ export * from './dto/bash-jobs.js';
9
10
  export * from './dto/messages.js';
10
11
  export * from './dto/reports.js';
11
12
  export * from './dto/lifecycle.js';
package/dist/api/index.js CHANGED
@@ -7,6 +7,7 @@ export { API_VERSION, routes } from './routes.js';
7
7
  export * from './dto/common.js';
8
8
  export * from './dto/health.js';
9
9
  export * from './dto/nodes.js';
10
+ export * from './dto/bash-jobs.js';
10
11
  export * from './dto/messages.js';
11
12
  export * from './dto/reports.js';
12
13
  export * from './dto/lifecycle.js';
@@ -14,6 +14,8 @@ export declare const routes: {
14
14
  readonly nodeContext: (id: string) => string;
15
15
  readonly nodeArtifacts: (id: string) => string;
16
16
  readonly nodeReports: (id: string) => string;
17
+ readonly nodeJobs: (id: string) => string;
18
+ readonly nodeJob: (id: string, jobId: string) => string;
17
19
  readonly nodeMessages: (id: string) => string;
18
20
  readonly nodeInterrupt: (id: string) => string;
19
21
  readonly nodeFork: (id: string) => string;
@@ -76,7 +78,12 @@ export declare const routes: {
76
78
  readonly humanInbox: () => string;
77
79
  readonly humanInboxTicket: (ticketId: string) => string;
78
80
  readonly humanInboxRespond: (ticketId: string) => string;
81
+ readonly humanInboxProgress: (ticketId: string) => string;
82
+ readonly humanInboxResponse: (ticketId: string) => string;
79
83
  readonly humanInboxCancel: (ticketId: string) => string;
84
+ readonly humanInboxRender: (ticketId: string) => string;
85
+ /** The built page runtime assets, served for whatever host renders a page. */
86
+ readonly humanPagesBundle: (asset: "js" | "css") => string;
80
87
  readonly profiles: () => string;
81
88
  readonly profile: (name: string) => string;
82
89
  readonly modelAuths: () => string;
@@ -30,6 +30,8 @@ export const routes = {
30
30
  nodeContext: (id) => `${V}/nodes/${id}/context`,
31
31
  nodeArtifacts: (id) => `${V}/nodes/${id}/artifacts`,
32
32
  nodeReports: (id) => `${V}/nodes/${id}/reports`,
33
+ nodeJobs: (id) => `${V}/nodes/${id}/jobs`,
34
+ nodeJob: (id, jobId) => `${V}/nodes/${id}/jobs/${jobId}`,
33
35
  // Node messages / feed
34
36
  nodeMessages: (id) => `${V}/nodes/${id}/messages`,
35
37
  nodeInterrupt: (id) => `${V}/nodes/${id}/interrupt`,
@@ -103,7 +105,12 @@ export const routes = {
103
105
  humanInbox: () => `${V}/human/inbox`,
104
106
  humanInboxTicket: (ticketId) => `${V}/human/inbox/${ticketId}`,
105
107
  humanInboxRespond: (ticketId) => `${V}/human/inbox/${ticketId}/respond`,
108
+ humanInboxProgress: (ticketId) => `${V}/human/inbox/${ticketId}/progress`,
109
+ humanInboxResponse: (ticketId) => `${V}/human/inbox/${ticketId}/response`,
106
110
  humanInboxCancel: (ticketId) => `${V}/human/inbox/${ticketId}/cancel`,
111
+ humanInboxRender: (ticketId) => `${V}/human/inbox/${ticketId}/render`,
112
+ /** The built page runtime assets, served for whatever host renders a page. */
113
+ humanPagesBundle: (asset) => `${V}/human/pages/bundle.${asset}`,
107
114
  // Profiles (server-side for P2 Core; CLI profile verbs stay fs-local)
108
115
  profiles: () => `${V}/profiles`,
109
116
  profile: (name) => `${V}/profiles/${name}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/crouter-api",
3
- "version": "0.3.192",
3
+ "version": "0.3.194",
4
4
  "description": "Typed crtrd /v1 API contract — DTOs, route builders, the error contract, and the CrtrClient. Zero runtime dependencies.",
5
5
  "type": "module",
6
6
  "main": "./dist/api/index.js",