@north-light/crouter 0.3.191 → 0.3.193

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.
Files changed (169) hide show
  1. package/dist/api/client.d.ts +31 -10
  2. package/dist/api/client.js +83 -24
  3. package/dist/api/dto/bash-jobs.d.ts +22 -0
  4. package/dist/api/dto/bash-jobs.js +9 -0
  5. package/dist/api/dto/human.d.ts +3 -4
  6. package/dist/api/dto/inbox.d.ts +106 -65
  7. package/dist/api/dto/inbox.js +2 -8
  8. package/dist/api/index.d.ts +1 -0
  9. package/dist/api/index.js +1 -0
  10. package/dist/api/routes.d.ts +7 -0
  11. package/dist/api/routes.js +7 -0
  12. package/dist/builtin-memory/internal/storage-tiers.md +1 -1
  13. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/serial/provider-rotation.test.ts +101 -3
  14. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +6 -2
  15. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +68 -14
  16. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +87 -13
  17. package/dist/clients/attach/__tests__/chat-view-snapshot-ordering.test.js +2 -2
  18. package/dist/clients/attach/__tests__/group-activity.test.js +2 -2
  19. package/dist/clients/attach/chrome/canvas-panels.d.ts +1 -1
  20. package/dist/clients/attach/chrome/canvas-panels.js +1 -1
  21. package/dist/clients/attach/input/overlay-owner.js +1 -1
  22. package/dist/clients/attach/overlays/auth.js +34 -8
  23. package/dist/clients/attach/overlays/pickers.d.ts +5 -8
  24. package/dist/clients/attach/overlays/pickers.js +37 -43
  25. package/dist/clients/attach/viewer.js +643 -620
  26. package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +38 -128
  27. package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +72 -148
  28. package/dist/clients/inbox/controller.d.ts +2 -3
  29. package/dist/clients/inbox/controller.js +41 -42
  30. package/dist/clients/inbox/page-adapter.d.ts +33 -0
  31. package/dist/clients/inbox/page-adapter.js +54 -0
  32. package/dist/clients/inbox/resolve.d.ts +2 -2
  33. package/dist/clients/inbox/resolve.js +1 -1
  34. package/dist/clients/inbox/review-visibility.d.ts +1 -1
  35. package/dist/clients/inbox/review-visibility.js +1 -1
  36. package/dist/clients/inbox/tui/ansi.d.ts +1 -1
  37. package/dist/clients/inbox/tui/ansi.js +2 -2
  38. package/dist/clients/inbox/tui/input.d.ts +1 -3
  39. package/dist/clients/inbox/tui/input.js +242 -553
  40. package/dist/clients/inbox/tui/panel.d.ts +3 -1
  41. package/dist/clients/inbox/tui/panel.js +59 -187
  42. package/dist/clients/inbox/tui/render.d.ts +0 -25
  43. package/dist/clients/inbox/tui/render.js +165 -416
  44. package/dist/clients/inbox/tui/slots.d.ts +32 -0
  45. package/dist/clients/inbox/tui/slots.js +156 -0
  46. package/dist/clients/inbox/tui/types.d.ts +25 -13
  47. package/dist/clients/inbox/tui.js +5 -3
  48. package/dist/commands/attention.js +2 -2
  49. package/dist/commands/human/doc.js +66 -20
  50. package/dist/commands/human/prompts.d.ts +3 -3
  51. package/dist/commands/human/prompts.js +158 -112
  52. package/dist/commands/human/queue.d.ts +0 -1
  53. package/dist/commands/human/queue.js +34 -94
  54. package/dist/commands/human/shared.d.ts +10 -4
  55. package/dist/commands/human/shared.js +62 -13
  56. package/dist/commands/human.js +8 -4
  57. package/dist/commands/memory/read.js +13 -5
  58. package/dist/commands/node/bash.js +15 -28
  59. package/dist/commands/surface/node/focus.js +1 -1
  60. package/dist/core/__tests__/human-deliver.test.js +40 -46
  61. package/dist/core/__tests__/seam/broker-attach-stream.test.js +7 -1
  62. package/dist/core/__tests__/serial/human-deliver-e2e.test.js +40 -31
  63. package/dist/core/__tests__/serial/spawn-root.test.js +3 -1
  64. package/dist/core/__tests__/spawn-worktree-id-conflict.test.js +3 -1
  65. package/dist/core/auth-interaction.d.ts +2 -2
  66. package/dist/core/bash-jobs.d.ts +32 -0
  67. package/dist/core/bash-jobs.js +72 -1
  68. package/dist/core/canvas/__tests__/attention.test.js +16 -10
  69. package/dist/core/canvas/__tests__/render-remote.test.js +9 -3
  70. package/dist/core/canvas/attention.js +1 -1
  71. package/dist/core/canvas/browse/app.js +1 -1
  72. package/dist/core/canvas/nav-model.d.ts +1 -1
  73. package/dist/core/canvas/nav-model.js +1 -1
  74. package/dist/core/canvas/remote-canvas-source.js +1 -1
  75. package/dist/core/config.js +4 -1
  76. package/dist/core/human/__tests__/page-render.test.d.ts +1 -0
  77. package/dist/core/human/__tests__/page-render.test.js +49 -0
  78. package/dist/core/human/__tests__/page-tickets.test.d.ts +1 -0
  79. package/dist/core/human/__tests__/page-tickets.test.js +112 -0
  80. package/dist/core/human/__tests__/page.test.d.ts +1 -0
  81. package/dist/core/human/__tests__/page.test.js +139 -0
  82. package/dist/core/human/__tests__/serial/inbox-core.test.js +42 -32
  83. package/dist/core/human/claim.js +2 -2
  84. package/dist/core/human/convention.d.ts +4 -4
  85. package/dist/core/human/convention.js +6 -13
  86. package/dist/core/human/markdown-html.d.ts +8 -0
  87. package/dist/core/human/markdown-html.js +581 -0
  88. package/dist/core/human/page-catalog.d.ts +6 -0
  89. package/dist/core/human/page-catalog.js +43 -0
  90. package/dist/core/human/page-render.d.ts +3 -0
  91. package/dist/core/human/page-render.js +131 -0
  92. package/dist/core/human/page-schema.d.ts +312 -0
  93. package/dist/core/human/page-schema.js +291 -0
  94. package/dist/core/human/page-synth.d.ts +9 -0
  95. package/dist/core/human/page-synth.js +17 -0
  96. package/dist/core/human/page.d.ts +22 -0
  97. package/dist/core/human/page.js +427 -0
  98. package/dist/core/human/review-schema.d.ts +19 -0
  99. package/dist/core/human/review-schema.js +34 -0
  100. package/dist/core/human/scan.d.ts +12 -3
  101. package/dist/core/human/scan.js +69 -26
  102. package/dist/core/human/summary.d.ts +5 -6
  103. package/dist/core/human/summary.js +54 -37
  104. package/dist/core/human/tickets.d.ts +61 -11
  105. package/dist/core/human/tickets.js +100 -57
  106. package/dist/core/human/types.d.ts +21 -66
  107. package/dist/core/human/types.js +1 -5
  108. package/dist/core/preview-registry.js +0 -1
  109. package/dist/core/review/ticket-filter.d.ts +1 -1
  110. package/dist/core/review/ticket-filter.js +2 -8
  111. package/dist/core/runtime/broker/read-ops.js +3 -0
  112. package/dist/core/runtime/broker.js +19 -10
  113. package/dist/core/runtime/package-health.js +1 -1
  114. package/dist/core/termrender/termrender.d.ts +1 -1
  115. package/dist/core/termrender/termrender.js +2 -2
  116. package/dist/core/tui/page-host.d.ts +1 -1
  117. package/dist/core/tui/page-host.js +2 -2
  118. package/dist/core/user-settings.d.ts +19 -0
  119. package/dist/core/user-settings.js +22 -0
  120. package/dist/daemon/api/handlers/bash-jobs.d.ts +2 -0
  121. package/dist/daemon/api/handlers/bash-jobs.js +89 -0
  122. package/dist/daemon/api/handlers/human.js +16 -14
  123. package/dist/daemon/api/handlers/inbox.js +311 -227
  124. package/dist/daemon/api/router.d.ts +5 -1
  125. package/dist/daemon/api/router.js +5 -0
  126. package/dist/daemon/api/server.js +2 -0
  127. package/dist/daemon/cron-run.js +23 -10
  128. package/dist/daemon/human/finish.d.ts +8 -3
  129. package/dist/daemon/human/finish.js +35 -15
  130. package/dist/pages/bundle.css +1 -0
  131. package/dist/pages/bundle.js +1347 -0
  132. package/dist/pages/comments.d.ts +34 -0
  133. package/dist/pages/comments.js +72 -0
  134. package/dist/pages/elements/cards.d.ts +17 -0
  135. package/dist/pages/elements/cards.js +785 -0
  136. package/dist/pages/elements/chart.d.ts +1 -0
  137. package/dist/pages/elements/chart.js +648 -0
  138. package/dist/pages/elements/options.d.ts +21 -0
  139. package/dist/pages/elements/options.js +604 -0
  140. package/dist/pages/elements/pages.d.ts +24 -0
  141. package/dist/pages/elements/pages.js +355 -0
  142. package/dist/pages/elements/slot.d.ts +21 -0
  143. package/dist/pages/elements/slot.js +105 -0
  144. package/dist/pages/elements/table.d.ts +19 -0
  145. package/dist/pages/elements/table.js +906 -0
  146. package/dist/pages/elements/text.d.ts +25 -0
  147. package/dist/pages/elements/text.js +951 -0
  148. package/dist/pages/entry.d.ts +8 -0
  149. package/dist/pages/entry.js +8 -0
  150. package/dist/pages/host.d.ts +132 -0
  151. package/dist/pages/host.js +149 -0
  152. package/dist/pages/register.d.ts +2 -0
  153. package/dist/pages/register.js +3 -0
  154. package/dist/pages/slot-config.d.ts +36 -0
  155. package/dist/pages/slot-config.js +50 -0
  156. package/dist/pages/types.d.ts +98 -0
  157. package/dist/pages/types.js +23 -0
  158. package/dist/pi-extensions/__tests__/canvas-structured-output.test.js +13 -5
  159. package/dist/pi-extensions/canvas-structured-output.js +7 -3
  160. package/dist/types.d.ts +4 -0
  161. package/dist/types.js +2 -0
  162. package/package.json +8 -8
  163. package/runtime.lock.json +156 -101
  164. package/dist/clients/inbox/deck-adapter.d.ts +0 -29
  165. package/dist/clients/inbox/deck-adapter.js +0 -63
  166. package/dist/core/human/deck-factories.d.ts +0 -8
  167. package/dist/core/human/deck-factories.js +0 -15
  168. package/dist/core/human/deck-schema.d.ts +0 -70
  169. package/dist/core/human/deck-schema.js +0 -92
