@linxiraos/pi-tui 1.2.0 → 1.3.0

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 (276) hide show
  1. package/CHANGELOG.md +188 -120
  2. package/README.md +36 -0
  3. package/THIRD-PARTY-NOTICES.txt +55 -5
  4. package/dist/types/app-keybindings.d.ts +18 -8
  5. package/dist/types/apps/git/diff-pane.d.ts +5 -0
  6. package/dist/types/apps/git/git-tui.d.ts +16 -2
  7. package/dist/types/apps/git/state.d.ts +11 -0
  8. package/dist/types/autocomplete.d.ts +2 -0
  9. package/dist/types/charts/chart-svg.d.ts +7 -13
  10. package/dist/types/chat/advisor-message.d.ts +4 -1
  11. package/dist/types/chat/assistant-message.d.ts +9 -4
  12. package/dist/types/chat/figure-markdown.d.ts +7 -2
  13. package/dist/types/chat/messages.d.ts +3 -1
  14. package/dist/types/chat/read-tool-group.d.ts +2 -2
  15. package/dist/types/chat/table-chart.d.ts +11 -15
  16. package/dist/types/chat/tool-execution.d.ts +32 -20
  17. package/dist/types/chat/user-message.d.ts +3 -2
  18. package/dist/types/chrome/countdown-timer.d.ts +3 -2
  19. package/dist/types/chrome/diff.d.ts +2 -3
  20. package/dist/types/chrome/transcript-container.d.ts +2 -0
  21. package/dist/types/components/image.d.ts +2 -0
  22. package/dist/types/components/markdown.d.ts +49 -3
  23. package/dist/types/components/text.d.ts +2 -0
  24. package/dist/types/debug-server.d.ts +2 -0
  25. package/dist/types/keybinding-matchers.d.ts +6 -0
  26. package/dist/types/multiplexers/tmux.d.ts +1 -0
  27. package/dist/types/multiplexers/types.d.ts +6 -0
  28. package/dist/types/native/backend.d.ts +5 -4
  29. package/dist/types/native/blobs.d.ts +24 -12
  30. package/dist/types/overlays/agent-hub-projection.d.ts +10 -5
  31. package/dist/types/overlays/annotation-overlay.d.ts +19 -0
  32. package/dist/types/overlays/approval-dialog.d.ts +79 -0
  33. package/dist/types/overlays/ask-dialog.d.ts +11 -6
  34. package/dist/types/overlays/btw-history-panel.d.ts +2 -6
  35. package/dist/types/overlays/hook-editor.d.ts +7 -2
  36. package/dist/types/overlays/hook-input.d.ts +12 -4
  37. package/dist/types/overlays/job-tree.d.ts +50 -0
  38. package/dist/types/overlays/jobs-panel.d.ts +25 -11
  39. package/dist/types/overlays/login-dialog.d.ts +8 -3
  40. package/dist/types/overlays/mcp-add-wizard.d.ts +9 -1
  41. package/dist/types/overlays/model-browser.d.ts +6 -1
  42. package/dist/types/overlays/model-picker.d.ts +2 -0
  43. package/dist/types/overlays/pause-screen.d.ts +5 -0
  44. package/dist/types/overlays/plan-review-overlay.d.ts +4 -0
  45. package/dist/types/overlays/report-panel.d.ts +5 -1
  46. package/dist/types/overlays/reset-usage-selector.d.ts +7 -5
  47. package/dist/types/overlays/session-account-selector.d.ts +4 -0
  48. package/dist/types/overlays/session-info-overlay.d.ts +7 -3
  49. package/dist/types/overlays/session-selector.d.ts +3 -1
  50. package/dist/types/overlays/usage-dashboard.d.ts +6 -6
  51. package/dist/types/prompt/composer.d.ts +22 -26
  52. package/dist/types/prompt/custom-editor.d.ts +20 -10
  53. package/dist/types/prompt/image-references.d.ts +22 -0
  54. package/dist/types/prompt/internal-url-autocomplete.d.ts +1 -0
  55. package/dist/types/prompt/macos-spelling.d.ts +2 -1
  56. package/dist/types/prompt/prose-gate.d.ts +3 -0
  57. package/dist/types/prompt/queued-messages.d.ts +1 -1
  58. package/dist/types/prompt/typing-word.d.ts +25 -0
  59. package/dist/types/prompt/welcome.d.ts +35 -43
  60. package/dist/types/render/hanging-wrap.d.ts +7 -0
  61. package/dist/types/render/hyperlink.d.ts +2 -0
  62. package/dist/types/render/render-utils.d.ts +9 -1
  63. package/dist/types/render/utils.d.ts +1 -1
  64. package/dist/types/setup/scenes/splash.d.ts +6 -5
  65. package/dist/types/status-line/context-usage.d.ts +4 -1
  66. package/dist/types/status-line/host.d.ts +6 -4
  67. package/dist/types/status-line/startup.d.ts +6 -6
  68. package/dist/types/status-line/types.d.ts +3 -0
  69. package/dist/types/stdin-buffer.d.ts +7 -0
  70. package/dist/types/terminal-capabilities.d.ts +16 -1
  71. package/dist/types/terminal-multiplexer.d.ts +7 -0
  72. package/dist/types/terminal.d.ts +8 -4
  73. package/dist/types/theme/symbols.d.ts +1 -1
  74. package/dist/types/theme/theme.d.ts +11 -0
  75. package/dist/types/thinking.d.ts +5 -0
  76. package/dist/types/tmux.d.ts +39 -4
  77. package/dist/types/tools/ast-edit.d.ts +2 -2
  78. package/dist/types/tools/ast-grep.d.ts +2 -2
  79. package/dist/types/tools/bash.d.ts +6 -1
  80. package/dist/types/tools/edit.d.ts +2 -0
  81. package/dist/types/tools/eval.d.ts +53 -4
  82. package/dist/types/tools/find.d.ts +14 -4
  83. package/dist/types/tools/glob.d.ts +3 -3
  84. package/dist/types/tools/grep.d.ts +2 -2
  85. package/dist/types/tools/grouped-file-output.d.ts +9 -4
  86. package/dist/types/tools/native-view.d.ts +1 -1
  87. package/dist/types/tools/read.d.ts +32 -10
  88. package/dist/types/tools/renderer.d.ts +2 -0
  89. package/dist/types/tools/task.d.ts +31 -0
  90. package/dist/types/tools/write.d.ts +2 -0
  91. package/dist/types/tui.d.ts +20 -3
  92. package/package.json +10 -10
  93. package/src/app-keybindings.d.ts +18 -8
  94. package/src/app-keybindings.ts +20 -8
  95. package/src/apps/autoresearch-dashboard.ts +444 -27
  96. package/src/apps/debug/raw-sse.ts +2 -2
  97. package/src/apps/git/diff-pane.ts +39 -41
  98. package/src/apps/git/git-tui.ts +80 -14
  99. package/src/apps/git/sidebar.ts +10 -1
  100. package/src/apps/git/state.ts +12 -0
  101. package/src/autocomplete.d.ts +2 -0
  102. package/src/autocomplete.ts +21 -19
  103. package/src/bracketed-paste.ts +9 -2
  104. package/src/charts/chart-svg.d.ts +7 -13
  105. package/src/charts/chart-svg.ts +19 -22
  106. package/src/chat/advisor-message.d.ts +4 -1
  107. package/src/chat/advisor-message.ts +77 -24
  108. package/src/chat/assistant-message.d.ts +9 -4
  109. package/src/chat/assistant-message.ts +127 -31
  110. package/src/chat/bash-execution.ts +7 -2
  111. package/src/chat/figure-markdown.d.ts +7 -2
  112. package/src/chat/figure-markdown.ts +10 -3
  113. package/src/chat/image-loading.ts +9 -6
  114. package/src/chat/messages.d.ts +3 -1
  115. package/src/chat/messages.ts +4 -1
  116. package/src/chat/read-tool-group.d.ts +2 -2
  117. package/src/chat/read-tool-group.ts +97 -42
  118. package/src/chat/svg-figure.ts +3 -1
  119. package/src/chat/svg-source.ts +6 -3
  120. package/src/chat/table-chart.d.ts +11 -15
  121. package/src/chat/table-chart.ts +24 -57
  122. package/src/chat/tool-execution.d.ts +32 -20
  123. package/src/chat/tool-execution.ts +103 -36
  124. package/src/chat/user-message.d.ts +3 -2
  125. package/src/chat/user-message.ts +54 -34
  126. package/src/chrome/countdown-timer.ts +3 -3
  127. package/src/chrome/diff.d.ts +2 -3
  128. package/src/chrome/diff.ts +4 -9
  129. package/src/chrome/transcript-container.d.ts +2 -0
  130. package/src/chrome/transcript-container.ts +212 -28
  131. package/src/components/editor.ts +12 -12
  132. package/src/components/image.d.ts +2 -0
  133. package/src/components/image.ts +54 -20
  134. package/src/components/markdown.d.ts +49 -3
  135. package/src/components/markdown.ts +461 -11
  136. package/src/components/settings-list.ts +2 -4
  137. package/src/components/text.d.ts +2 -0
  138. package/src/components/text.ts +11 -1
  139. package/src/debug-server.d.ts +2 -0
  140. package/src/debug-server.ts +51 -69
  141. package/src/hotkeys-markdown.ts +2 -0
  142. package/src/keybinding-matchers.d.ts +6 -0
  143. package/src/keybinding-matchers.ts +19 -0
  144. package/src/multiplexers/tmux.d.ts +1 -0
  145. package/src/multiplexers/tmux.ts +3 -0
  146. package/src/multiplexers/types.d.ts +6 -0
  147. package/src/multiplexers/types.ts +6 -0
  148. package/src/native/backend.d.ts +5 -4
  149. package/src/native/backend.ts +13 -5
  150. package/src/native/blobs.d.ts +24 -12
  151. package/src/native/blobs.ts +56 -7
  152. package/src/native/reconcile.ts +43 -21
  153. package/src/overlays/advisor-config.ts +259 -8
  154. package/src/overlays/agent-hub-projection.d.ts +10 -5
  155. package/src/overlays/agent-hub-projection.ts +63 -12
  156. package/src/overlays/agent-hub.ts +2 -2
  157. package/src/overlays/annotation-overlay.ts +422 -168
  158. package/src/overlays/approval-dialog.ts +389 -0
  159. package/src/overlays/ask-dialog.ts +244 -105
  160. package/src/overlays/btw-history-panel.ts +208 -147
  161. package/src/overlays/extensions/extension-list.ts +4 -6
  162. package/src/overlays/hook-editor.ts +90 -32
  163. package/src/overlays/hook-input.ts +105 -19
  164. package/src/overlays/job-tree.ts +177 -0
  165. package/src/overlays/jobs-panel.ts +222 -142
  166. package/src/overlays/login-dialog.ts +201 -91
  167. package/src/overlays/mcp-add-wizard.ts +562 -279
  168. package/src/overlays/model-browser.ts +32 -7
  169. package/src/overlays/model-hub.ts +4 -10
  170. package/src/overlays/model-picker.ts +58 -21
  171. package/src/overlays/pause-screen.ts +81 -23
  172. package/src/overlays/plan-review-overlay.ts +16 -0
  173. package/src/overlays/plugin-selector.ts +17 -1
  174. package/src/overlays/plugin-settings.ts +8 -2
  175. package/src/overlays/queue-mode-selector.ts +10 -0
  176. package/src/overlays/report-panel.ts +6 -2
  177. package/src/overlays/reset-usage-selector.ts +130 -21
  178. package/src/overlays/session-account-selector.ts +36 -0
  179. package/src/overlays/session-info-overlay.ts +199 -76
  180. package/src/overlays/session-selector.ts +15 -10
  181. package/src/overlays/settings-selector.ts +3 -0
  182. package/src/overlays/show-images-selector.ts +1 -0
  183. package/src/overlays/snapcompact-shape-preview.ts +13 -6
  184. package/src/overlays/theme-selector.ts +6 -1
  185. package/src/overlays/thinking-selector.ts +8 -4
  186. package/src/overlays/tree-selector.ts +117 -2
  187. package/src/overlays/usage-dashboard.ts +498 -144
  188. package/src/prompt/composer-cache.ts +4 -6
  189. package/src/prompt/composer.ts +196 -96
  190. package/src/prompt/custom-editor.ts +168 -65
  191. package/src/prompt/image-references.d.ts +22 -0
  192. package/src/prompt/image-references.ts +79 -2
  193. package/src/prompt/internal-url-autocomplete.ts +1 -0
  194. package/src/prompt/macos-spelling.ts +28 -5
  195. package/src/prompt/prose-gate.ts +3 -0
  196. package/src/prompt/queued-messages.ts +9 -2
  197. package/src/prompt/tips.txt +1 -2
  198. package/src/prompt/typing-word.ts +59 -0
  199. package/src/prompt/welcome.ts +256 -423
  200. package/src/render/code-cell.ts +18 -5
  201. package/src/render/hanging-wrap.d.ts +7 -0
  202. package/src/render/hanging-wrap.ts +58 -0
  203. package/src/render/hyperlink.d.ts +2 -0
  204. package/src/render/hyperlink.ts +5 -0
  205. package/src/render/render-utils.d.ts +9 -1
  206. package/src/render/render-utils.ts +16 -2
  207. package/src/render/utils.d.ts +1 -1
  208. package/src/render/utils.ts +1 -1
  209. package/src/render/width-aware-text.ts +6 -5
  210. package/src/setup/scenes/splash.ts +137 -60
  211. package/src/status-line/component.ts +23 -10
  212. package/src/status-line/context-usage.ts +176 -73
  213. package/src/status-line/footer.ts +18 -35
  214. package/src/status-line/host.ts +6 -1
  215. package/src/status-line/segments.ts +16 -5
  216. package/src/status-line/startup.ts +6 -6
  217. package/src/status-line/types.ts +1 -0
  218. package/src/stdin-buffer.d.ts +7 -0
  219. package/src/stdin-buffer.ts +11 -1
  220. package/src/terminal-capabilities.d.ts +16 -1
  221. package/src/terminal-capabilities.ts +66 -28
  222. package/src/terminal-multiplexer.d.ts +7 -0
  223. package/src/terminal-multiplexer.ts +14 -0
  224. package/src/terminal.d.ts +8 -4
  225. package/src/terminal.ts +160 -46
  226. package/src/theme/symbols.d.ts +1 -1
  227. package/src/theme/symbols.ts +1 -0
  228. package/src/theme/theme-class.ts +1 -0
  229. package/src/theme/theme.d.ts +11 -0
  230. package/src/theme/theme.ts +46 -14
  231. package/src/thinking.d.ts +5 -0
  232. package/src/thinking.ts +9 -1
  233. package/src/tmux.d.ts +39 -4
  234. package/src/tmux.ts +110 -16
  235. package/src/tools/ask.ts +20 -9
  236. package/src/tools/ast-edit.d.ts +2 -2
  237. package/src/tools/ast-edit.ts +30 -7
  238. package/src/tools/ast-grep.d.ts +2 -2
  239. package/src/tools/ast-grep.ts +39 -35
  240. package/src/tools/bash.d.ts +6 -1
  241. package/src/tools/bash.ts +15 -9
  242. package/src/tools/debug.ts +2 -1
  243. package/src/tools/default-renderer.ts +3 -2
  244. package/src/tools/edit.d.ts +2 -0
  245. package/src/tools/edit.ts +100 -21
  246. package/src/tools/eval.d.ts +53 -4
  247. package/src/tools/eval.ts +173 -137
  248. package/src/tools/find.d.ts +14 -4
  249. package/src/tools/find.ts +71 -18
  250. package/src/tools/glob.d.ts +3 -3
  251. package/src/tools/glob.ts +51 -17
  252. package/src/tools/grep.d.ts +2 -2
  253. package/src/tools/grep.ts +99 -22
  254. package/src/tools/grouped-file-output.d.ts +9 -4
  255. package/src/tools/grouped-file-output.ts +21 -6
  256. package/src/tools/lsp.ts +30 -18
  257. package/src/tools/mcp.ts +2 -2
  258. package/src/tools/native-view.d.ts +1 -1
  259. package/src/tools/native-view.ts +2 -2
  260. package/src/tools/read.d.ts +32 -10
  261. package/src/tools/read.ts +89 -43
  262. package/src/tools/renderer.d.ts +2 -0
  263. package/src/tools/renderer.ts +2 -0
  264. package/src/tools/task.d.ts +31 -0
  265. package/src/tools/task.ts +40 -7
  266. package/src/tools/todo.ts +3 -2
  267. package/src/tools/wait.ts +16 -30
  268. package/src/tools/web-search.ts +2 -1
  269. package/src/tools/write.d.ts +2 -0
  270. package/src/tools/write.ts +36 -12
  271. package/src/tools/yield.ts +2 -2
  272. package/src/tui.d.ts +20 -3
  273. package/src/tui.ts +140 -36
  274. package/src/utils.ts +5 -3
  275. package/dist/types/tools/eval-format/code-mode-declarations.d.ts +0 -5
  276. package/src/tools/eval-format/code-mode-declarations.ts +0 -61
