@north-light/crouter 0.3.197 → 0.3.199

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 (339) hide show
  1. package/dist/api/client.d.ts +19 -19
  2. package/dist/api/client.js +29 -37
  3. package/dist/api/dto/broker-ops.d.ts +3 -1
  4. package/dist/api/dto/broker.d.ts +13 -2
  5. package/dist/api/dto/broker.js +4 -4
  6. package/dist/api/dto/human.d.ts +6 -5
  7. package/dist/api/dto/inbox.d.ts +126 -33
  8. package/dist/api/dto/nodes.d.ts +8 -2
  9. package/dist/api/dto/profiles.d.ts +23 -0
  10. package/dist/api/dto/profiles.js +2 -2
  11. package/dist/api/dto/worktree.d.ts +3 -5
  12. package/dist/api/routes.d.ts +3 -5
  13. package/dist/api/routes.js +4 -6
  14. package/dist/builtin-memory/00-runtime-base.md +8 -8
  15. package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
  16. package/dist/builtin-memory/02-lifecycle/00-terminal.md +3 -3
  17. package/dist/builtin-memory/04-orchestration-kernel.md +6 -6
  18. package/dist/builtin-memory/05-kinds/design/00-base.md +1 -1
  19. package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +2 -2
  20. package/dist/builtin-memory/05-kinds/review/00-base.md +2 -2
  21. package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -1
  22. package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +1 -1
  23. package/dist/builtin-memory/insights/capture.md +2 -2
  24. package/dist/builtin-memory/insights/init.md +3 -3
  25. package/dist/builtin-memory/internal/INDEX.md +1 -1
  26. package/dist/builtin-memory/internal/memory-loading.md +1 -1
  27. package/dist/builtin-memory/internal/nodes-and-canvas.md +3 -3
  28. package/dist/builtin-memory/internal/plugins.md +59 -2
  29. package/dist/builtin-memory/internal/storage-tiers.md +3 -1
  30. package/dist/builtin-memory/spec/roadmap.md +2 -2
  31. package/dist/builtin-memory/testing.md +1 -1
  32. package/dist/builtin-memory/wedged-child-on-runaway-bash.md +1 -1
  33. package/dist/clients/attach/chrome/bash-jobs.js +3 -3
  34. package/dist/clients/attach/render/chat-view.d.ts +29 -9
  35. package/dist/clients/attach/render/chat-view.js +64 -35
  36. package/dist/clients/attach/render/markdown-source.js +0 -6
  37. package/dist/clients/attach/render/page-block.d.ts +34 -0
  38. package/dist/clients/attach/render/page-block.js +226 -0
  39. package/dist/clients/attach/render/tool-calls.js +16 -2
  40. package/dist/clients/attach/session/whip.d.ts +2 -0
  41. package/dist/clients/attach/session/whip.js +2 -0
  42. package/dist/clients/attach/viewer.js +1419 -982
  43. package/dist/clients/conversation/projection.js +18 -3
  44. package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +9 -6
  45. package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +13 -14
  46. package/dist/clients/inbox/controller.js +10 -16
  47. package/dist/clients/inbox/page-adapter.d.ts +3 -4
  48. package/dist/clients/inbox/page-adapter.js +3 -4
  49. package/dist/clients/inbox/resolve.d.ts +1 -1
  50. package/dist/clients/inbox/resolve.js +3 -3
  51. package/dist/clients/inbox/review/document-surface.js +3 -1
  52. package/dist/clients/inbox/review/state.d.ts +2 -1
  53. package/dist/clients/inbox/review/state.js +5 -16
  54. package/dist/clients/inbox/tui/input.js +37 -19
  55. package/dist/clients/inbox/tui/panel.js +5 -31
  56. package/dist/clients/inbox/tui/render.d.ts +11 -0
  57. package/dist/clients/inbox/tui/render.js +101 -36
  58. package/dist/clients/inbox/tui/slots.d.ts +4 -4
  59. package/dist/clients/inbox/tui/slots.js +19 -44
  60. package/dist/clients/inbox/tui/types.d.ts +6 -10
  61. package/dist/clients/inbox/tui.js +2 -2
  62. package/dist/commands/__tests__/human.test.js +118 -24
  63. package/dist/commands/__tests__/node-message.test.js +14 -0
  64. package/dist/commands/attention.js +4 -4
  65. package/dist/commands/canvas-browse.js +1 -1
  66. package/dist/commands/canvas-snapshot.js +1 -1
  67. package/dist/commands/canvas.js +1 -1
  68. package/dist/commands/dashboard.js +1 -1
  69. package/dist/commands/human/{doc.d.ts → components.d.ts} +1 -1
  70. package/dist/commands/human/components.js +77 -0
  71. package/dist/commands/human/feedback.d.ts +2 -0
  72. package/dist/commands/human/feedback.js +108 -0
  73. package/dist/commands/human/prompts.d.ts +17 -8
  74. package/dist/commands/human/prompts.js +217 -226
  75. package/dist/commands/human/queue.js +57 -88
  76. package/dist/commands/human/shared.d.ts +4 -15
  77. package/dist/commands/human/shared.js +19 -61
  78. package/dist/commands/human.js +32 -19
  79. package/dist/commands/memory/origin.js +2 -2
  80. package/dist/commands/node/bash.js +4 -4
  81. package/dist/commands/node/inspect.js +3 -3
  82. package/dist/commands/node/lifecycle.js +1 -1
  83. package/dist/commands/node/message.js +2 -2
  84. package/dist/commands/node-worktree.js +8 -8
  85. package/dist/commands/pkg/browse.js +1 -1
  86. package/dist/commands/pkg/plugin-manage.js +129 -10
  87. package/dist/commands/profile/delete.js +41 -15
  88. package/dist/commands/profile.js +1 -1
  89. package/dist/commands/push.js +1 -1
  90. package/dist/commands/surface/node/focus.js +29 -14
  91. package/dist/commands/surface-edit.js +3 -3
  92. package/dist/commands/surface-inbox.d.ts +2 -0
  93. package/dist/commands/{human/inbox.js → surface-inbox.js} +14 -18
  94. package/dist/commands/surface.js +2 -1
  95. package/dist/commands/sys/doctor.js +60 -3
  96. package/dist/commands/sys/feedback.js +79 -25
  97. package/dist/commands/sys/settings.js +1 -1
  98. package/dist/core/__tests__/cron-broker-capacity.test.js +1 -1
  99. package/dist/core/__tests__/daemon-boot.test.js +7 -4
  100. package/dist/core/__tests__/dead-node-policy-table.test.js +20 -36
  101. package/dist/core/__tests__/extension-abort.test.js +5 -2
  102. package/dist/core/__tests__/fixtures/fake-engine.d.ts +13 -2
  103. package/dist/core/__tests__/fixtures/fake-engine.js +47 -3
  104. package/dist/core/__tests__/human-cancel-guard.test.js +31 -10
  105. package/dist/core/__tests__/human-deliver.test.js +41 -45
  106. package/dist/core/__tests__/human-node-not-supervised.test.js +1 -1
  107. package/dist/core/__tests__/plugin-page-components.test.js +157 -0
  108. package/dist/core/__tests__/seam/broker-crash-teardown.test.js +13 -11
  109. package/dist/core/__tests__/seam/dormancy-release.test.js +103 -4
  110. package/dist/core/__tests__/serial/flagship-lifecycle.test.js +2 -2
  111. package/dist/core/__tests__/serial/human-deliver-e2e.test.js +12 -16
  112. package/dist/core/__tests__/serial/live-mutation.test.js +1 -1
  113. package/dist/core/__tests__/serial/tmux-surface.test.js +4 -2
  114. package/dist/core/__tests__/serial/worktree.test.js +24 -198
  115. package/dist/core/__tests__/session-cycles.test.js +22 -0
  116. package/dist/core/__tests__/stop-guard.test.js +4 -4
  117. package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +19 -4
  118. package/dist/core/bash-jobs.d.ts +6 -0
  119. package/dist/core/bash-jobs.js +10 -0
  120. package/dist/core/canvas/__tests__/attention.test.js +8 -5
  121. package/dist/core/canvas/__tests__/render-remote.test.js +5 -5
  122. package/dist/core/canvas/attention.js +8 -6
  123. package/dist/core/canvas/browse/app.d.ts +1 -1
  124. package/dist/core/canvas/browse/app.js +21 -3
  125. package/dist/core/canvas/browse/model.d.ts +8 -2
  126. package/dist/core/canvas/browse/model.js +11 -4
  127. package/dist/core/canvas/browse/render.d.ts +4 -0
  128. package/dist/core/canvas/browse/render.js +5 -1
  129. package/dist/core/canvas/canvas.d.ts +33 -0
  130. package/dist/core/canvas/canvas.js +92 -0
  131. package/dist/core/canvas/crons.d.ts +9 -0
  132. package/dist/core/canvas/crons.js +51 -0
  133. package/dist/core/canvas/node-order.d.ts +22 -1
  134. package/dist/core/canvas/node-order.js +26 -1
  135. package/dist/core/canvas/render-source.d.ts +5 -0
  136. package/dist/core/canvas/render-source.js +1 -0
  137. package/dist/core/canvas/types.d.ts +9 -3
  138. package/dist/core/command-plugins/bundle.d.ts +6 -1
  139. package/dist/core/command-plugins/bundle.js +26 -13
  140. package/dist/core/command.js +8 -12
  141. package/dist/core/config.d.ts +47 -1
  142. package/dist/core/config.js +138 -4
  143. package/dist/core/help.d.ts +3 -0
  144. package/dist/core/help.js +1 -1
  145. package/dist/core/human/__tests__/page-catalog.test.d.ts +1 -0
  146. package/dist/core/human/__tests__/page-catalog.test.js +20 -0
  147. package/dist/core/human/__tests__/page-tickets.test.js +68 -34
  148. package/dist/core/human/__tests__/page.test.js +60 -119
  149. package/dist/core/human/__tests__/serial/inbox-core.test.js +6 -16
  150. package/dist/core/human/answer-text.d.ts +3 -0
  151. package/dist/core/human/answer-text.js +98 -0
  152. package/dist/core/human/answer.d.ts +53 -0
  153. package/dist/core/human/answer.js +119 -0
  154. package/dist/core/human/component-docs.d.ts +18 -0
  155. package/dist/core/human/component-docs.js +434 -0
  156. package/dist/core/human/convention.d.ts +17 -3
  157. package/dist/core/human/convention.js +36 -5
  158. package/dist/core/human/feedback-companion.d.ts +20 -0
  159. package/dist/core/human/feedback-companion.js +98 -0
  160. package/dist/core/human/feedback.d.ts +65 -0
  161. package/dist/core/human/feedback.js +116 -0
  162. package/dist/core/human/page-catalog.d.ts +56 -5
  163. package/dist/core/human/page-catalog.js +151 -22
  164. package/dist/core/human/page-errors.d.ts +4 -0
  165. package/dist/core/human/page-errors.js +7 -0
  166. package/dist/core/human/page-eval.d.ts +20 -0
  167. package/dist/core/human/page-eval.js +123 -0
  168. package/dist/core/human/page-schema.d.ts +48 -116
  169. package/dist/core/human/page-schema.js +100 -72
  170. package/dist/core/human/page-synth.d.ts +2 -4
  171. package/dist/core/human/page-synth.js +20 -7
  172. package/dist/core/human/page.d.ts +13 -21
  173. package/dist/core/human/page.js +147 -391
  174. package/dist/core/human/review-schema.d.ts +1 -0
  175. package/dist/core/human/review-schema.js +1 -1
  176. package/dist/core/human/root.d.ts +6 -2
  177. package/dist/core/human/root.js +12 -4
  178. package/dist/core/human/scan.d.ts +3 -3
  179. package/dist/core/human/scan.js +21 -21
  180. package/dist/core/human/summary.d.ts +2 -6
  181. package/dist/core/human/summary.js +3 -59
  182. package/dist/core/human/tickets.d.ts +22 -15
  183. package/dist/core/human/tickets.js +90 -28
  184. package/dist/core/human/types.d.ts +14 -2
  185. package/dist/core/keybindings/catalog.d.ts +2 -2
  186. package/dist/core/keybindings/catalog.js +1 -0
  187. package/dist/core/preview-registry.js +8 -10
  188. package/dist/core/profiles/default-binding.d.ts +4 -0
  189. package/dist/core/profiles/default-binding.js +24 -0
  190. package/dist/core/profiles/deletion-reservation.d.ts +8 -0
  191. package/dist/core/profiles/deletion-reservation.js +38 -0
  192. package/dist/core/profiles/manifest.d.ts +3 -0
  193. package/dist/core/profiles/manifest.js +12 -0
  194. package/dist/core/profiles/select.js +2 -2
  195. package/dist/core/review/realize.js +12 -4
  196. package/dist/core/review/store.d.ts +6 -0
  197. package/dist/core/review/store.js +31 -0
  198. package/dist/core/runtime/bearings.d.ts +0 -4
  199. package/dist/core/runtime/bearings.js +4 -62
  200. package/dist/core/runtime/bin-contributions.d.ts +44 -0
  201. package/dist/core/runtime/bin-contributions.js +186 -0
  202. package/dist/core/runtime/boot-root.js +6 -1
  203. package/dist/core/runtime/broker/client-registry.d.ts +4 -0
  204. package/dist/core/runtime/broker/client-registry.js +13 -1
  205. package/dist/core/runtime/broker/extension-abort.d.ts +1 -1
  206. package/dist/core/runtime/broker/extension-abort.js +4 -2
  207. package/dist/core/runtime/broker/fault-retry.js +4 -0
  208. package/dist/core/runtime/broker/frame-dispatch.d.ts +3 -1
  209. package/dist/core/runtime/broker/frame-dispatch.js +5 -6
  210. package/dist/core/runtime/broker/read-ops.d.ts +1 -1
  211. package/dist/core/runtime/broker/read-ops.js +2 -2
  212. package/dist/core/runtime/broker/rebind.d.ts +1 -0
  213. package/dist/core/runtime/broker/rebind.js +6 -1
  214. package/dist/core/runtime/broker-extension-render.js +19 -0
  215. package/dist/core/runtime/broker-protocol.d.ts +14 -0
  216. package/dist/core/runtime/broker.d.ts +1 -1
  217. package/dist/core/runtime/broker.js +25 -12
  218. package/dist/core/runtime/busy.d.ts +4 -3
  219. package/dist/core/runtime/busy.js +4 -3
  220. package/dist/core/runtime/front-door-env.d.ts +5 -0
  221. package/dist/core/runtime/front-door-env.js +16 -0
  222. package/dist/core/runtime/front-door.d.ts +1 -5
  223. package/dist/core/runtime/front-door.js +2 -5
  224. package/dist/core/runtime/host.d.ts +1 -1
  225. package/dist/core/runtime/invocation.d.ts +6 -0
  226. package/dist/core/runtime/invocation.js +10 -0
  227. package/dist/core/runtime/kickoff.js +34 -0
  228. package/dist/core/runtime/launch.d.ts +2 -6
  229. package/dist/core/runtime/node-read.js +4 -1
  230. package/dist/core/runtime/nodes.js +2 -0
  231. package/dist/core/runtime/promote.d.ts +1 -0
  232. package/dist/core/runtime/promote.js +3 -1
  233. package/dist/core/runtime/session-cycles.d.ts +6 -0
  234. package/dist/core/runtime/session-cycles.js +15 -1
  235. package/dist/core/runtime/session-visibility.d.ts +17 -8
  236. package/dist/core/runtime/session-visibility.js +19 -10
  237. package/dist/core/runtime/spawn-env.d.ts +9 -1
  238. package/dist/core/runtime/spawn-env.js +62 -2
  239. package/dist/core/runtime/spawn.js +4 -4
  240. package/dist/core/runtime/stop-guard.d.ts +2 -2
  241. package/dist/core/runtime/stop-guard.js +2 -2
  242. package/dist/core/runtime/stop-signals.d.ts +1 -1
  243. package/dist/core/runtime/stop-signals.js +2 -2
  244. package/dist/core/runtime/tmux-bindings.js +1 -1
  245. package/dist/core/session-model/session-state.d.ts +4 -3
  246. package/dist/core/session-model/session-state.js +14 -1
  247. package/dist/core/user-settings.d.ts +36 -2
  248. package/dist/core/user-settings.js +56 -14
  249. package/dist/core/worktree.d.ts +15 -45
  250. package/dist/core/worktree.js +59 -261
  251. package/dist/daemon/api/__tests__/seam/profile-delete.test.d.ts +1 -0
  252. package/dist/daemon/api/__tests__/seam/profile-delete.test.js +386 -0
  253. package/dist/daemon/api/handlers/bash-jobs.js +1 -1
  254. package/dist/daemon/api/handlers/broker-ops.js +9 -5
  255. package/dist/daemon/api/handlers/feedback-comments.d.ts +2 -0
  256. package/dist/daemon/api/handlers/feedback-comments.js +207 -0
  257. package/dist/daemon/api/handlers/human.js +24 -20
  258. package/dist/daemon/api/handlers/inbox.d.ts +10 -0
  259. package/dist/daemon/api/handlers/inbox.js +48 -109
  260. package/dist/daemon/api/handlers/profiles.js +11 -29
  261. package/dist/daemon/api/map.d.ts +6 -1
  262. package/dist/daemon/api/map.js +23 -0
  263. package/dist/daemon/api/server.js +3 -1
  264. package/dist/daemon/companion-retire.d.ts +2 -0
  265. package/dist/daemon/companion-retire.js +33 -0
  266. package/dist/daemon/cron-run.js +29 -17
  267. package/dist/daemon/crtrd.js +1 -1
  268. package/dist/daemon/fleet.d.ts +22 -10
  269. package/dist/daemon/fleet.js +43 -24
  270. package/dist/daemon/human/finish.d.ts +12 -6
  271. package/dist/daemon/human/finish.js +134 -54
  272. package/dist/daemon/human/sweep.js +9 -3
  273. package/dist/daemon/profile-delete.d.ts +7 -0
  274. package/dist/daemon/profile-delete.js +306 -0
  275. package/dist/daemon/reconcilers/broker-supervision.js +14 -9
  276. package/dist/daemon/reconcilers/dormant-inbox.js +11 -6
  277. package/dist/daemon/reconcilers/live-obligation.d.ts +21 -1
  278. package/dist/daemon/reconcilers/live-obligation.js +30 -2
  279. package/dist/daemon/reconcilers/storage-maintenance.d.ts +5 -0
  280. package/dist/daemon/reconcilers/storage-maintenance.js +28 -1
  281. package/dist/daemon/review/deliver.js +3 -3
  282. package/dist/daemon/review/finish.d.ts +0 -2
  283. package/dist/daemon/review/finish.js +1 -29
  284. package/dist/daemon/review/sweep.js +12 -1
  285. package/dist/pi-extensions/__tests__/canvas-bash-valve.test.js +56 -1
  286. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +4 -4
  287. package/dist/pi-extensions/canvas-bash-valve.d.ts +9 -1
  288. package/dist/pi-extensions/canvas-bash-valve.js +31 -13
  289. package/dist/pi-extensions/canvas-inbox-watcher.js +1 -1
  290. package/dist/pi-extensions/canvas-review-boundary.d.ts +4 -0
  291. package/dist/pi-extensions/canvas-review-boundary.js +18 -9
  292. package/dist/pi-extensions/canvas-stophook.js +4 -4
  293. package/dist/shared/generated-context.js +2 -2
  294. package/dist/types.d.ts +61 -5
  295. package/dist/types.js +8 -2
  296. package/package.json +4 -4
  297. package/runtime.lock.json +5 -33
  298. package/scripts/install-runtime.mjs +19 -24
  299. package/dist/builtin-memory/init.md +0 -38
  300. package/dist/builtin-memory/plan.md +0 -19
  301. package/dist/builtin-memory/spec.md +0 -19
  302. package/dist/clients/attach/render/html-markdown.d.ts +0 -13
  303. package/dist/clients/attach/render/html-markdown.js +0 -206
  304. package/dist/commands/human/doc.js +0 -144
  305. package/dist/commands/human/inbox.d.ts +0 -2
  306. package/dist/core/human/__tests__/page-render.test.js +0 -49
  307. package/dist/core/human/markdown-html.d.ts +0 -8
  308. package/dist/core/human/markdown-html.js +0 -581
  309. package/dist/core/human/page-render.d.ts +0 -3
  310. package/dist/core/human/page-render.js +0 -131
  311. package/dist/pages/bundle.css +0 -1
  312. package/dist/pages/bundle.js +0 -1347
  313. package/dist/pages/comments.d.ts +0 -43
  314. package/dist/pages/comments.js +0 -125
  315. package/dist/pages/elements/cards.d.ts +0 -17
  316. package/dist/pages/elements/cards.js +0 -802
  317. package/dist/pages/elements/chart.js +0 -648
  318. package/dist/pages/elements/options.d.ts +0 -21
  319. package/dist/pages/elements/options.js +0 -629
  320. package/dist/pages/elements/pages.d.ts +0 -24
  321. package/dist/pages/elements/pages.js +0 -355
  322. package/dist/pages/elements/slot.d.ts +0 -21
  323. package/dist/pages/elements/slot.js +0 -105
  324. package/dist/pages/elements/table.d.ts +0 -19
  325. package/dist/pages/elements/table.js +0 -935
  326. package/dist/pages/elements/text.d.ts +0 -25
  327. package/dist/pages/elements/text.js +0 -968
  328. package/dist/pages/entry.d.ts +0 -8
  329. package/dist/pages/entry.js +0 -8
  330. package/dist/pages/host.d.ts +0 -158
  331. package/dist/pages/host.js +0 -182
  332. package/dist/pages/register.d.ts +0 -2
  333. package/dist/pages/register.js +0 -3
  334. package/dist/pages/slot-config.d.ts +0 -36
  335. package/dist/pages/slot-config.js +0 -50
  336. package/dist/pages/types.d.ts +0 -98
  337. package/dist/pages/types.js +0 -23
  338. /package/dist/{core/human/__tests__/page-render.test.d.ts → commands/__tests__/node-message.test.d.ts} +0 -0
  339. /package/dist/{pages/elements/chart.d.ts → core/__tests__/plugin-page-components.test.d.ts} +0 -0