@@ -0,0 +1,32 @@
1
+ import type { CommentAnchor, PageManifest, PageSlot, SlotResponse, TableConfig } from '../../../core/human/page-schema.js';
2
+ import type { TuiState } from './types.js';
3
+ export type SelectGroup = 'option' | 'card' | 'row' | 'column';
4
+ export type ActionRow = {
5
+ kind: 'select';
6
+ group: SelectGroup;
7
+ id: string;
8
+ label: string;
9
+ description?: string;
10
+ shortcut?: string;
11
+ mode: 'single' | 'multi';
12
+ } | {
13
+ kind: 'freetext';
14
+ label: string;
15
+ } | {
16
+ kind: 'accept';
17
+ label: string;
18
+ } | {
19
+ kind: 'edit';
20
+ label: string;
21
+ };
22
+ export declare function describeSlot(slot: PageSlot): string;
23
+ /** False when the slot's response cannot be authored here: an unvalidated product kind, or a source-backed cards slot whose cards the terminal never sees. */
24
+ export declare function terminalAnswerable(slot: PageSlot): boolean;
25
+ export declare function slotActions(slot: PageSlot): ActionRow[];
26
+ export declare function tableMarkdown(config: TableConfig): string;
27
+ export declare function summarizeSlotResponse(slot: PageSlot, response: SlotResponse | undefined): string;
28
+ export declare function slotAnswered(state: TuiState, slot: PageSlot): boolean;
29
+ export declare function pageComplete(state: Pick<TuiState, 'manifest' | 'responses'>): boolean;
30
+ export declare function commentIdFor(anchor: CommentAnchor): string;
31
+ export declare function anchorLabel(slot: PageSlot, anchor: CommentAnchor): string;
32
+ export declare function awaitsResponse(manifest: PageManifest): boolean;
@@ -0,0 +1,156 @@
1
+ import { isResponseBearingSlot, validatePageResponses, validatePartialPageResponses } from '../../../core/human/page-schema.js';
2
+ const RESERVED = new Set(['c', 'r', 'e', 'a', 'n', 'p', 'q', 'j', 'k', 'u', 'd', ' ']);
3
+ export function describeSlot(slot) {
4
+ if (slot.unvalidated === true)
5
+ return slot.kind;
6
+ if (slot.kind === 'options')
7
+ return 'Options';
8
+ if (slot.kind === 'text')
9
+ return 'Text';
10
+ if (slot.kind === 'table')
11
+ return slot.config.caption ?? 'Table';
12
+ if (slot.kind === 'cards')
13
+ return 'Cards';
14
+ if (slot.kind === 'chart')
15
+ return 'Chart';
16
+ return slot.kind;
17
+ }
18
+ /** False when the slot's response cannot be authored here: an unvalidated product kind, or a source-backed cards slot whose cards the terminal never sees. */
19
+ export function terminalAnswerable(slot) {
20
+ if (slot.unvalidated === true)
21
+ return false;
22
+ if (slot.kind === 'cards' && slot.config.cards === undefined)
23
+ return false;
24
+ return true;
25
+ }
26
+ export function slotActions(slot) {
27
+ if (!terminalAnswerable(slot))
28
+ return [];
29
+ let rows = [];
30
+ if (slot.kind === 'options') {
31
+ const config = slot.config;
32
+ rows = config.options.map((option) => ({ kind: 'select', group: 'option', id: option.id, label: option.label, ...(option.description === undefined ? {} : { description: option.description }), mode: config.mode }));
33
+ if (config.allowFreetext === true)
34
+ rows.push({ kind: 'freetext', label: config.freetextLabel ?? 'Other' });
35
+ }
36
+ else if (slot.kind === 'cards') {
37
+ const config = slot.config;
38
+ rows = (config.cards ?? []).map((card) => ({ kind: 'select', group: 'card', id: card.id, label: card.subtitle === undefined ? card.title : `${card.title} — ${card.subtitle}`, ...(card.body === undefined ? {} : { description: card.body }), mode: config.mode }));
39
+ }
40
+ else if (slot.kind === 'text') {
41
+ if (slot.config.editable === true)
42
+ rows.push({ kind: 'edit', label: 'Edit' });
43
+ rows.push({ kind: 'accept', label: 'Accept as-is' });
44
+ }
45
+ else if (slot.kind === 'table') {
46
+ const config = slot.config;
47
+ if (config.rowSelect !== 'none') {
48
+ const first = config.columns[0];
49
+ rows.push(...(config.rows ?? []).map((row) => ({ kind: 'select', group: 'row', id: row.id, label: first === undefined ? row.id : String(row.cells[first.id] ?? row.id), mode: config.rowSelect })));
50
+ }
51
+ if (config.columnSelect !== 'none')
52
+ rows.push(...config.columns.map((column) => ({ kind: 'select', group: 'column', id: column.id, label: column.label, mode: config.columnSelect })));
53
+ }
54
+ const used = new Set();
55
+ return rows.map((row) => {
56
+ if (row.kind !== 'select')
57
+ return row;
58
+ let shortcut;
59
+ for (const letter of [...row.label.toLowerCase()].filter((c) => /[a-z]/.test(c))) {
60
+ if (!used.has(letter) && !RESERVED.has(letter)) {
61
+ shortcut = letter;
62
+ break;
63
+ }
64
+ }
65
+ if (shortcut === undefined)
66
+ for (let d = 1; d <= 9; d++)
67
+ if (!used.has(String(d))) {
68
+ shortcut = String(d);
69
+ break;
70
+ }
71
+ if (shortcut !== undefined)
72
+ used.add(shortcut);
73
+ return { ...row, ...(shortcut === undefined ? {} : { shortcut }) };
74
+ });
75
+ }
76
+ function escapeCell(value, mono = false) {
77
+ if (value === null || value === undefined)
78
+ return '';
79
+ const text = String(value).replaceAll('|', '\\|').replaceAll('\n', '<br>');
80
+ return mono && text !== '' ? `\`${text.replaceAll('`', '\\`')}\`` : text;
81
+ }
82
+ export function tableMarkdown(config) {
83
+ const header = `| ${config.columns.map((column) => escapeCell(column.label)).join(' | ')} |`;
84
+ const divider = `| ${config.columns.map((column) => column.align === 'right' ? '---:' : '---').join(' | ')} |`;
85
+ return [header, divider, ...(config.rows ?? []).map((row) => `| ${config.columns.map((column) => escapeCell(row.cells[column.id], column.mono === true)).join(' | ')} |`)].join('\n');
86
+ }
87
+ export function summarizeSlotResponse(slot, response) {
88
+ if (response === undefined)
89
+ return isResponseBearingSlot(slot) ? 'unanswered' : 'display';
90
+ if (slot.kind === 'options') {
91
+ const r = response;
92
+ const labels = slot.config.options.filter((o) => r.selectedOptionIds.includes(o.id)).map((o) => o.label);
93
+ if ((r.freetext?.trim() ?? '') !== '')
94
+ labels.push(r.freetext.trim());
95
+ return labels.join(', ') || (r.comments.length > 0 ? `${r.comments.length} comment${r.comments.length === 1 ? '' : 's'}` : 'answered');
96
+ }
97
+ if (slot.kind === 'cards') {
98
+ const r = response;
99
+ return slot.config.cards?.filter((c) => r.selectedCardIds.includes(c.id)).map((c) => c.title).join(', ') || 'answered';
100
+ }
101
+ if (slot.kind === 'table') {
102
+ const r = response;
103
+ return `${r.selectedRowIds.length} row${r.selectedRowIds.length === 1 ? '' : 's'}, ${r.selectedColumnIds.length} column${r.selectedColumnIds.length === 1 ? '' : 's'}`;
104
+ }
105
+ if (slot.kind === 'text')
106
+ return response.edited ? 'edited' : 'accepted';
107
+ return 'answered';
108
+ }
109
+ export function slotAnswered(state, slot) {
110
+ if (!terminalAnswerable(slot) || slot.id === undefined || !state.responses.has(slot.id))
111
+ return false;
112
+ try {
113
+ validatePartialPageResponses(state.manifest, { [slot.id]: state.responses.get(slot.id) });
114
+ return true;
115
+ }
116
+ catch {
117
+ return false;
118
+ }
119
+ }
120
+ export function pageComplete(state) {
121
+ if (state.manifest.slots.some((slot) => isResponseBearingSlot(slot) && !terminalAnswerable(slot)))
122
+ return false;
123
+ try {
124
+ validatePageResponses(state.manifest, Object.fromEntries(state.responses));
125
+ return true;
126
+ }
127
+ catch {
128
+ return false;
129
+ }
130
+ }
131
+ export function commentIdFor(anchor) {
132
+ switch (anchor.kind) {
133
+ case 'whole': return 'whole';
134
+ case 'option': return `option:${anchor.optionId}`;
135
+ case 'card': return `card:${anchor.cardId}`;
136
+ case 'row': return `row:${anchor.rowId}`;
137
+ case 'column': return `column:${anchor.columnId}`;
138
+ case 'range': return `range:${anchor.start}:${anchor.end}`;
139
+ }
140
+ }
141
+ export function anchorLabel(slot, anchor) {
142
+ if (anchor.kind === 'whole')
143
+ return describeSlot(slot);
144
+ if (anchor.kind === 'option')
145
+ return slot.config.options.find((x) => x.id === anchor.optionId)?.label ?? anchor.optionId;
146
+ if (anchor.kind === 'card')
147
+ return slot.config.cards?.find((x) => x.id === anchor.cardId)?.title ?? anchor.cardId;
148
+ if (anchor.kind === 'row')
149
+ return slot.config.rows?.find((x) => x.id === anchor.rowId)?.id ?? anchor.rowId;
150
+ if (anchor.kind === 'column')
151
+ return slot.config.columns.find((x) => x.id === anchor.columnId)?.label ?? anchor.columnId;
152
+ return anchor.quote;
153
+ }
154
+ export function awaitsResponse(manifest) {
155
+ return manifest.slots.some(isResponseBearingSlot);
156
+ }
@@ -1,22 +1,31 @@
1
1
  import type { Key } from './keys.js';