package/src/tools/eval.ts CHANGED
@@ -9,7 +9,7 @@ import type {
9
9
  ToolRenderer,
10
10
  } from "./renderer";
11
11
  import type { TspSpan } from "@linxiraos/pi-wire";
12
- import { compact, keyed, md, node, span, text } from "../native/describe";
12
+ import { code, compact, keyed, md, node, span, text } from "../native/describe";
13
13
  import type { NativeChild, NativeNode } from "../native/node";
14
14
  import { plainText } from "../native/spans";
15
15
  import { resultText, runBox, runFoot, type RunFootInput, runInput, runOutput, type RunState } from "./native-view";
@@ -35,12 +35,16 @@ import {
35
35
  renderJsonTreeLines,
36
36
  } from "./json-tree";
37
37
  import { formatStyledTruncationWarning, stripOutputNotice } from "./output-meta";
38
+ import { splitPathAndSel } from "./read";
38
39
  import {
39
40
  DEFAULT_TERMINAL_PREVIEW_LINES,
41
+ PREVIEW_LIMITS,
40
42
  cappedHeadLines,
41
43
  formatTitle,
44
+ previewLine,
42
45
  previewWindowRows,
43
46
  replaceTabs,
47
+ sanitizeDisplayLines,
44
48
  shortenPath,
45
49
  truncateToWidth,
46
50
  wrapBrackets,
@@ -60,26 +64,57 @@ export interface EvalStatusEvent {
60
64
  [key: string]: unknown;
61
65
  }
62
66
 
67
+ /** One column of a table view. */
68
+ export interface EvalTableColumn {
69
+ head: string;
70
+ /** Numeric values: renderers right-align the column. */
71
+ numeric?: boolean;
72
+ }
73
+
74
+ /**
75
+ * A pretty rendering of something a cell printed, shown to the user in place
76
+ * of its text; the model only ever sees the text.
77
+ * - `table`: a data frame or `console.table`; leading index columns are plain
78
+ * columns, and a truncated frame's elided rows/columns are `…` cells.
79
+ * - `json`: a structured value (dict/list, object/array) as a collapsible tree.
80
+ * - `markdown`: an object's Markdown representation.
81
+ */
82
+ export type EvalView =
83
+ | { kind: "table"; columns: EvalTableColumn[]; rows: string[][] }
84
+ | { kind: "json"; value: unknown }
85
+ | { kind: "markdown"; text: string };
86
+
87
+ /**
88
+ * A view and the exact printed text it stands for. Renderers locate `text` in
89
+ * the cell's `output` (entries in print order) and show the view there; an
90
+ * entry whose text is no longer in the output (truncated) is skipped.
91
+ */
92
+ export interface EvalViewEntry {
93
+ text: string;
94
+ view: EvalView;
95
+ }
96
+
63
97
  /** Per-cell execution result for transcript rendering. */
64
98
  export interface EvalCellResult {
65
99
  index: number;
66
100
  title?: string;
67
101
  code: string;
68
102
  language?: EvalLanguage;
103
+ /** What the model saw: the cell's printed text plus image notes. */
69
104
  output: string;
105
+ /** Pretty views of spans of `output`, in print order. */
106
+ views?: EvalViewEntry[];
70
107
  status: "pending" | "running" | "complete" | "error";
71
108
  durationMs?: number;
72
109
  exitCode?: number;
73
110
  statusEvents?: EvalStatusEvent[];
74
111
  /** Discrete events dropped from the front of `statusEvents` (see {@link recordStatusEvent}). */
75
112
  statusEventsElided?: number;
76
- hasMarkdown?: boolean;
77
113
  }
78
114
 
79
115
  /** Tool result detail object surfaced to the UI/transcript. */
80
116
  export interface EvalToolDetails {
81
117
  cells?: EvalCellResult[];
82
- jsonOutputs?: unknown[];
83
118
  images?: ImageContent[];
84
119
  statusEvents?: EvalStatusEvent[];
85
120
  /** Discrete events dropped from the front of `statusEvents` (see {@link recordStatusEvent}). */
@@ -92,9 +127,9 @@ export interface EvalToolDetails {
92
127
  languages?: EvalLanguage[];
93
128
  /** Optional human-readable notice (e.g. fallback explanation). */
94
129
  notice?: string;
95
- /** Present when the cell was auto-backgrounded as an async job. */
130
+ /** Present when the cell was auto-backgrounded as an async job; `cancelled`: killed before it finished. */
96
131
  async?: {
97
- state: "running" | "completed" | "failed";
132
+ state: "running" | "completed" | "failed" | "cancelled";
98
133
  jobId: string;
99
134
  type: "eval";
100
135
  };
@@ -583,7 +618,8 @@ function formatCellOutputLines(
583
618
  theme: Theme,
584
619
  width: number,
585
620
  ): { lines: readonly string[]; hiddenCount: number } {
586
- if (!cell.output) {
621
+ const output = cell.output;
622
+ if (!output) {
587
623
  return { lines: [], hiddenCount: 0 };
588
624
  }
589
625
 
@@ -595,32 +631,56 @@ function formatCellOutputLines(
595
631
  // exact hidden-count row, so the pane marker stays off here.
596
632
  const innerWidth = outputBlockContentWidth(width);
597
633
 
598
- if (cell.hasMarkdown && cell.status !== "error") {
599
- const md = new Markdown(cell.output, 0, 0, getMarkdownTheme());
600
- const allLines = md.render(innerWidth);
634
+ const isError = cell.status === "error";
635
+ const styleText = (line: string): string => theme.fg(isError ? "error" : "toolOutput", replaceTabs(line));
636
+ // A table's printed text is already a terminal table, aligned to the line it starts on: keep it as text.
637
+ const parts = splitEvalOutput(
638
+ output,
639
+ cell.views?.filter(entry => entry.view.kind !== "table"),
640
+ );
641
+ if (parts.some(part => part.kind === "view")) {
642
+ // Printed text keeps its place; json and markdown views replace theirs with a tree / rendered Markdown.
643
+ const treeDepth = expanded ? JSON_TREE_MAX_DEPTH_EXPANDED : JSON_TREE_MAX_DEPTH_COLLAPSED;
644
+ const treeLineCap = expanded ? JSON_TREE_MAX_LINES_EXPANDED : JSON_TREE_MAX_LINES_COLLAPSED;
645
+ const treeScalarLen = expanded ? JSON_TREE_SCALAR_LEN_EXPANDED : JSON_TREE_SCALAR_LEN_COLLAPSED;
646
+ const allLines: string[] = [];
647
+ for (const part of parts) {
648
+ if (part.kind === "text") {
649
+ for (const line of part.text.split("\n")) allLines.push(styleText(line));
650
+ continue;
651
+ }
652
+ const view = part.view;
653
+ if (view.kind === "json") {
654
+ const tree = renderJsonTreeLines(view.value, theme, treeDepth, treeLineCap, treeScalarLen);
655
+ allLines.push(...tree.lines);
656
+ if (tree.truncated) allLines.push(theme.fg("dim", "…"));
657
+ } else if (view.kind === "markdown") {
658
+ allLines.push(...new Markdown(view.text, 0, 0, getMarkdownTheme()).render(innerWidth));
659
+ }
660
+ }
601
661
  const formatted = formatOutputPaneLines(
602
662
  {
603
663
  lines: allLines,
604
664
  expanded,
605
665
  collapsedMaxLines: previewLines,
606
666
  edge: "tail",
667
+ visual: true,
668
+ width: innerWidth,
607
669
  showHiddenMarker: false,
608
670
  },
609
671
  theme,
610
672
  );
611
673
  return { lines: formatted.lines, hiddenCount: formatted.hiddenCount };
612
674
  }
613
-
614
- const isError = cell.status === "error";
615
675
  const formatted = formatOutputPaneLines(
616
676
  {
617
- lines: cell.output.split("\n"),
677
+ lines: output.split("\n"),
618
678
  expanded,
619
679
  collapsedMaxLines: previewLines,
620
680
  edge: "tail",
621
681
  visual: true,
622
682
  width: innerWidth,
623
- styleLine: line => theme.fg(isError ? "error" : "toolOutput", replaceTabs(line)),
683
+ styleLine: styleText,
624
684
  showHiddenMarker: false,
625
685
  },
626
686
  theme,
@@ -628,17 +688,42 @@ function formatCellOutputLines(
628
688
  return { lines: formatted.lines, hiddenCount: formatted.hiddenCount };
629
689
  }
630
690
 
631
- /** Plain one-line summary of a status event: op plus its scalar fields. */
632
- function describeStatusEvent(event: EvalStatusEvent): NativeNode {
691
+ /** Display cells a status field value keeps on its one-line summary. */
692
+ const STATUS_VALUE_WIDTH = 200;
693
+
694
+ /**
695
+ * Plain one-line summary of a status event: op plus its scalar fields, each
696
+ * collapsed to one line. A `preview` (file or command text a helper returned)
697
+ * never joins the line: an expanded cell shows it under the line as a code
698
+ * block holding its first {@link PREVIEW_LIMITS.OUTPUT_EXPANDED} lines, folded
699
+ * to {@link PREVIEW_LIMITS.COLLAPSED_LINES}.
700
+ */
701
+ function describeStatusEvent(event: EvalStatusEvent, expanded: boolean): NativeNode {
633
702
  const spans: TspSpan[] = [span(event.op, "accent")];
634
703
  for (const key in event) {
635
- if (key === "op" || key === "resolvedThinkingLevel") continue;
704
+ if (key === "op" || key === "preview" || key === "resolvedThinkingLevel") continue;
636
705
  const value = event[key];
637
706
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
638
- spans.push(span(` ${key}=`, "muted"), span(sanitizeText(String(value))));
707
+ spans.push(span(` ${key}=`, "muted"), span(previewLine(sanitizeText(String(value)), STATUS_VALUE_WIDTH)));
639
708
  }
640
709
  }
641
- return text(spans, { truncate: "end", role: "zeta.tool.eval.status" });
710
+ const line = text(spans, { truncate: "end", role: "zeta.tool.eval.status" });
711
+ const preview = expanded ? eventString(event.preview)?.trimEnd() : undefined;
712
+ if (!preview) return line;
713
+ const head = cappedHeadLines(sanitizeDisplayLines(preview), PREVIEW_LIMITS.OUTPUT_EXPANDED).lines;
714
+ const path = eventString(event.path);
715
+ return node("col", { role: "zeta.tool.eval.status.event", gap: "none" }, [
716
+ keyed(line, "line"),
717
+ keyed(
718
+ code(head.join("\n"), {
719
+ role: "zeta.tool.eval.status.preview",
720
+ path: path ? splitPathAndSel(path).path : undefined,
721
+ wrap: false,
722
+ preview: { lines: PREVIEW_LIMITS.COLLAPSED_LINES },
723
+ }),
724
+ "preview",
725
+ ),
726
+ ]);
642
727
  }
643
728
 
644
729
  /**
@@ -664,100 +749,79 @@ function evalToolHead(
664
749
  };
665
750
  }
666
751
 
667
- /** Box-drawing `console.table` output split out of plain text: `text` runs and parsed tables, in order. */
668
- type EvalOutputPart =
669
- | { readonly kind: "text"; readonly text: string }
670
- | { readonly kind: "table"; readonly head: readonly string[]; readonly rows: readonly string[][] };
671
-
672
- /** The cells of a `│ a │ b │` row, trimmed; undefined when the line is not a table row. */
673
- function tableRowCells(line: string): string[] | undefined {
674
- const plain = plainText(line).trim();
675
- if (plain.length < 2 || !plain.startsWith("│") || !plain.endsWith("│")) return undefined;
676
- return plain
677
- .slice(1, -1)
678
- .split("│")
679
- .map(cell => cell.trim());
680
- }
752
+ /** One ordered piece of a cell's output: printed text, or a view shown in place of the text it matched. */
753
+ export type EvalOutputPart = { kind: "text"; text: string } | { kind: "view"; view: EvalView };
681
754
 
682
755
  /**
683
- * Split `console.table` blocks (`┌…┐` header `├…┤` rows `└…┘`, as Node/Bun
684
- * print them) out of cell output so they render as a `table`; anything that
685
- * does not parse as one stays text.
756
+ * Split `output` around its views, in order: each view's `text` is searched
757
+ * from the end of the previous match, unmatched entries are skipped, and
758
+ * blank text between views is dropped (newlines bordering a view trimmed).
686
759
  */
687
- function splitConsoleTables(output: string): EvalOutputPart[] {
688
- const lines = output.split("\n");
760
+ export function splitEvalOutput(output: string, views: readonly EvalViewEntry[] | undefined): EvalOutputPart[] {
689
761
  const parts: EvalOutputPart[] = [];
690
- let textStart = 0;
691
- const flushText = (end: number): void => {
692
- const chunk = lines
693
- .slice(textStart, end)
694
- .join("\n")
695
- .replace(/^\n+|\s+$/g, "");
696
- if (chunk.length > 0) parts.push({ kind: "text", text: chunk });
762
+ const pushText = (chunk: string, trimStart: boolean, trimEnd: boolean): void => {
763
+ let text = chunk;
764
+ if (trimStart) text = text.replace(/^\n+/, "");
765
+ if (trimEnd) text = text.replace(/\n+$/, "");
766
+ if (text.trim().length > 0) parts.push({ kind: "text", text });
697
767
  };
698
- for (let i = 0; i < lines.length; i++) {
699
- if (!plainText(lines[i]!).trimStart().startsWith("┌")) continue;
700
- const head = tableRowCells(lines[i + 1] ?? "");
701
- if (
702
- !head ||
703
- !plainText(lines[i + 2] ?? "")
704
- .trimStart()
705
- .startsWith("├")
706
- )
707
- continue;
708
- const rows: string[][] = [];
709
- let end = i + 3;
710
- for (; end < lines.length; end++) {
711
- const cells = tableRowCells(lines[end]!);
712
- if (!cells) break;
713
- rows.push(cells);
714
- }
715
- if (
716
- !plainText(lines[end] ?? "")
717
- .trimStart()
718
- .startsWith("└")
719
- )
720
- continue;
721
- if (rows.some(row => row.length !== head.length)) continue;
722
- flushText(i);
723
- parts.push({ kind: "table", head, rows });
724
- i = end;
725
- textStart = end + 1;
768
+ let cursor = 0;
769
+ for (const entry of views ?? []) {
770
+ if (!entry.text) continue;
771
+ const at = output.indexOf(entry.text, cursor);
772
+ if (at < 0) continue;
773
+ pushText(output.slice(cursor, at), cursor > 0, true);
774
+ parts.push({ kind: "view", view: entry.view });
775
+ cursor = at + entry.text.length;
726
776
  }
727
- flushText(lines.length);
777
+ pushText(output.slice(cursor), cursor > 0, cursor > 0);
728
778
  return parts;
729
779
  }
730
780
 
781
+ /** A table view as a native `table`; numeric columns align to the end. */
782
+ function evalTableNode(view: Extract<EvalView, { kind: "table" }>, key: string): NativeNode {
783
+ return node(
784
+ "table",
785
+ {
786
+ cols: view.columns.map((column, c) => ({
787
+ id: `c${c}`,
788
+ head: column.head,
789
+ align: column.numeric ? "end" : undefined,
790
+ })),
791
+ rows: view.rows.map((cells, r) => ({
792
+ id: `r${r}`,
793
+ cells: Object.fromEntries(cells.map((cell, c) => [`c${c}`, cell])),
794
+ })),
795
+ role: "zeta.tool.eval.table",
796
+ },
797
+ undefined,
798
+ key,
799
+ );
800
+ }
801
+
731
802
  /**
732
- * One cell's outputs: markdown, else terminal text (following its tail,
733
- * clamped to the last `previewLines` while folded) with any `console.table`
734
- * blocks as tables.
803
+ * One cell's outputs in order: printed text (following its tail, clamped to
804
+ * the last `previewLines` while folded) with each view in place of its text.
735
805
  */
736
- function evalOutputNodes(output: string, markdown: boolean, previewLines: number): NativeNode[] {
806
+ function evalOutputNodes(
807
+ output: string,
808
+ views: readonly EvalViewEntry[] | undefined,
809
+ previewLines: number,
810
+ ): NativeNode[] {
737
811
  if (output.trim().length === 0) return [];
738
- if (markdown) return [keyed(md(output), "output")];
739
- return splitConsoleTables(output).map((part, i) =>
740
- part.kind === "text"
741
- ? runOutput(part.text, {
742
- role: "zeta.tool.eval.output",
743
- previewLines,
744
- key: i === 0 ? "output" : `output-${i}`,
745
- })
746
- : node(
747
- "table",
748
- {
749
- // Node's index column header is noise in a real table.
750
- cols: part.head.map((head, c) => ({ id: `c${c}`, head: head === "(index)" ? "" : head })),
751
- rows: part.rows.map((cells, r) => ({
752
- id: `r${r}`,
753
- cells: Object.fromEntries(cells.map((cell, c) => [`c${c}`, cell])),
754
- })),
755
- role: "zeta.tool.eval.table",
756
- },
757
- undefined,
758
- `table-${i}`,
759
- ),
760
- );
812
+ return splitEvalOutput(output, views).map((part, i) => {
813
+ if (part.kind === "text") {
814
+ return runOutput(part.text, {
815
+ role: "zeta.tool.eval.output",
816
+ previewLines,
817
+ key: i === 0 ? "output" : `output-${i}`,
818
+ });
819
+ }
820
+ const view = part.view;
821
+ if (view.kind === "table") return evalTableNode(view, `table-${i}`);
822
+ if (view.kind === "json") return keyed(describeJsonTree(view.value, { hiddenRootKeys: [] }), `json-${i}`);
823
+ return keyed(md(view.text), `markdown-${i}`);
824
+ });
761
825
  }
762
826
 
763
827
  /** Status lines a folded cell keeps: the newest ones. */
@@ -774,7 +838,9 @@ function evalStatusSection(log: StatusEventLog, expanded: boolean): NativeNode |
774
838
  if (events.length === 0 && elided === 0) return undefined;
775
839
  const shownFrom = expanded ? 0 : Math.max(0, events.length - RUN_STATUS_TAIL);
776
840
  const hidden = shownFrom + elided;
777
- const lines = events.slice(shownFrom).map((event, i) => keyed(describeStatusEvent(event), `s${hidden + i}`));
841
+ const lines = events
842
+ .slice(shownFrom)
843
+ .map((event, i) => keyed(describeStatusEvent(event, expanded), `s${hidden + i}`));
778
844
  return node(
779
845
  "col",
780
846
  { role: "zeta.run.status", gap: "none" },
@@ -922,18 +988,6 @@ export const evalToolRenderer = {
922
988
  // the styled `warningLine` below carries the same text in ⟨…⟩ form.
923
989
  const output = stripOutputNotice(rawOutput, details?.meta).trimEnd();
924
990
 
925
- const jsonOutputs = details?.jsonOutputs ?? [];
926
- const treeExpanded = options.renderContext?.expanded ?? options.expanded;
927
- const treeDepth = treeExpanded ? JSON_TREE_MAX_DEPTH_EXPANDED : JSON_TREE_MAX_DEPTH_COLLAPSED;
928
- const treeLineCap = treeExpanded ? JSON_TREE_MAX_LINES_EXPANDED : JSON_TREE_MAX_LINES_COLLAPSED;
929
- const treeScalarLen = treeExpanded ? JSON_TREE_SCALAR_LEN_EXPANDED : JSON_TREE_SCALAR_LEN_COLLAPSED;
930
- const labelOutputs = jsonOutputs.length > 1;
931
- const jsonLines = jsonOutputs.flatMap((value, index) => {
932
- const tree = renderJsonTreeLines(value, uiTheme, treeDepth, treeLineCap, treeScalarLen);
933
- const body = tree.truncated ? [...tree.lines, uiTheme.fg("dim", "…")] : tree.lines;
934
- return labelOutputs ? [uiTheme.fg("dim", `display[${index + 1}]`), ...body] : body;
935
- });
936
-
937
991
  const timeoutSeconds = options.renderContext?.timeout;
938
992
  const timeoutLine =
939
993
  typeof timeoutSeconds === "number"
@@ -1057,7 +1111,6 @@ export const evalToolRenderer = {
1057
1111
  lines.push("");
1058
1112
  }
1059
1113
  }
1060
- lines.push(...jsonLines);
1061
1114
  if (timeoutLine) {
1062
1115
  lines.push(timeoutLine);
1063
1116
  }
@@ -1080,9 +1133,6 @@ export const evalToolRenderer = {
1080
1133
  });
1081
1134
  }
1082
1135
 
1083
- const displayOutput = output;
1084
- const combinedOutput = [displayOutput, ...jsonLines].filter(Boolean).join("\n");
1085
-
1086
1136
  const statusEvents = details?.statusEvents ?? [];
1087
1137
  const statusLines = renderStatusEvents(
1088
1138
  statusEvents,
@@ -1091,12 +1141,12 @@ export const evalToolRenderer = {
1091
1141
  details?.statusEventsElided,
1092
1142
  );
1093
1143
 
1094
- if (!combinedOutput && statusLines.length === 0) {
1144
+ if (!output && statusLines.length === 0) {
1095
1145
  const lines = [timeoutLine, noticeLine, asyncLine, warningLine].filter(Boolean) as string[];
1096
1146
  return new Text(lines.join("\n"), 0, 0);
1097
1147
  }
1098
1148
 
1099
- if (!combinedOutput && statusLines.length > 0) {
1149
+ if (!output && statusLines.length > 0) {
1100
1150
  const lines = [
1101
1151
  uiTheme.fg("dim", "Status"),
1102
1152
  ...statusLines,
@@ -1109,7 +1159,7 @@ export const evalToolRenderer = {
1109
1159
  }
1110
1160
 
1111
1161
  if (options.renderContext?.expanded ?? options.expanded) {
1112
- const styledOutput = combinedOutput
1162
+ const styledOutput = output
1113
1163
  .split("\n")
1114
1164
  .map(line => uiTheme.fg("toolOutput", line))
1115
1165
  .join("\n");
@@ -1124,7 +1174,7 @@ export const evalToolRenderer = {
1124
1174
  return new Text(lines.join("\n"), 0, 0);
1125
1175
  }
1126
1176
 
1127
- const styledOutput = combinedOutput
1177
+ const styledOutput = output
1128
1178
  .split("\n")
1129
1179
  .map(line => uiTheme.fg("toolOutput", line))
1130
1180
  .join("\n");
@@ -1214,9 +1264,6 @@ export const evalToolRenderer = {
1214
1264
  const cancelled = options.cancelled === true;
1215
1265
  const expanded = options.renderContext?.expanded ?? options.expanded;
1216
1266
  const previewLines = options.renderContext?.previewLines ?? EVAL_DEFAULT_PREVIEW_LINES;
1217
- const jsonNodes = (details?.jsonOutputs ?? []).map((value, index) =>
1218
- keyed(describeJsonTree(value, { hiddenRootKeys: [] }), `display-${index}`),
1219
- );
1220
1267
  // The call's notice and truncation join the last run's foot facts (a background job is a head badge).
1221
1268
  const callFacts = { facts: compact([details?.notice]), meta: details?.meta };
1222
1269
  const cellResults = details?.cells;
@@ -1242,15 +1289,7 @@ export const evalToolRenderer = {
1242
1289
  language,
1243
1290
  code: formatEvalCodeForDisplay(cell.code, language),
1244
1291
  title: cell.title,
1245
- outputs: [
1246
- ...evalOutputNodes(
1247
- cell.output,
1248
- cell.hasMarkdown === true && cell.status !== "error",
1249
- previewLines,
1250
- ),
1251
-
1252
- ...(i === cellResults.length - 1 ? jsonNodes : []),
1253
- ],
1292
+ outputs: [...evalOutputNodes(cell.output, cell.views, previewLines)],
1254
1293
  status: evalStatusSection(cell, expanded),
1255
1294
  foot: state && {
1256
1295
  state,
@@ -1275,10 +1314,7 @@ export const evalToolRenderer = {
1275
1314
  const rawOutput = options.renderContext?.output ?? resultText(result).trimEnd();
1276
1315
  const state: RunState = cancelled ? "cancelled" : isPartial ? "running" : result.isError ? "failed" : "done";
1277
1316
  const last: Omit<EvalCellRun, "language" | "code" | "title"> = {
1278
- outputs: [
1279
- ...evalOutputNodes(stripOutputNotice(rawOutput, details?.meta).trimEnd(), false, previewLines),
1280
- ...jsonNodes,
1281
- ],
1317
+ outputs: evalOutputNodes(stripOutputNotice(rawOutput, details?.meta).trimEnd(), undefined, previewLines),
1282
1318
  status: evalStatusSection(details ?? {}, expanded),
1283
1319
  foot: { state, elapsedMs: options.elapsedMs, ...callFacts },
1284
1320
  };
@@ -1,7 +1,7 @@
1
1
  import type { Theme } from "../theme/theme";
2
2
  import type { Component } from "../tui";
3
3
  import type { OutputMeta } from "./output-meta";
4
- import type { NativeToolView, RenderResultOptions } from "./renderer";
4
+ import type { NativeToolView, RenderResultContextOptions, RenderResultOptions } from "./renderer";
5
5
  /** A verified line range with its yes-probability and a one-line preview. */
6
6
  export interface FindRange {
7
7
  start: number;
@@ -22,12 +22,21 @@ export interface FindHit {
22
22
  linesSeen: number;
23
23
  truncated: boolean;
24
24
  }
25
+ /**
26
+ * How much of a search the judge actually covered. `unavailable`: every judge
27
+ * request failed, so an empty result is not evidence of absence. `partial`:
28
+ * some requests failed. `complete`: none failed (or none were needed).
29
+ */
30
+ export type FindCoverage = "complete" | "partial" | "unavailable";
25
31
  /** Search accounting reported alongside the hits. */
26
32
  export interface FindStats {
27
33
  /** Eligible files under the root. */
28
34
  listed: number;
29
35
  requests: number;
30
- errors: number;
36
+ /** Judge requests that failed outright; decides {@link FindCoverage}. */
37
+ failed: number;
38
+ /** Questions a successful response left without a usable answer. */
39
+ unanswered: number;
31
40
  /** Entries judged by name. */
32
41
  judged: number;
33
42
  /** Files whose content was read and sent. */
@@ -51,6 +60,7 @@ export interface FindToolDetails {
51
60
  /** Verified-passage probability at or above which a file is a hit. */
52
61
  threshold: number;
53
62
  hits: FindHit[];
63
+ coverage: FindCoverage;
54
64
  stats: FindStats;
55
65
  elapsedMs: number;
56
66
  /** Session cwd; hit paths are relative to it. */
@@ -74,7 +84,7 @@ export declare const findToolRenderer: {
74
84
  }>;
75
85
  details?: FindToolDetails;
76
86
  isError?: boolean;
77
- }, options: RenderResultOptions, uiTheme: Theme, args?: FindRenderArgs): Component;
87
+ }, options: RenderResultContextOptions, uiTheme: Theme, args?: FindRenderArgs): Component;
78
88
  describeCall(args: FindRenderArgs): NativeToolView;
79
89
  /**
80
90
  * Inline (§7.3 find): `Find “query” 2 hits · 8 files`, then per hit its
@@ -88,7 +98,7 @@ export declare const findToolRenderer: {
88
98
  }>;
89
99
  details?: FindToolDetails;
90
100
  isError?: boolean;
91
- }, options: RenderResultOptions, args?: FindRenderArgs): NativeToolView;
101
+ }, options: RenderResultContextOptions, args?: FindRenderArgs): NativeToolView;
92
102
  mergeCallAndResult: true;
93
103
  animatedPendingPreview: true;
94
104
  animatedPartialResult: true;