@north-light/crouter 0.3.196 → 0.3.198

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 (148) hide show
  1. package/dist/api/client.d.ts +2 -2
  2. package/dist/api/dto/inbox.d.ts +46 -0
  3. package/dist/builtin-memory/00-runtime-base.md +3 -3
  4. package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
  5. package/dist/builtin-memory/internal/storage-tiers.md +1 -1
  6. package/dist/builtin-memory/wedged-child-on-runaway-bash.md +1 -1
  7. package/dist/clients/attach/render/chat-view.d.ts +3 -8
  8. package/dist/clients/attach/render/chat-view.js +3 -24
  9. package/dist/clients/attach/render/markdown-source.js +0 -6
  10. package/dist/clients/attach/render/tool-calls.js +16 -2
  11. package/dist/clients/attach/viewer.js +613 -602
  12. package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +2 -2
  13. package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +11 -4
  14. package/dist/clients/inbox/controller.js +4 -4
  15. package/dist/clients/inbox/page-adapter.d.ts +2 -1
  16. package/dist/clients/inbox/tui/render.js +46 -25
  17. package/dist/clients/inbox/tui/slots.d.ts +1 -3
  18. package/dist/clients/inbox/tui/slots.js +8 -37
  19. package/dist/clients/inbox/tui/types.d.ts +4 -3
  20. package/dist/commands/__tests__/human.test.js +160 -23
  21. package/dist/commands/canvas-browse.js +1 -1
  22. package/dist/commands/human/doc.js +21 -74
  23. package/dist/commands/human/html.d.ts +2 -0
  24. package/dist/commands/human/html.js +81 -0
  25. package/dist/commands/human/prompts.d.ts +5 -4
  26. package/dist/commands/human/prompts.js +90 -168
  27. package/dist/commands/human/shared.d.ts +5 -6
  28. package/dist/commands/human/shared.js +13 -61
  29. package/dist/commands/human.js +7 -2
  30. package/dist/core/__tests__/daemon-boot.test.js +7 -4
  31. package/dist/core/__tests__/dead-node-policy-table.test.js +20 -36
  32. package/dist/core/__tests__/human-deliver.test.js +28 -37
  33. package/dist/core/__tests__/seam/broker-crash-teardown.test.js +13 -11
  34. package/dist/core/__tests__/seam/dormancy-release.test.js +63 -4
  35. package/dist/core/__tests__/serial/human-deliver-e2e.test.js +3 -13
  36. package/dist/core/canvas/__tests__/attention.test.js +2 -2
  37. package/dist/core/canvas/__tests__/render-remote.test.js +2 -2
  38. package/dist/core/canvas/browse/app.d.ts +1 -1
  39. package/dist/core/canvas/browse/app.js +21 -3
  40. package/dist/core/canvas/browse/render.d.ts +4 -0
  41. package/dist/core/canvas/browse/render.js +5 -1
  42. package/dist/core/canvas/canvas.d.ts +26 -0
  43. package/dist/core/canvas/canvas.js +55 -0
  44. package/dist/core/command.js +6 -11
  45. package/dist/core/config.js +3 -2
  46. package/dist/core/help.d.ts +3 -0
  47. package/dist/core/human/__tests__/html-markdown.test.d.ts +1 -0
  48. package/dist/core/human/__tests__/html-markdown.test.js +52 -0
  49. package/dist/core/human/__tests__/page-catalog.test.d.ts +1 -0
  50. package/dist/core/human/__tests__/page-catalog.test.js +16 -0
  51. package/dist/core/human/__tests__/page-render.test.js +40 -23
  52. package/dist/core/human/__tests__/page-tickets.test.js +48 -27
  53. package/dist/core/human/__tests__/page.test.js +48 -65
  54. package/dist/core/human/__tests__/serial/inbox-core.test.js +5 -15
  55. package/dist/core/human/answer-text.d.ts +3 -0
  56. package/dist/core/human/answer-text.js +98 -0
  57. package/dist/core/human/answer.d.ts +51 -0
  58. package/dist/core/human/answer.js +125 -0
  59. package/dist/core/human/component-docs.d.ts +14 -0
  60. package/dist/core/human/component-docs.js +161 -0
  61. package/dist/core/human/html-markdown.d.ts +3 -0
  62. package/dist/core/human/html-markdown.js +288 -0
  63. package/dist/core/human/page-catalog.d.ts +19 -3
  64. package/dist/core/human/page-catalog.js +73 -16
  65. package/dist/core/human/page-render.d.ts +3 -2
  66. package/dist/core/human/page-render.js +19 -45
  67. package/dist/core/human/page-schema.d.ts +108 -8
  68. package/dist/core/human/page-schema.js +88 -31
  69. package/dist/core/human/page-synth.d.ts +2 -4
  70. package/dist/core/human/page-synth.js +23 -7
  71. package/dist/core/human/page.d.ts +14 -7
  72. package/dist/core/human/page.js +178 -90
  73. package/dist/core/human/review-schema.d.ts +1 -0
  74. package/dist/core/human/review-schema.js +1 -1
  75. package/dist/core/human/scan.d.ts +3 -2
  76. package/dist/core/human/scan.js +7 -8
  77. package/dist/core/human/summary.d.ts +4 -7
  78. package/dist/core/human/summary.js +4 -60
  79. package/dist/core/human/tickets.d.ts +10 -9
  80. package/dist/core/human/tickets.js +5 -3
  81. package/dist/core/human/types.d.ts +4 -0
  82. package/dist/core/keybindings/catalog.d.ts +2 -2
  83. package/dist/core/keybindings/catalog.js +1 -0
  84. package/dist/core/review/realize.js +9 -1
  85. package/dist/core/runtime/boot-root.js +6 -1
  86. package/dist/core/runtime/broker.js +8 -6
  87. package/dist/core/runtime/busy.d.ts +4 -3
  88. package/dist/core/runtime/busy.js +4 -3
  89. package/dist/core/runtime/spawn.js +4 -4
  90. package/dist/core/termrender/display.d.ts +7 -3
  91. package/dist/core/termrender/display.js +45 -9
  92. package/dist/core/termrender/termrender.d.ts +3 -12
  93. package/dist/core/termrender/termrender.js +6 -37
  94. package/dist/core/user-settings.d.ts +17 -2
  95. package/dist/core/user-settings.js +32 -11
  96. package/dist/daemon/api/handlers/human.js +1 -1
  97. package/dist/daemon/api/handlers/inbox.js +22 -7
  98. package/dist/daemon/cron-run.js +16 -2
  99. package/dist/daemon/crtrd.js +1 -1
  100. package/dist/daemon/fleet.d.ts +22 -10
  101. package/dist/daemon/fleet.js +43 -24
  102. package/dist/daemon/human/finish.d.ts +2 -1
  103. package/dist/daemon/human/finish.js +45 -28
  104. package/dist/daemon/human/sweep.js +4 -2
  105. package/dist/daemon/reconcilers/broker-supervision.js +11 -8
  106. package/dist/daemon/reconcilers/dormant-inbox.js +11 -6
  107. package/dist/daemon/reconcilers/live-obligation.d.ts +21 -1
  108. package/dist/daemon/reconcilers/live-obligation.js +30 -2
  109. package/dist/daemon/reconcilers/storage-maintenance.d.ts +5 -0
  110. package/dist/daemon/reconcilers/storage-maintenance.js +28 -1
  111. package/dist/daemon/review/sweep.js +12 -1
  112. package/dist/pages/bundle.css +1 -1
  113. package/dist/pages/bundle.js +1008 -577
  114. package/dist/pages/comments.d.ts +29 -0
  115. package/dist/pages/comments.js +51 -0
  116. package/dist/pages/controls.d.ts +98 -0
  117. package/dist/pages/controls.js +261 -0
  118. package/dist/pages/elements/cards.d.ts +15 -4
  119. package/dist/pages/elements/cards.js +299 -186
  120. package/dist/pages/elements/chart.d.ts +22 -0
  121. package/dist/pages/elements/chart.js +107 -91
  122. package/dist/pages/elements/options.d.ts +9 -0
  123. package/dist/pages/elements/options.js +268 -155
  124. package/dist/pages/elements/pages.d.ts +6 -0
  125. package/dist/pages/elements/pages.js +60 -38
  126. package/dist/pages/elements/slot.d.ts +5 -0
  127. package/dist/pages/elements/slot.js +17 -10
  128. package/dist/pages/elements/table.d.ts +17 -0
  129. package/dist/pages/elements/table.js +336 -228
  130. package/dist/pages/elements/text.d.ts +9 -3
  131. package/dist/pages/elements/text.js +350 -143
  132. package/dist/pages/host.d.ts +0 -24
  133. package/dist/pages/host.js +0 -17
  134. package/dist/pages/readonly.d.ts +24 -0
  135. package/dist/pages/readonly.js +31 -0
  136. package/dist/pages/responses.d.ts +37 -0
  137. package/dist/pages/responses.js +56 -0
  138. package/dist/pi-extensions/canvas-bash-valve.d.ts +9 -1
  139. package/dist/pi-extensions/canvas-bash-valve.js +12 -8
  140. package/dist/types.d.ts +19 -5
  141. package/dist/types.js +2 -1
  142. package/package.json +1 -1
  143. package/runtime.lock.json +2 -2
  144. package/scripts/install-runtime.mjs +19 -24
  145. package/dist/clients/attach/render/html-markdown.d.ts +0 -13
  146. package/dist/clients/attach/render/html-markdown.js +0 -206
  147. package/dist/core/human/markdown-html.d.ts +0 -8
  148. package/dist/core/human/markdown-html.js +0 -581