2
- import type { Deck, Interaction, InteractionResponse } from '../../../core/human/types.js';
2
+ import type { CommentAnchor, PageManifest, PageResponses, PageSlot, SlotResponse } from '../../../core/human/page-schema.js';
3
+ import type { PageSourceProjection } from '../../../core/human/page.js';
3
4
  export type Phase = 'overview' | 'item-review' | 'final';
4
5
  export type InputMode = null | {
5
6
  kind: 'comment';
6
7
  buffer: string;
7
8
  cursor: number;
8
- selectedOptionId?: string;
9
+ anchor?: CommentAnchor;
9
10
  } | {
10
11
  kind: 'freetext';
11
12
  buffer: string;
12
13
  cursor: number;
14
+ } | {
15
+ kind: 'text';
16
+ buffer: string;
17
+ cursor: number;
13
18
  };
14
19
  export interface TuiState {
15
20
  phase: Phase;
16
21
  currentIndex: number;
17
- interactions: Interaction[];
18
- responses: Map<string, InteractionResponse>;
19
- preAnsweredIds: Set<string>;
22
+ manifest: PageManifest;
23
+ slots: PageSlot[];
24
+ overviewProse: string[];
25
+ slotProse: Map<number, string[]>;
26
+ finalProse: string[];
27
+ responses: Map<string, SlotResponse>;
28
+ productKinds: string[];
20
29
  inputMode: InputMode;
21
30
  selectedAction: number;
22
31
  bodyMode: 'question';
@@ -32,13 +41,15 @@ export interface TuiState {
32
41
  persist?: () => void;
33
42
  }
34
43
  export interface MountedPanelOpts {
35
- deck: Deck;
36
- progressPath?: string;
37
- onEditorRequest?: () => void;
44
+ manifest: PageManifest;
45
+ projection: PageSourceProjection;
46
+ productKinds?: string[];
47
+ initialResponses?: PageResponses | null;
38
48
  cols: number;
39
49
  rows: number;
40
- onProgress?: (responses: InteractionResponse[]) => void;
41
- onComplete?: (responses: InteractionResponse[]) => void;
50
+ onEditorRequest?: () => void;
51
+ onProgress?: (responses: PageResponses) => void;
52
+ onComplete?: (responses: PageResponses) => void;
42
53
  onExit?: () => void;
43
54
  onDirty?: () => void;
44
55
  }
@@ -47,11 +58,12 @@ export interface MountedPanel {
47
58
  render(): string[];
48
59
  handleResize(cols: number, rows: number): string[];
49
60
  unmount(): void;
50
- loadDeck(deck: Deck, opts?: {
51
- progressPath?: string;
61
+ loadPage(manifest: PageManifest, projection: PageSourceProjection, opts?: {
62
+ initialResponses?: PageResponses | null;
63
+ productKinds?: string[];
52
64
  }): void;
53
65
  canAcceptHostKeys(): boolean;
54
- atDeckTop(): boolean;
66
+ atPageTop(): boolean;
55
67
  getInputBuffer(): string | undefined;
56
68
  setInputBuffer(text: string): void;
57
69
  }
@@ -8,8 +8,8 @@ function ansiColor(text, color) {
8
8
  default: return text;
9
9
  }
10
10
  }
11
- export const KIND_ICON = { notify: '✉', decision: '◆', context: '✎', error: '⚠', review: '▤' };
12
- export const KIND_COLOR = { notify: 'gray', decision: 'cyan', context: 'cyan', error: 'red', review: 'yellow' };
11
+ export const KIND_ICON = { notify: '✉', decision: '◆', context: '✎', error: '⚠', review: '▤', display: '✉', rich: '▣', interactive: '◆' };
12
+ export const KIND_COLOR = { notify: 'gray', decision: 'cyan', context: 'cyan', error: 'red', review: 'yellow', display: 'gray', rich: 'cyan', interactive: 'cyan' };
13
13
  export function formatTimeAgo(iso) {
14
14
  const minutes = Math.floor((Date.now() - new Date(iso).getTime()) / 60000);
15
15
  if (minutes >= 60)
@@ -35,7 +35,9 @@ export function buildInboxLines(items, width, selectedIndex, height = Number.MAX
35
35
  return lines;
36
36
  }
37
37
  function buildItemLines(item, index, selectedIndex, contentWidth) {
38
- const kind = item.kind === 'deck' ? item.interactionKind ?? 'decision' : 'review';
38
+ const kind = item.kind === 'page'
39
+ ? item.awaitsResponse === false ? 'display' : item.dialect === 'html' ? 'rich' : 'interactive'
40
+ : item.kind;
39
41
  const icon = KIND_ICON[kind] ?? '·';
40
42
  // The inbox is a title-first triage surface. Origin details remain available
41
43
  // after opening a ticket, rather than consuming the list's scarce row width.
@@ -1,7 +1,7 @@
1
1
  // `crtr canvas attention` — aggregate pending human tickets across the canvas.
2
2
  //
3
- // A pending "ticket" is any humanloop interaction awaiting a human: a deck,
4
- // notification, or anchored review. Each lives in its human bridge node
3
+ // A pending "ticket" is any humanloop interaction awaiting a human: a page or
4
+ // an anchored review. Each lives in its human bridge node
5
5
  // directory and is attributed through that bridge's parent relation. This
6
6
  // subtree surfaces a direct node projection; cwd is display metadata only.
7
7
  //
@@ -1,17 +1,20 @@
1
- import { existsSync, readFileSync } from 'node:fs';
2
- import { resolve } from 'node:path';
1
+ import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { join, resolve } from 'node:path';
3
+ import { tmpdir } from 'node:os';
3
4
  import { defineBranch, defineLeaf } from '../../core/command.js';
4
5
  import { InputError } from '../../core/io.js';
6
+ import { readConfig } from '../../core/config.js';
7
+ import { derivePageManifest } from '../../core/human/page.js';
5
8
  import { checkMarkdown, renderMarkdown } from '../../core/termrender/termrender.js';
6
- function markdownSource(input, context) {
9
+ function documentSource(input, context) {
7
10
  const path = input['path'];
8
11
  const body = input['body'];
9
12
  const stdinProvided = context?.providedParams.has('body') === true;
10
13
  if (path === undefined && !stdinProvided) {
11
14
  throw new InputError({
12
15
  error: 'missing_parameter',
13
- message: 'provide exactly one markdown source: PATH or stdin',
14
- next: 'Run `crtr human doc check -h` or `crtr human doc render -h` and provide a path or pipe markdown on stdin.',
16
+ message: 'provide exactly one document source: PATH or stdin',
17
+ next: 'Run `crtr human doc check -h` or `crtr human doc render -h` and provide a path or pipe a document on stdin.',
15
18
  });
16
19
  }
17
20
  if (path !== undefined && path !== '-' && stdinProvided && body.trim() !== '') {
@@ -19,51 +22,94 @@ function markdownSource(input, context) {
19
22
  error: 'bad_invocation',
20
23
  message: 'both a positional path and piped stdin were provided',
21
24
  field: 'path, body',
22
- next: 'Pass exactly one markdown source: either PATH or piped stdin, not both.',
25
+ next: 'Pass exactly one document source: either PATH or piped stdin, not both.',
23
26
  });
24
27
  }
25
28
  if (path === undefined || path === '-')
26
- return body;
29
+ return { body };
27
30
  const abs = resolve(path);
28
31
  if (!existsSync(abs)) {
29
32
  throw new InputError({
30
33
  error: 'file_not_found',
31
34
  message: `file not found: ${abs}`,
32
35
  field: 'path',
33
- next: 'Provide a path to an existing markdown file, or pipe markdown on stdin.',
36
+ next: 'Provide a path to an existing markdown or HTML file, or pipe a document on stdin.',
34
37
  });
35
38
  }
36
- return readFileSync(abs, 'utf8');
39
+ return { body: readFileSync(abs, 'utf8'), path: abs };
40
+ }
41
+ function markdownSource(input, context) {
42
+ return documentSource(input, context).body;
43
+ }
44
+ function pageDialect(source) {
45
+ const extension = source.path?.toLowerCase();
46
+ if (extension?.endsWith('.html') || extension?.endsWith('.htm'))
47
+ return 'html';
48
+ if (/^\s*<!doctype\b/i.test(source.body) || /^\s*<html(?:\s|>)/i.test(source.body))
49
+ return 'html';
50
+ if (source.body.startsWith('---'))
51
+ return 'md';
52
+ return undefined;
53
+ }
54
+ function checkPage(source, dialect) {
55
+ const config = readConfig('user');
56
+ if (dialect === 'html' && !config.html_pages) {
57
+ return { ok: false, error: 'HTML page validation requires the user-scope html_pages capability flag to be enabled (html_pages is false).' };
58
+ }
59
+ let tempDir;
60
+ try {
61
+ const sourcePath = source.path ?? (() => {
62
+ tempDir = mkdtempSync(join(tmpdir(), 'crtr-human-doc-'));
63
+ const path = join(tempDir, dialect === 'html' ? 'page.html' : 'page.md');
64
+ writeFileSync(path, source.body, 'utf8');
65
+ return path;
66
+ })();
67
+ derivePageManifest(sourcePath, dialect, config.page_components);
68
+ return { ok: true };
69
+ }
70
+ catch (error) {
71
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
72
+ }
73
+ finally {
74
+ if (tempDir !== undefined)
75
+ rmSync(tempDir, { recursive: true, force: true });
76
+ }
37
77
  }
38
78
  const humanDocCheck = defineLeaf({
39
79
  name: 'check',
40
- description: 'validate a directive-flavored markdown body',
41
- whenToUse: 'validating a deck or review body before submitting it.',
80
+ description: 'validate a directive-flavored markdown body or page source',
81
+ whenToUse: 'validating a page source or review body before submitting it.',
42
82
  help: {
43
83
  name: 'human doc check',
44
- summary: 'validate directive-flavored markdown through the pinned managed renderer',
45
- guide: 'The pinned renderer runs from a private managed venv and is not on PATH, so this is the only way to validate a deck or review body before submitting it.',
84
+ summary: 'validate directive-flavored markdown or a page through the managed renderer and page schemas',
85
+ guide: 'Plain markdown is checked by the pinned renderer. A file with page front matter or an HTML document is parsed as a page; HTML page validation requires the user-scope html_pages capability flag.',
46
86
  params: [
47
- { kind: 'positional', name: 'path', type: 'path', required: false, constraint: 'Markdown file to validate. Provide exactly one of PATH or stdin; use - to read stdin.' },
48
- { kind: 'stdin', name: 'body', required: false, constraint: 'Markdown to validate. Provide exactly one of piped stdin or PATH.' },
87
+ { kind: 'positional', name: 'path', type: 'path', required: false, constraint: 'Markdown or HTML file to validate. Files ending in .html/.htm, HTML documents beginning with a doctype or <html, and markdown beginning with --- front matter are checked as pages. Provide exactly one of PATH or stdin; use - to read stdin.' },
88
+ { kind: 'stdin', name: 'body', required: false, constraint: 'Markdown or HTML to validate. Provide exactly one of piped stdin or PATH.' },
49
89
  ],
50
90
  output: [
51
91
  { name: 'ok', type: 'boolean', required: true, constraint: 'True when the markdown is valid.' },
52
- { name: 'error', type: 'string', required: false, constraint: 'The renderer validation error when ok is false.' },
92
+ { name: 'error', type: 'string', required: false, constraint: 'The renderer or page-parser validation error when ok is false.' },
53
93
  ],
54
94
  outputKind: 'object',
55
- effects: ['Read-only: invokes the private managed renderer to validate the supplied body.'],
95
+ effects: ['Read-only: invokes the private managed renderer or page parser to validate the supplied document.'],
96
+ },
97
+ run: async (input, context) => {
98
+ const source = documentSource(input, context);
99
+ const dialect = pageDialect(source);
100
+ if (dialect !== undefined)
101
+ return checkPage(source, dialect);
102
+ return checkMarkdown(source.body);
56
103
  },
57
- run: async (input, context) => checkMarkdown(markdownSource(input, context)),
58
104
  });
59
105
  const humanDocRender = defineLeaf({
60
106
  name: 'render',
61
107
  description: 'render a directive-flavored markdown body',
62
- whenToUse: 'previewing how a deck or review body renders before submitting it.',
108
+ whenToUse: 'previewing how a page or review body renders before submitting it.',
63
109
  help: {
64
110
  name: 'human doc render',
65
111
  summary: 'render directive-flavored markdown through the pinned managed renderer',
66
- guide: 'The pinned renderer runs from a private managed venv and is not on PATH, so this is the only way to render a deck or review body before submitting it.',
112
+ guide: 'The pinned renderer runs from a private managed venv and is not on PATH, so this is the only way to render a page or review body before submitting it.',
67
113
  params: [
68
114
  { kind: 'positional', name: 'path', type: 'path', required: false, constraint: 'Markdown file to render. Provide exactly one of PATH or stdin; use - to read stdin.' },
69
115
  { kind: 'stdin', name: 'body', required: false, constraint: 'Markdown to render. Provide exactly one of piped stdin or PATH.' },
@@ -1,12 +1,12 @@
1
- import type { DeckSource } from '../../core/human/types.js';
1
+ import type { PageSource } from '../../core/human/page-schema.js';
2
2
  /** Provenance for crouter tickets. Ticket *identity* comes from the bridge node
3
3
  * directory, never ticket data; `nodeId` carries the originating node so a
4
4
  * consumer can relate a ticket back to whatever that node belongs to, and
5
5
  * `sessionName` is a presentation label only. Review tickets already stamp
6
- * `nodeId` the same way (`core/review/realize.ts`), so decks match them.
6
+ * `nodeId` the same way (`core/review/realize.ts`), so pages match them.
7
7
  * `nodeId` is stamped even when the daemon is unreachable — losing the label is
8
8
  * survivable, losing machine attribution is not. */
9
- export declare function sourceStamp(): Promise<DeckSource>;
9
+ export declare function sourceStamp(): Promise<PageSource>;
10
10
  export declare const humanAsk: import("../../core/command.js").LeafDef;
11
11
  export declare const humanNotify: import("../../core/command.js").LeafDef;
12
12
  export declare const humanShow: import("../../core/command.js").LeafDef;