@@ -24,12 +24,12 @@ import { Container, Loader, Spacer, Text } from '@earendil-works/pi-tui';
24
24
  import { AssistantMessageComponent, BashExecutionComponent, BranchSummaryMessageComponent, CompactionSummaryMessageComponent, CustomMessageComponent, parseSkillBlock, SkillInvocationMessageComponent, ToolExecutionComponent, UserMessageComponent, } from '@earendil-works/pi-coding-agent';
25
25
  import { attachMarkdownTheme } from '../config.js';
26
26
  import { ContextMessageComponent } from './context-message.js';
27
+ import { detectInlinePageResult, PageBlockComponent } from './page-block.js';
27
28
  import { CRTR_OUTPUT_CUSTOM_TYPE, CrtrOutputMessageComponent, createCrtrBashToolDefinition, } from './crtr-output.js';
28
- import { CRTR_CYCLE_DIVIDER_CUSTOM_TYPE } from '../../../core/runtime/session-cycles.js';
29
+ import { CRTR_CYCLE_DIVIDER_CUSTOM_TYPE, endedByAbort } from '../../../core/runtime/session-cycles.js';
29
30
  import { generatedContextExpandedBody, generatedContextPresentation, isRuntimeRestartContinuation, } from '../../../shared/generated-context.js';