@@ -16,7 +16,7 @@ import type { CredentialRemovalResultDTO, CredentialResultDTO, InstallCredential
16
16
  import type { CreateHumanBridgeRequest, HumanBridgeResultDTO, HumanCancelRequest, HumanCancelResultDTO, HumanResolveRequest, HumanResolveResultDTO } from './dto/human.js';
17
17
  import type { CancelReviewRequest, CreateReviewRequest, ListReviewsQuery, ReviewCancelResultDTO, ReviewDocumentBaseDTO, ReviewDTO, ReviewListDTO, ReviewSubmitResultDTO } from './dto/reviews.js';
18
18
  import type { CreateReviewCommentRequest, EditReviewCommentRequest, ListReviewCommentsQuery, ReadReviewCommentEventsQuery, ReviewCommentActionRequest, ReviewCommentDetailDTO, ReviewCommentEventsDTO, ReviewCommentListDTO, ReviewCommentMutationDTO, ReviewCommentRangeBatchRequest, ReviewCommentRangeBatchResultDTO } from './dto/review-comments.js';
19
- import type { CancelInboxTicketRequest, CanceledTicketResultDTO, InboxListDTO, InboxPageDTO, InboxPageHistoryDTO, InboxPageRenderDTO, PagesBundleAssetDTO, InboxTicketIdDTO, PageResponsesDTO, PageTicketResultDTO, RespondInboxPageRequest } from './dto/inbox.js';
19
+ import type { CancelInboxTicketRequest, CanceledTicketResultDTO, InboxListDTO, InboxPageDTO, InboxPageHistoryDTO, InboxPageRenderDTO, PagesBundleAssetDTO, InboxPageResponseDTO, InboxTicketIdDTO, PageResponsesDTO, PageTicketResultDTO, RespondInboxPageRequest } from './dto/inbox.js';
20
20
  import type { AttentionCountsDTO, AttentionDTO, DashboardDTO, DashboardQuery, HistoryGrepQuery, HistoryGrepResultDTO, HistoryReadQuery, HistoryReadResultDTO, HistorySearchQuery, HistorySearchResultDTO, PruneRequest, PruneResultDTO, RebuildIndexResultDTO, RosterDTO, SnapshotDTO } from './dto/canvas.js';
21
21
  import type { CloseWorktreeResultDTO } from './dto/worktree.js';
22
22
  import type { BrokerExtensionStateDTO, BrokerGeneratedNameRequest, BrokerGeneratedNameResultDTO, BrokerInboxCursorDirective, BrokerInboxCursorRequest, BrokerModelCommitRequest, BrokerModelCommitResultDTO, BrokerPersonaAckRequest, BrokerSessionBoundRequest, BrokerSessionBoundResultDTO, BrokerSettleDirective, BrokerSettleRequest } from './dto/broker-ops.js';
@@ -204,7 +204,7 @@ export declare class CrtrClient {
204
204
  /** Autosave partial page work. Omitted slots are permitted in progress updates. */
205
205
  postInboxProgress(ticketId: InboxTicketIdDTO, responses: PageResponsesDTO): Promise<void>;
206
206
  /** Get the published response for a resolved page ticket. 404 if pending or canceled. */
207
- getInboxResponse(ticketId: InboxTicketIdDTO): Promise<PageTicketResultDTO>;
207
+ getInboxResponse(ticketId: InboxTicketIdDTO): Promise<InboxPageResponseDTO>;
208
208
  /** The page's body-level HTML payload, for pending, resolved, and canceled
209
209
  * tickets alike. The host wraps it and injects the page runtime bundle. */
210
210
  renderHumanInboxPage(ticketId: InboxTicketIdDTO): Promise<InboxPageRenderDTO>;
@@ -5,6 +5,7 @@ export interface TicketSourceDTO {
5
5
  sessionName?: string;
6
6
  askedBy?: string;
7
7
  blockedSince?: IsoTime;
8
+ profileName?: string;
8
9
  nodeId?: string;
9
10
  }
10
11
  export interface ReviewTicketSummaryDTO {
@@ -28,6 +29,8 @@ export interface PageTicketSummaryDTO {
28
29
  slot_kinds: string[];
29
30
  awaits_response: boolean;
30
31
  state?: 'pending' | 'resolved' | 'canceled';
32
+ /** Present only on resolved history entries; the stored one-line answer digest. */
33
+ answer_digest?: string;
31
34
  }
32
35
  export type InboxTicketSummaryDTO = PageTicketSummaryDTO | ReviewTicketSummaryDTO;
33
36
  export interface InboxListDTO {
@@ -39,6 +42,7 @@ export interface PageSlotDTO {
39
42
  step: number;
40
43
  config: Record<string, unknown>;
41
44
  unvalidated?: true;
45
+ display?: true;
42
46
  }
43
47
  /** The nested protocol object is deliberately camelCase on the wire. */
44
48
  export interface PageManifestDTO {
@@ -131,6 +135,48 @@ export interface PageTicketResultDTO {
131
135
  summary: string;
132
136
  completedAt: IsoTime;
133
137
  }
138
+ export interface PageAnswerGroupDTO {
139
+ group: 'option' | 'card' | 'row' | 'column';
140
+ mode: 'single' | 'multi';
141
+ total: number;
142
+ picked: {
143
+ id: string;
144
+ label: string;
145
+ }[];
146
+ }
147
+ export interface PageAnswerCommentDTO {
148
+ anchor: PageCommentAnchorDTO;
149
+ anchorLabel: string;
150
+ text: string;
151
+ }
152
+ export interface PageAnswerSlotDTO {
153
+ id: string;
154
+ kind: string;
155
+ label?: string;
156
+ state: 'answered' | 'unanswered';
157
+ blank: boolean;
158
+ groups: PageAnswerGroupDTO[];
159
+ freetext?: string;
160
+ text?: {
161
+ value: string;
162
+ edited: boolean;
163
+ singleLine?: boolean;
164
+ };
165
+ comments: PageAnswerCommentDTO[];
166
+ raw?: Record<string, unknown>;
167
+ }
168
+ /** The camelCase protocol object shipped only by GET .../response. */
169
+ export interface PageAnswerDTO {
170
+ title: string;
171
+ subtitle?: string;
172
+ completedAt: IsoTime;
173
+ kind: 'answer' | 'acknowledgement';
174
+ digest: string;
175
+ slots: PageAnswerSlotDTO[];
176
+ }
177
+ export interface InboxPageResponseDTO extends PageTicketResultDTO {
178
+ answer: PageAnswerDTO;
179
+ }
134
180
  /** `POST /v1/human/inbox/:ticket_id/cancel` body. */
135
181
  export interface CancelInboxTicketRequest {
136
182
  reason?: string;
@@ -10,7 +10,7 @@ rationale: >-
10
10
 
11
11
  "Waiting is a way to end a turn" lived in its own ungated all-node doc until 2026-07-28. Same gate, same audience, never independently readable — so the split bought no routing and cost a stub cross-reference in this file pointing at a section spliced a few hundred tokens later. Split it back out only if it ever needs a gate of its own.
12
12
 
13
- The rich-Markdown line exists because supported viewer affordances are otherwise invisible to an agent working from ordinary Markdown defaults. Silas explicitly wanted one generic capability entrance rather than a permanent tag catalog in every node's system prompt.
13
+ The Mermaid line exists because the viewer's inline diagram affordance is otherwise invisible to an agent working from ordinary Markdown defaults.
14
14
  lint-ignore: length
15
15
  ---
16
16
 
@@ -41,8 +41,8 @@ Two different moves; don't conflate them. **Promote** when worthwhile independen
41
41
 
42
42
  Never yield carrying an unasked question: put anything you're still wondering for the human through `crtr human ask` BEFORE you yield — an in-flight ask survives the refresh, and its answer wakes your fresh window like any child's report.
43
43
 
44
- ## Rich Markdown
45
- When visual structure or progressive disclosure would land faster than prose, use a Mermaid fence or supported HTML in Markdown; the human's terminal viewer renders both inline. Put nonessential supporting detail in collapsible sections, and use highlighted text only for the one thing the reader should notice. Reach for them when the shape is the point, not for everything.
44
+ ## Mermaid diagrams
45
+ When visual structure would land faster than prose, use a Mermaid fence; the human's terminal viewer renders it inline. Reach for it when the shape is the point, not for everything.
46
46
 
47
47
  ## Waiting is a way to end a turn
48
48
 
@@ -48,4 +48,4 @@ Tear-down: `node lifecycle close` cascade-cancels a node + its exclusive subtree
48
48
 
49
49
  Use `node lifecycle revive <id>` to reopen one dormant or terminal node. It resumes an existing saved conversation by default; `--fresh` starts a new cycle on its existing session file, or a truly fresh session if no file exists. A finalized node requires `--reopen` before it can take a new mandate. Mass-reconnecting every eligible disconnected node after a reboot or outage is `canvas revive --all` instead. `reviveNode()` is the **only** sanctioned launcher of a node's broker engine — it builds the pi invocation, sets `CRTR_NODE_ID` + canvas extensions, runs `transition('revive')`, and starts the headless broker host, keeping the db row and broker process in lockstep. Never spawn `pi --session` raw, and never open a node by spawning pi directly — UIs go through `surface node focus` / `node lifecycle revive`.
50
50
 
51
- The daemon (`crtrd`, managed via `sys daemon start/stop/status`) supervises live broker exits. It releases an unattended broker after 15 minutes: residents with no live obligation become `done` and pruning-eligible, while terminal nodes and residents awaiting a live child, controller, human, deadline, or structured output become `idle` and remain wakeable. It applies a bounded respawn policy to interrupted nonterminal nodes: a cleanly aborted saved turn resumes with a continuation, while a dirty interrupted turn, pending refresh, or pending cycle starts a fresh cycle from the saved session. Repeated short-lived exits and boot failures terminalize the node as `dead`; no automatic recovery occurs after terminalization, so use an explicit lifecycle revive or an inbox wake. It does not host agents or open viewers. When activating source changes, build, run `npm run install-runtime`, then restart the daemon because new daemon/brokers select the atomically switched, immutable generation while live brokers retain their own. Restarting is safe: it never signals running nodes.
51
+ The daemon (`crtrd`, managed via `sys daemon start/stop/status`) supervises live broker exits. It releases an unattended broker after 15 minutes: residents with no live obligation become `done` and pruning-eligible, while terminal nodes and residents awaiting a live child, controller, human, deadline, or structured output become `idle` and remain wakeable. It applies a bounded respawn policy to interrupted nonterminal nodes: an interrupted saved turn resumes in the same conversation with a continuation, whether the broker persisted a clean abort or died abruptly. A pending refresh or pending cycle still starts a fresh cycle from the saved session. Repeated short-lived exits and boot failures terminalize the node as `dead`; no automatic recovery occurs after terminalization, so use an explicit lifecycle revive or an inbox wake. It does not host agents or open viewers. When activating source changes, build, run `npm run install-runtime`, then restart the daemon because new daemon/brokers select the atomically switched, immutable generation while live brokers retain their own. Restarting is safe: it never signals running nodes.
@@ -22,7 +22,7 @@ User-wide content with no cwd dimension also belongs here: `~/.crouter/profile-d
22
22
 
23
23
  ## 2. Canvas home — node-graph runtime state, node artifacts, and bounded diagnostics
24
24
 
25
- `~/.crouter/canvas/` (overridable with `CRTR_HOME`) is the cwd-agnostic node-graph home. `canvas.db` is the SQLite WAL topology store for nodes and edges, including durable tmux-pane focus. `nodes/<node_id>/` owns `meta.json`, `context/`, `reports/`, `messages/`, `inbox.jsonl`, `transcript.jsonl`, `session.ptr`, and `job/` state. Human ticket files (`page.json`, `page.md`, `run.json`, `response.json`, `review.json`, and `branch-point.jsonl`) live directly in a terminal human bridge node directory; `nodes/` is the single ticket root, derived rather than registered.
25
+ `~/.crouter/canvas/` (overridable with `CRTR_HOME`) is the cwd-agnostic node-graph home. `canvas.db` is the SQLite WAL topology store for nodes and edges, including durable tmux-pane focus. `nodes/<node_id>/` owns `meta.json`, `context/`, `reports/`, `messages/`, `inbox.jsonl`, `transcript.jsonl`, `session.ptr`, and `job/` state. Human ticket files (`page.json`, `page.html`, `run.json`, `response.json`, `review.json`, and `branch-point.jsonl`) live directly in a terminal human bridge node directory; `nodes/` is the single ticket root, derived rather than registered.
26
26
 
27
27
  Specifications and plans are ordinary node-context artifacts. They share the node's lifetime and are removed when that node is reaped.
28
28
 
@@ -13,7 +13,7 @@ A base node can wedge mid-turn on a runaway bash command (for example, an unboun
13
13
  ## Recover the process that is actually stuck
14
14
 
15
15
  - **A subprocess exists.** The daemon leaves the broker alone because a runaway bash/find/grep is the likely cause. Inspect the broker process tree, kill the scanner subprocess rather than its shell wrapper or node, and let the tool call return. The pi turn then resumes with its context intact. Tell the owning orchestrator if you discovered the wedge before its doctrine wake.
16
- - **No subprocess exists.** The broker itself is stalled. The daemon SIGTERMs it; its live-exit policy then makes a bounded recovery attempt. A broker-certified clean abort resumes the saved session with a continuation. A dirty interrupted turn, pending refresh, or pending cycle starts a new recovery cycle from the saved session instead. A broker that never established a session terminalizes rather than entering a respawn loop.
16
+ - **No subprocess exists.** The broker itself is stalled. The daemon SIGTERMs it; its live-exit policy then makes a bounded recovery attempt. An interrupted saved turn resumes in the same conversation with a continuation, whether the broker persisted a clean abort or died abruptly. A pending refresh or pending cycle starts a new cycle from the saved session instead. A broker that never established a session terminalizes rather than entering a respawn loop.
17
17
 
18
18
  Recovery is not unconditional. Repeated short-lived exits or a boot failure terminalize the node as `dead`; no daemon respawn follows that terminal state. Reopen it explicitly with `crtr node lifecycle revive <id>` (use `--reopen` if it finalized), or deliver an inbox wake where that is the intended control path. If the daemon is down, it cannot detect, signal, or respawn a wedge; restore the daemon before relying on lifecycle recovery.
19
19
 
@@ -111,15 +111,11 @@ export declare class ChatView {
111
111
  private readonly recapPalette;
112
112
  /** Bold accent for the expanded crtr-context label. */
113
113
  private readonly labelStyle;
114
- /** Undefined honors each `<details open>` attribute until Ctrl+O establishes
115
- * one viewer-wide disclosure state. */
116
- private detailsExpandedOverride;
117
114
  /** Viewer-local Markdown projection, handed to pi's message components so
118
115
  * THEY apply it at render time with the exact content width. The viewer keeps
119
116
  * no projected copy: the components hold canonical source, and each
120
- * transformer reads live viewer state (disclosure, palette) on every call.
121
- * Diagrams and the HTML allowlist apply only to assistant-authored
122
- * Markdown. */
117
+ * transformer reads live viewer state on every call. Diagrams apply only to
118
+ * assistant-authored Markdown. */
123
119
  private readonly markdownTransformers;
124
120
  /** The assistant message component currently being streamed (between
125
121
  * message_start and message_end for an assistant turn). */
@@ -204,8 +200,7 @@ export declare class ChatView {
204
200
  * one process it should call this on detach to avoid a leaked interval. */
205
201
  dispose(): void;
206
202
  /** Ctrl+O prefers summaries when the user enabled both folding and summary
207
- * generation. The same disclosure state drives assistant-authored `<details>`
208
- * sections, so the transcript has one reveal-more action rather than two. */
203
+ * generation. */
209
204
  toggleToolsExpanded(): ToolDisplayToggle;
210
205
  /** The `fold finished tool calls` view setting (`/fold-tools`, Alt+C → z → f).
211
206
  * Returns the new state so the caller can surface a notice. */
@@ -29,7 +29,6 @@ import { CRTR_CYCLE_DIVIDER_CUSTOM_TYPE } from '../../../core/runtime/session-cy
29
29
  import { generatedContextExpandedBody, generatedContextPresentation, isRuntimeRestartContinuation, } from '../../../shared/generated-context.js';
30
30
  import { assistantVisibleText, isTrueUserMessage, } from '../../../shared/tool-groups.js';
31
31
  import { transformDiagramFences } from './diagram.js';
32
- import { transformHtmlMarkdown } from './html-markdown.js';
33
32
  import { styleAttachAgentMarkdown, styleAttachMarkdownSource, styleAttachMessageMarkdown, styleAttachSummaryMarkdown, } from './markdown-source.js';
34
33
  import { createCrtrEditToolDefinition } from './edit-diff.js';
35
34
  import { GroupActivityRecorder } from './group-activity.js';
@@ -305,27 +304,13 @@ export class ChatView {
305
304
  recapPalette;
306
305
  /** Bold accent for the expanded crtr-context label. */
307
306
  labelStyle = (s) => `\x1b[1m${s}\x1b[22m`;
308
- /** Undefined honors each `<details open>` attribute until Ctrl+O establishes
309
- * one viewer-wide disclosure state. */
310
- detailsExpandedOverride;
311
307
  /** Viewer-local Markdown projection, handed to pi's message components so
312
308
  * THEY apply it at render time with the exact content width. The viewer keeps
313
309
  * no projected copy: the components hold canonical source, and each
314
- * transformer reads live viewer state (disclosure, palette) on every call.
315
- * Diagrams and the HTML allowlist apply only to assistant-authored
316
- * Markdown. */
310
+ * transformer reads live viewer state on every call. Diagrams apply only to
311
+ * assistant-authored Markdown. */
317
312
  markdownTransformers = [
318
313
  (markdown, { messageType, availableWidth }) => (messageType === 'assistant' ? transformDiagramFences(markdown, availableWidth) : markdown),
319
- (markdown, { messageType }) => (messageType === 'assistant'
320
- ? transformHtmlMarkdown(markdown, {
321
- detailsExpanded: this.detailsExpandedOverride,
322
- styles: {
323
- kbd: (text) => this.recapPalette.active(this.recapPalette.bold(`‹${text}›`)),
324
- mark: (text) => `\x1b[7m${this.recapPalette.bold(this.recapPalette.accent(text))}\x1b[27m`,
325
- hint: this.dimStyle,
326
- },
327
- })
328
- : markdown),
329
314
  (markdown, { messageType }) => (messageType === 'user' ? styleAttachMarkdownSource(markdown) : styleAttachAgentMarkdown(markdown)),
330
315
  ];
331
316
  /** The assistant message component currently being streamed (between
@@ -674,8 +659,7 @@ export class ChatView {
674
659
  // `setToolsExpanded` / `toggleThinkingBlockVisibility`.
675
660
  // -------------------------------------------------------------------------
676
661
  /** Ctrl+O prefers summaries when the user enabled both folding and summary
677
- * generation. The same disclosure state drives assistant-authored `<details>`
678
- * sections, so the transcript has one reveal-more action rather than two. */
662
+ * generation. */
679
663
  toggleToolsExpanded() {
680
664
  let result;
681
665
  if (this.summarizeToolCalls && this.foldSettledTools) {
@@ -687,11 +671,6 @@ export class ChatView {
687
671
  this.toolOutputExpanded = !this.toolOutputExpanded;
688
672
  result = { kind: 'output', expanded: this.toolOutputExpanded };
689
673
  }
690
- this.detailsExpandedOverride = this.toolOutputExpanded;
691
- // The disclosure state lives in the transformer closure, so the transcript
692
- // only has to drop its projections: pi's message components rebuild their
693
- // Markdown on invalidate() and re-run the transformer on the next render.
694
- this.historyContainer.invalidate();
695
674
  this.applyToolDisplay();
696
675
  return result;
697
676
  }
@@ -30,10 +30,6 @@ const FENCE_OPEN = /^\s{0,3}(`{3,}|~{3,})/;
30
30
  * a literal `>` stays untouched rather than being mis-split. */
31
31
  const XML_TAG_LINE = /^([ \t]{0,3})(<\/?)([A-Za-z][\w.:-]*)((?:[ \t]+[^<>]*?)?)(\/?)>[ \t]*$/;
32
32
  const XML_ATTRIBUTE = /([^\s=]+)(?:=("[^"]*"|'[^']*'|[^\s]*))?/g;
33
- /** Tags the HTML allowlist owns (see html-markdown.ts). A `<details>` block
34
- * that reaches this pass is one that failed validation there and is meant to
35
- * stay literal. */
36
- const HTML_ALLOWLIST_TAGS = new Set(['details', 'summary', 'kbd', 'mark']);
37
33
  // A constant-lightness ramp from warm yellow to cool blue, with saturation
38
34
  // deliberately reduced at each step so the hierarchy recedes rather than
39
35
  // becoming a tour of unrelated accent colors.
@@ -85,8 +81,6 @@ function styleXmlTagLine(line) {
85
81
  if (!match)
86
82
  return undefined;
87
83
  const [, indent, open, name, attributes, selfClose] = match;
88
- if (HTML_ALLOWLIST_TAGS.has(name.toLowerCase()))
89
- return undefined;
90
84
  return indent
91
85
  + paint(XML_PUNCTUATION, open)
92
86
  + paint(XML_NAME, name)
@@ -15,6 +15,7 @@ import { Text, getCapabilities, hyperlink, truncateToWidth, visibleWidth, wrapTe
15
15
  import { homedir } from 'node:os';
16
16
  import { isAbsolute, resolve } from 'node:path';
17
17
  import { pathToFileURL } from 'node:url';
18
+ import { normalizeBashJobPurpose } from '../../../core/bash-jobs.js';
18
19
  import { TOOL_ICON } from '../../../core/preview-registry.js';
19
20
  function str(value) {
20
21
  return typeof value === 'string' ? value : '';
@@ -216,18 +217,25 @@ function styleCommandLine(line, theme) {
216
217
  * there is more. Expanded (ctrl+o) it prints every line of the command, wrapped
217
218
  * (never clipped — expanded is the raw truth) into the pane with continuations
218
219
  * hanging under the icon. A line wider than the pane would desync pi-tui's line
219
- * accounting against the terminal's own wrapping and garble the frame. */
220
+ * accounting against the terminal's own wrapping and garble the frame.
221
+ *
222
+ * FOLDED, a call that supplied a `purpose` shows that clause INSTEAD of the
223
+ * command: the fold is a dense index of what the turn did, and the agent's own
224
+ * words for why it ran this read better there than `rg -n … | head -30`. The
225
+ * command itself is never lost — unfolding (or ctrl+o) shows it verbatim. */
220
226
  class BashCallComponent {
221
227
  command = '';
228
+ purpose;
222
229
  expanded = false;
223
230
  timeout;
224
231
  theme;
225
232
  /** Nothing is cached across paints (render recomputes from the fields), so
226
233
  * pi's cache-drop hook has nothing to do here. */
227
234
  invalidate() { }
228
- update(theme, command, expanded, timeout) {
235
+ update(theme, command, purpose, expanded, timeout) {
229
236
  this.theme = theme;
230
237
  this.command = command;
238
+ this.purpose = purpose;
231
239
  this.expanded = expanded;
232
240
  this.timeout = timeout;
233
241
  }
@@ -236,6 +244,9 @@ class BashCallComponent {
236
244
  if (!theme)
237
245
  return [];
238
246
  const icon = theme.fg('accent', TOOL_ICON.bash);
247
+ if (this.purpose !== undefined) {
248
+ return [truncateToWidth(`${icon} ${theme.fg('toolOutput', this.purpose)}`, width, '…')];
249
+ }
239
250
  const lines = this.command.split('\n').map((line) => line.trim()).filter((line) => line.length > 0);
240
251
  const suffix = this.timeout ? theme.fg('muted', ` (timeout ${this.timeout}s)`) : '';
241
252
  if (lines.length === 0)
@@ -286,6 +297,9 @@ export function createCrtrPlainBashToolDefinition(cwd, fold = new FoldedToolCall
286
297
  }
287
298
  const component = context.lastComponent instanceof BashCallComponent ? context.lastComponent : new BashCallComponent();
288
299
  component.update(theme, str(a?.command),
300
+ // The purpose stands in for the command only while folded; it is normalized
301
+ // here because it is model-supplied text painted straight into the frame.
302
+ fold.folded ? normalizeBashJobPurpose(a?.purpose) ?? undefined : undefined,
289
303
  // Folded wins over ctrl+o expansion: a folded bash is always its one-line form.
290
304
  Boolean(context.expanded) && !fold.folded, typeof a?.timeout === 'number' ? a.timeout : undefined);
291
305
  return fold.capture(component);