30
31
  import { assistantVisibleText, isTrueUserMessage, } from '../../../shared/tool-groups.js';
31
32
  import { transformDiagramFences } from './diagram.js';
32
- import { transformHtmlMarkdown } from './html-markdown.js';
33
33
  import { styleAttachAgentMarkdown, styleAttachMarkdownSource, styleAttachMessageMarkdown, styleAttachSummaryMarkdown, } from './markdown-source.js';
34
34
  import { createCrtrEditToolDefinition } from './edit-diff.js';
35
35
  import { GroupActivityRecorder } from './group-activity.js';
@@ -267,6 +267,11 @@ export class ChatView {
267
267
  * a nested container while condensed history is being built, so the same
268
268
  * message-rendering path serves both regions. */
269
269
  appendTarget = this.historyContainer;
270
+ /** Inline page blocks placed in this transcript, by page id — one block per
271
+ * page, so a `--replace` revision refreshes the block already on screen. */
272
+ pageBlocks = new Map();
273
+ inlinePageBlocks;
274
+ pageOpenHint;
270
275
  /** Trailing cycles kept at full fidelity (config `live_cycles`). */
271
276
  liveCycles;
272
277
  /** Messages kept from a condensed cycle (config `condensed_history`). */
@@ -305,27 +310,13 @@ export class ChatView {
305
310
  recapPalette;
306
311
  /** Bold accent for the expanded crtr-context label. */
307
312
  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
313
  /** Viewer-local Markdown projection, handed to pi's message components so
312
314
  * THEY apply it at render time with the exact content width. The viewer keeps
313
315
  * 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. */
316
+ * transformer reads live viewer state on every call. Diagrams apply only to
317
+ * assistant-authored Markdown. */
317
318
  markdownTransformers = [
318
319
  (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
320
  (markdown, { messageType }) => (messageType === 'user' ? styleAttachMarkdownSource(markdown) : styleAttachAgentMarkdown(markdown)),
330
321
  ];
331
322
  /** The assistant message component currently being streamed (between
@@ -394,6 +385,8 @@ export class ChatView {
394
385
  this.condensedHistory = opts.condensedHistory ?? readCondensedHistory();
395
386
  this.onFooterEvent = opts.onFooterEvent;
396
387
  this.onActivityChange = opts.onActivityChange;
388
+ this.inlinePageBlocks = opts.inlinePageBlocks ?? false;
389
+ this.pageOpenHint = opts.pageOpenHint ?? (() => 'crtr human list');
397
390
  this.recapPalette = opts.palette ?? defaultAttachPalette;
398
391
  this.spinnerStyle = this.recapPalette.active;
399
392
  this.dimStyle = this.recapPalette.muted;
@@ -497,9 +490,7 @@ export class ChatView {
497
490
  const hasProse = assistantVisibleText(message).trim() !== '';
498
491
  const toolCalls = this.assistantToolCalls(message);
499
492
  const endedWithFailure = message.stopReason === 'aborted' || message.stopReason === 'error';
500
- const terminalErrorMessage = message.stopReason === 'aborted'
501
- ? 'Interrupted'
502
- : (message.errorMessage ?? 'Error');
493
+ const terminalErrorMessage = endedByAbort(message) ? 'Interrupted' : (message.errorMessage ?? 'Error');
503
494
  // A terminal assistant failure remains visible as its own boundary. Settle
504
495
  // any interrupted calls so the completed span before it can still recap.
505
496
  if (endedWithFailure) {
@@ -554,6 +545,7 @@ export class ChatView {
554
545
  this.historyContainer.markDirty(component);
555
546
  this.settleGroupTool(component, message, message.isError === true);
556
547
  renderedPendingTools.delete(message.toolCallId);
548
+ this.maybeAppendPageBlock(message);
557
549
  }
558
550
  }
559
551
  else {
@@ -674,8 +666,7 @@ export class ChatView {
674
666
  // `setToolsExpanded` / `toggleThinkingBlockVisibility`.
675
667
  // -------------------------------------------------------------------------
676
668
  /** 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. */
669
+ * generation. */
679
670
  toggleToolsExpanded() {
680
671
  let result;
681
672
  if (this.summarizeToolCalls && this.foldSettledTools) {
@@ -687,11 +678,6 @@ export class ChatView {
687
678
  this.toolOutputExpanded = !this.toolOutputExpanded;
688
679
  result = { kind: 'output', expanded: this.toolOutputExpanded };
689
680
  }
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
681
  this.applyToolDisplay();
696
682
  return result;
697
683
  }
@@ -850,15 +836,21 @@ export class ChatView {
850
836
  if (this.streamingComponent) {
851
837
  const stopReason = event.message.stopReason;
852
838
  let errorMessage = event.message.errorMessage;
853
- // An aborted turn may be a user interrupt or a lifecycle interruption.
854
- // The provider tends to attach its own generic text ("Request was
855
- // aborted."), so override it outright with one plain, human label.
856
- if (stopReason === 'aborted') {
839
+ // An aborted turn may be a user interrupt or a lifecycle interruption
840
+ // (a refresh-yield aborts its own turn). Providers spell it several
841
+ // ways — including `stopReason: 'error'` carrying a bare AbortError,
842
+ // which would otherwise render as a red `Error: This operation was
843
+ // aborted` for a perfectly healthy yield — so override it outright
844
+ // with one plain, human label.
845
+ if (endedByAbort(event.message)) {
857
846
  errorMessage = 'Interrupted';
858
847
  // Surface the abort on the assistant bubble itself, mirroring pi
859
848
  // (interactive-mode.js:2280 sets streamingMessage.errorMessage before
860
- // updateContent) so the rendered message shows the annotation.
849
+ // updateContent) so the rendered message shows the annotation. The
850
+ // stopReason moves with it: pi's component reserves its red
851
+ // `Error: …` line for stopReason 'error', and an abort is not one.
861
852
  event.message.errorMessage = errorMessage;
853
+ event.message.stopReason = 'aborted';
862
854
  }
863
855
  this.streamingComponent.updateContent(event.message);
864
856
  if (stopReason === 'aborted' || stopReason === 'error') {
@@ -926,6 +918,7 @@ export class ChatView {
926
918
  this.settleGroupTool(component, result, event.isError);
927
919
  this.applyToolDisplay();
928
920
  this.historyContainer.markDirty(component);
921
+ this.maybeAppendPageBlock(result);
929
922
  }
930
923
  break;
931
924
  }
@@ -1162,10 +1155,16 @@ export class ChatView {
1162
1155
  // -------------------------------------------------------------------------
1163
1156
  // Helpers
1164
1157
  // -------------------------------------------------------------------------
1165
- /** Render an assistant reply plus its initially-hidden boundary separator. */
1158
+ /** Render an assistant reply plus its initially-hidden boundary separator.
1159
+ * A turn that ended on its own abort is rendered as the interrupt it is,
1160
+ * matching the live seam above, so re-reading the transcript later never
1161
+ * turns a healthy interrupt into a red `Error:` line. */
1166
1162
  appendAssistantMessage(message, groupMember = false) {
1167
1163
  const component = new ViewerAssistantMessageComponent(undefined, this.hideThinking, attachMarkdownTheme(), this.hiddenThinkingLabel, this.markdownTransformers);
1168
- component.updateContent(message);
1164
+ const rendered = endedByAbort(message)
1165
+ ? { ...message, stopReason: 'aborted', errorMessage: 'Interrupted' }
1166
+ : message;
1167
+ component.updateContent(rendered);
1169
1168
  this.appendAssistantPart(component, groupMember);
1170
1169
  this.registerCopyBlock(component, 'assistant', assistantVisibleText(message));
1171
1170
  const separator = this.appendAssistantSeparator(groupMember);
@@ -1421,8 +1420,38 @@ export class ChatView {
1421
1420
  this.copyBlockRecords.length = 0;
1422
1421
  this.copyIndex.clear();
1423
1422
  this.transcriptFilePaths.length = 0;
1423
+ this.pageBlocks.clear();
1424
1424
  this.historyContainer.clear();
1425
1425
  }
1426
+ /** Place the transcript block for an inline page a `human send` just raised.
1427
+ * The block is a plain history child, NOT a tool-group member: collapsing the
1428
+ * tool group folds the command away and leaves the page itself on screen, the
1429
+ * way assistant prose stays. Condensed history is skipped deliberately — old
1430
+ * cycles keep only prose and cycle seams. */
1431
+ maybeAppendPageBlock(result) {
1432
+ if (!this.inlinePageBlocks || this.appendTarget !== this.historyContainer)
1433
+ return;
1434
+ const page = detectInlinePageResult(result);
1435
+ if (page === undefined || this.pageBlocks.has(page.pageId))
1436
+ return;
1437
+ const block = new PageBlockComponent(page.pageId, page.dir, this.pageOpenHint);
1438
+ this.pageBlocks.set(page.pageId, block);
1439
+ this.append(block);
1440
+ }
1441
+ /** Re-read every placed page from disk. Driven by the viewer's ticket-activity
1442
+ * watcher, so an answer, a cancellation, or a `--replace` revision lands on
1443
+ * the block already anchored in the transcript. */
1444
+ refreshPageBlocks() {
1445
+ let changed = false;
1446
+ for (const block of this.pageBlocks.values()) {
1447
+ if (!block.refresh())
1448
+ continue;
1449
+ changed = true;
1450
+ this.historyContainer.markDirty(block);
1451
+ }
1452
+ if (changed)
1453
+ this.tui.requestRender();
1454
+ }
1426
1455
  makeLoader(message) {
1427
1456
  // Loader updates itself on an interval. Its TUI callback has to evict the
1428
1457
  // status block's cached frame before requesting the next render.
@@ -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)
@@ -0,0 +1,34 @@
1
+ import type { Component } from '@earendil-works/pi-tui';
2
+ export interface InlinePageRef {
3
+ pageId: string;
4
+ dir: string;
5
+ }
6
+ /**
7
+ * The inline page a `human send` tool result announces, or undefined for every
8
+ * other result. Read from the structured CLI record the canvas-preview extension
9
+ * attaches (`details.crtrPreview`), which is present on both live tool events and
10
+ * rebuilt snapshot messages — never from the rendered bash text.
11
+ */
12
+ export declare function detectInlinePageResult(result: unknown): InlinePageRef | undefined;
13
+ export declare class PageBlockComponent implements Component {
14
+ readonly pageId: string;
15
+ private readonly dir;
16
+ private readonly openHint;
17
+ private expanded;
18
+ private generation;
19
+ private stamp;
20
+ private snapshot;
21
+ private cache;
22
+ constructor(pageId: string, dir: string, openHint: () => string);
23
+ /** Re-read the ticket when its request or its result changed on disk. */
24
+ refresh(): boolean;
25
+ /** Ctrl+O global expansion (ChatView.isExpandable duck-type). */
26
+ setExpanded(expanded: boolean): void;
27
+ render(width: number): string[];
28
+ invalidate(): void;
29
+ private diskStamp;
30
+ private reload;
31
+ private compose;
32
+ private bodyLines;
33
+ private footerLine;
34
+ }
@@ -0,0 +1,226 @@
1
+ // render/page-block.ts — the transcript home for an inline page.
2
+ //
3
+ // `crtr human send --placement inline` puts a page in THIS conversation, and in
4
+ // crouter the conversation is the attach viewer's transcript. So the page gets a
5
+ // durable block anchored where it was sent, exactly as the assistant's prose is
6
+ // anchored: the inbox stays purely the queue (a page sent `--no-inbox` has no
7
+ // queue entry at all and would otherwise be invisible here), while this block is
8
+ // what the person actually reads.
9
+ //
10
+ // The block renders from the ticket directory on disk, never from the tool
11
+ // result text, so `--replace` revisions and the answer that settles a page both
12
+ // land on the already-placed block when `refresh()` is driven by the viewer's
13
+ // inbox-activity watcher.
14
+ import { statSync } from 'node:fs';
15
+ import { parsePage } from '../../../core/human/page.js';
16
+ import { pageManifestPath, responsePath } from '../../../core/human/convention.js';
17
+ import { pageTicketState, readTicketResult } from '../../../core/human/tickets.js';
18
+ import { renderMarkdownBlockAwareLines } from '../../../core/termrender/termrender.js';
19
+ import { describeSlot, tableMarkdown, terminalAnswerable } from '../../inbox/tui/slots.js';
20
+ import { BOLD, CYAN, DIM, GREEN, RESET, YELLOW, sanitize, singleLine, truncate, wrap } from '../../inbox/tui/ansi.js';
21
+ /** Rows of page body shown while the block is folded; Ctrl+O expands it. */
22
+ const FOLDED_BODY_LINES = 16;
23
+ /**
24
+ * The inline page a `human send` tool result announces, or undefined for every
25
+ * other result. Read from the structured CLI record the canvas-preview extension
26
+ * attaches (`details.crtrPreview`), which is present on both live tool events and
27
+ * rebuilt snapshot messages — never from the rendered bash text.
28
+ */
29
+ export function detectInlinePageResult(result) {
30
+ if (result === null || typeof result !== 'object')
31
+ return undefined;
32
+ const details = result['details'];
33
+ if (details === null || typeof details !== 'object')
34
+ return undefined;
35
+ const preview = details['crtrPreview'];
36
+ if (preview === null || typeof preview !== 'object')
37
+ return undefined;
38
+ const record = preview;
39
+ if (record['path'] !== 'human send')
40
+ return undefined;
41
+ const payload = record['result'];
42
+ if (payload === null || typeof payload !== 'object')
43
+ return undefined;
44
+ const fields = payload;
45
+ const pageId = fields['page_id'];
46
+ const dir = fields['dir'];
47
+ if (typeof pageId !== 'string' || typeof dir !== 'string')
48
+ return undefined;
49
+ if (fields['placement'] !== 'inline')
50
+ return undefined;
51
+ return { pageId, dir };
52
+ }
53
+ function mtimeOf(path) {
54
+ try {
55
+ return String(statSync(path).mtimeMs);
56
+ }
57
+ catch {
58
+ return '-';
59
+ }
60
+ }
61
+ export class PageBlockComponent {
62
+ pageId;
63
+ dir;
64
+ openHint;
65
+ expanded = false;
66
+ generation = 0;
67
+ stamp = '';
68
+ snapshot;
69
+ cache;
70
+ constructor(pageId, dir, openHint) {
71
+ this.pageId = pageId;
72
+ this.dir = dir;
73
+ this.openHint = openHint;
74
+ this.reload();
75
+ }
76
+ /** Re-read the ticket when its request or its result changed on disk. */
77
+ refresh() {
78
+ if (this.diskStamp() === this.stamp)
79
+ return false;
80
+ this.reload();
81
+ return true;
82
+ }
83
+ /** Ctrl+O global expansion (ChatView.isExpandable duck-type). */
84
+ setExpanded(expanded) {
85
+ if (expanded === this.expanded)
86
+ return;
87
+ this.expanded = expanded;
88
+ this.cache = undefined;
89
+ }
90
+ render(width) {
91
+ const w = width > 0 ? width : 80;
92
+ const cached = this.cache;
93
+ if (cached !== undefined && cached.width === w && cached.generation === this.generation && cached.expanded === this.expanded) {
94
+ return cached.lines;
95
+ }
96
+ const lines = this.compose(w);
97
+ this.cache = { width: w, generation: this.generation, expanded: this.expanded, lines };
98
+ return lines;
99
+ }
100
+ invalidate() {
101
+ this.cache = undefined;
102
+ }
103
+ diskStamp() {
104
+ return `${mtimeOf(pageManifestPath(this.dir))}|${mtimeOf(responsePath(this.dir))}`;
105
+ }
106
+ reload() {
107
+ this.stamp = this.diskStamp();
108
+ this.generation++;
109
+ this.cache = undefined;
110
+ try {
111
+ const manifest = parsePage(this.dir);
112
+ const state = pageTicketState(this.dir);
113
+ const digest = state === 'resolved' ? readTicketResult(this.dir) : null;
114
+ this.snapshot = {
115
+ manifest,
116
+ state,
117
+ ...(digest?.kind === 'page' ? { answerDigest: digest.summary.replace(/\s+/g, ' ').trim() } : {}),
118
+ };
119
+ }
120
+ catch {
121
+ // A removed or half-written ticket keeps whatever was last read; a block
122
+ // that never read one renders its unavailable line instead.
123
+ if (this.snapshot === undefined)
124
+ this.snapshot = undefined;
125
+ }
126
+ }
127
+ compose(width) {
128
+ const snapshot = this.snapshot;
129
+ if (snapshot === undefined)
130
+ return ['', ` ${DIM}[page] no longer available${RESET}`, ''];
131
+ const maxW = Math.min(Math.max(20, width - 4), 100);
132
+ const out = ['', ` ${BOLD}${CYAN}${truncate(sanitize(singleLine(snapshot.manifest.title)), maxW)}${RESET}`];
133
+ if (snapshot.manifest.subtitle) {
134
+ for (const line of wrap(sanitize(snapshot.manifest.subtitle), maxW))
135
+ out.push(` ${DIM}${line}${RESET}`);
136
+ }
137
+ const body = this.bodyLines(snapshot.manifest, maxW, Math.max(20, width - 4));
138
+ if (body.length > 0) {
139
+ out.push('');
140
+ const shown = this.expanded || body.length <= FOLDED_BODY_LINES ? body : body.slice(0, FOLDED_BODY_LINES);
141
+ for (const line of shown)
142
+ out.push(` ${line}`);
143
+ if (shown.length < body.length)
144
+ out.push(` ${DIM}… +${body.length - shown.length} lines — ctrl+o to expand${RESET}`);
145
+ }
146
+ const footer = this.footerLine(snapshot, maxW);
147
+ if (footer !== undefined)
148
+ out.push('', ` ${footer}`);
149
+ out.push('');
150
+ return out;
151
+ }
152
+ bodyLines(manifest, maxW, paneW) {
153
+ const md = (text) => renderMarkdownBlockAwareLines(text, maxW, paneW);
154
+ if (manifest.dialect === 'html')
155
+ return [`${DIM}HTML page — opens in a browser surface${RESET}`];
156
+ // Display components (Card, Alert, the rest of the shadcn set) are page markup,
157
+ // not manifest slots, so a page built only from them keeps nothing here a
158
+ // terminal could draw. Say so rather than paint a title over an empty body.
159
+ if (manifest.slots.length === 0)
160
+ return [`${DIM}display-only page — its content renders in Northlight${RESET}`];
161
+ const lines = [];
162
+ let priorStep = -1;
163
+ for (const slot of manifest.slots) {
164
+ if (manifest.steps > 1 && slot.step !== priorStep) {
165
+ if (lines.length > 0)
166
+ lines.push('');
167
+ lines.push(`${BOLD}Step ${slot.step + 1}/${manifest.steps}${RESET}`);
168
+ priorStep = slot.step;
169
+ }
170
+ if (lines.length > 0)
171
+ lines.push('');
172
+ lines.push(`${BOLD}${truncate(sanitize(singleLine(describeSlot(slot))), maxW)}${RESET}`);
173
+ const body = slot.config.body;
174
+ if (typeof body === 'string' && body !== '')
175
+ lines.push(...md(body));
176
+ if (!terminalAnswerable(slot)) {
177
+ lines.push(`${DIM}${sanitize(slot.kind)} — open in Northlight to answer this${RESET}`);
178
+ continue;
179
+ }
180
+ if (slot.kind === 'text') {
181
+ lines.push(...md(slot.config.initialText));
182
+ }
183
+ else if (slot.kind === 'table') {
184
+ lines.push(...md(tableMarkdown(slot.config)));
185
+ }
186
+ else if (slot.kind === 'cards') {
187
+ for (const card of slot.config.cards ?? []) {
188
+ lines.push(` • ${truncate(sanitize(singleLine(card.subtitle === undefined ? card.title : `${card.title} — ${card.subtitle}`)), Math.max(10, maxW - 4))}`);
189
+ if (card.body !== undefined)
190
+ for (const line of wrap(sanitize(card.body), Math.max(10, maxW - 6)))
191
+ lines.push(` ${DIM}${line}${RESET}`);
192
+ }
193
+ }
194
+ else if (slot.kind === 'options') {
195
+ for (const option of slot.config.options) {
196
+ lines.push(` • ${truncate(sanitize(singleLine(option.label)), Math.max(10, maxW - 4))}`);
197
+ if (option.description !== undefined)
198
+ for (const line of wrap(sanitize(option.description), Math.max(10, maxW - 6)))
199
+ lines.push(` ${DIM}${line}${RESET}`);
200
+ }
201
+ }
202
+ else if (slot.kind === 'chart') {
203
+ const config = slot.config;
204
+ const title = config.title ?? `${config.x ?? ''} vs ${(config.y ?? []).map((y) => y.label ?? y.field).join(', ')}`;
205
+ lines.push(`${DIM}chart — ${sanitize(config.kind ?? 'chart')}: ${sanitize(title)}${RESET}`);
206
+ }
207
+ }
208
+ return lines;
209
+ }
210
+ footerLine(snapshot, maxW) {
211
+ switch (snapshot.state) {
212
+ case 'pending':
213
+ return snapshot.manifest.delivery.reply
214
+ ? `${YELLOW}▸ awaiting your answer${RESET} ${DIM}— ${sanitize(this.openHint())}${RESET}`
215
+ : `${DIM}◦ queued in your inbox — ${sanitize(this.openHint())}${RESET}`;
216
+ case 'resolved':
217
+ return snapshot.answerDigest === undefined
218
+ ? `${GREEN}✓ answered${RESET}`
219
+ : `${GREEN}✓ answered${RESET} ${DIM}— ${truncate(sanitize(snapshot.answerDigest), Math.max(10, maxW - 12))}${RESET}`;
220
+ case 'canceled':
221
+ return `${DIM}⊘ canceled${RESET}`;
222
+ case 'passive':
223
+ return undefined;
224
+ }
225
+ }
226
+ }
@@ -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);
@@ -1,5 +1,7 @@
1
1
  import { type Component, type TUI } from '@earendil-works/pi-tui';
2
2
  import type { AttachPalette } from '../config.js';
3
+ /** The fixed line the plain whip sends — not drawn from `whip_messages`. */
4
+ export declare const GO_FASTER_WHIP_MESSAGE = "Go faster";
3
5
  /** A random line, never the one the previous whip used. */
4
6
  export declare function randomWhipMessage(messages: readonly string[]): string;
5
7
  /** A 20-frame, 12-row header animation that gives the focused agent a playful nudge. */
@@ -306,6 +306,8 @@ const WHIP_FRAMES = [
306
306
  ""
307
307
  ]
308
308
  ];
309
+ /** The fixed line the plain whip sends — not drawn from `whip_messages`. */
310
+ export const GO_FASTER_WHIP_MESSAGE = 'Go faster';
309
311
  let lastMessage;
310
312
  /** A random line, never the one the previous whip used. */
311
313
  export function randomWhipMessage(messages) {