@linxiraos/pi-tui 1.2.1 → 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 (274) hide show
  1. package/CHANGELOG.md +188 -125
  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 +21 -20
  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 +26 -6
  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/annotation-overlay.ts +422 -168
  157. package/src/overlays/approval-dialog.ts +389 -0
  158. package/src/overlays/ask-dialog.ts +244 -105
  159. package/src/overlays/btw-history-panel.ts +208 -147
  160. package/src/overlays/extensions/extension-list.ts +4 -6
  161. package/src/overlays/hook-editor.ts +90 -32
  162. package/src/overlays/hook-input.ts +105 -19
  163. package/src/overlays/job-tree.ts +177 -0
  164. package/src/overlays/jobs-panel.ts +222 -142
  165. package/src/overlays/login-dialog.ts +201 -91
  166. package/src/overlays/mcp-add-wizard.ts +562 -279
  167. package/src/overlays/model-browser.ts +32 -7
  168. package/src/overlays/model-hub.ts +4 -10
  169. package/src/overlays/model-picker.ts +58 -21
  170. package/src/overlays/pause-screen.ts +81 -23
  171. package/src/overlays/plan-review-overlay.ts +16 -0
  172. package/src/overlays/plugin-selector.ts +17 -1
  173. package/src/overlays/queue-mode-selector.ts +10 -0
  174. package/src/overlays/report-panel.ts +6 -2
  175. package/src/overlays/reset-usage-selector.ts +130 -21
  176. package/src/overlays/session-account-selector.ts +36 -0
  177. package/src/overlays/session-info-overlay.ts +199 -76
  178. package/src/overlays/session-selector.ts +15 -10
  179. package/src/overlays/settings-selector.ts +3 -0
  180. package/src/overlays/show-images-selector.ts +1 -0
  181. package/src/overlays/snapcompact-shape-preview.ts +13 -6
  182. package/src/overlays/theme-selector.ts +6 -1
  183. package/src/overlays/thinking-selector.ts +8 -4
  184. package/src/overlays/tree-selector.ts +117 -2
  185. package/src/overlays/usage-dashboard.ts +498 -144
  186. package/src/prompt/composer-cache.ts +1 -1
  187. package/src/prompt/composer.ts +194 -74
  188. package/src/prompt/custom-editor.ts +168 -65
  189. package/src/prompt/image-references.d.ts +22 -0
  190. package/src/prompt/image-references.ts +79 -2
  191. package/src/prompt/internal-url-autocomplete.ts +1 -0
  192. package/src/prompt/macos-spelling.ts +28 -5
  193. package/src/prompt/prose-gate.ts +3 -0
  194. package/src/prompt/queued-messages.ts +9 -2
  195. package/src/prompt/tips.txt +1 -2
  196. package/src/prompt/typing-word.ts +59 -0
  197. package/src/prompt/welcome.ts +97 -45
  198. package/src/render/code-cell.ts +18 -5
  199. package/src/render/hanging-wrap.d.ts +7 -0
  200. package/src/render/hanging-wrap.ts +58 -0
  201. package/src/render/hyperlink.d.ts +2 -0
  202. package/src/render/hyperlink.ts +5 -0
  203. package/src/render/render-utils.d.ts +9 -1
  204. package/src/render/render-utils.ts +16 -2
  205. package/src/render/utils.d.ts +1 -1
  206. package/src/render/utils.ts +1 -1
  207. package/src/render/width-aware-text.ts +6 -5
  208. package/src/setup/scenes/splash.ts +22 -8
  209. package/src/status-line/component.ts +23 -10
  210. package/src/status-line/context-usage.ts +176 -73
  211. package/src/status-line/footer.ts +18 -35
  212. package/src/status-line/host.ts +6 -1
  213. package/src/status-line/segments.ts +16 -5
  214. package/src/status-line/startup.ts +6 -6
  215. package/src/status-line/types.ts +1 -0
  216. package/src/stdin-buffer.d.ts +7 -0
  217. package/src/stdin-buffer.ts +11 -1
  218. package/src/terminal-capabilities.d.ts +16 -1
  219. package/src/terminal-capabilities.ts +66 -28
  220. package/src/terminal-multiplexer.d.ts +7 -0
  221. package/src/terminal-multiplexer.ts +14 -0
  222. package/src/terminal.d.ts +8 -4
  223. package/src/terminal.ts +160 -46
  224. package/src/theme/symbols.d.ts +1 -1
  225. package/src/theme/symbols.ts +1 -0
  226. package/src/theme/theme-class.ts +1 -0
  227. package/src/theme/theme.d.ts +11 -0
  228. package/src/theme/theme.ts +46 -14
  229. package/src/thinking.d.ts +5 -0
  230. package/src/thinking.ts +9 -1
  231. package/src/tmux.d.ts +39 -4
  232. package/src/tmux.ts +110 -16
  233. package/src/tools/ask.ts +20 -9
  234. package/src/tools/ast-edit.d.ts +2 -2
  235. package/src/tools/ast-edit.ts +30 -7
  236. package/src/tools/ast-grep.d.ts +2 -2
  237. package/src/tools/ast-grep.ts +39 -35
  238. package/src/tools/bash.d.ts +6 -1
  239. package/src/tools/bash.ts +15 -9
  240. package/src/tools/debug.ts +2 -1
  241. package/src/tools/default-renderer.ts +3 -2
  242. package/src/tools/edit.d.ts +2 -0
  243. package/src/tools/edit.ts +100 -21
  244. package/src/tools/eval.d.ts +53 -4
  245. package/src/tools/eval.ts +173 -137
  246. package/src/tools/find.d.ts +14 -4
  247. package/src/tools/find.ts +71 -18
  248. package/src/tools/glob.d.ts +3 -3
  249. package/src/tools/glob.ts +51 -17
  250. package/src/tools/grep.d.ts +2 -2
  251. package/src/tools/grep.ts +99 -22
  252. package/src/tools/grouped-file-output.d.ts +9 -4
  253. package/src/tools/grouped-file-output.ts +21 -6
  254. package/src/tools/lsp.ts +30 -18
  255. package/src/tools/mcp.ts +2 -2
  256. package/src/tools/native-view.d.ts +1 -1
  257. package/src/tools/native-view.ts +2 -2
  258. package/src/tools/read.d.ts +32 -10
  259. package/src/tools/read.ts +89 -43
  260. package/src/tools/renderer.d.ts +2 -0
  261. package/src/tools/renderer.ts +2 -0
  262. package/src/tools/task.d.ts +31 -0
  263. package/src/tools/task.ts +40 -7
  264. package/src/tools/todo.ts +3 -2
  265. package/src/tools/wait.ts +16 -30
  266. package/src/tools/web-search.ts +2 -1
  267. package/src/tools/write.d.ts +2 -0
  268. package/src/tools/write.ts +36 -12
  269. package/src/tools/yield.ts +2 -2
  270. package/src/tui.d.ts +20 -3
  271. package/src/tui.ts +140 -36
  272. package/src/utils.ts +5 -3
  273. package/dist/types/tools/eval-format/code-mode-declarations.d.ts +0 -5
  274. package/src/tools/eval-format/code-mode-declarations.ts +0 -61
package/src/tools/bash.ts CHANGED
@@ -62,6 +62,8 @@ export interface BashToolDetails {
62
62
  requestedTimeoutSeconds?: number;
63
63
  timeoutDisabled?: boolean;
64
64
  wallTimeMs?: number;
65
+ /** False when no wall-time footer was emitted; absent on older results. */
66
+ wallTimeNoticeEmitted?: boolean;
65
67
  /** Exit code of a command that ran to completion but failed (non-zero). */
66
68
  exitCode?: number;
67
69
  /** True when the command was killed by its timeout deadline (not a failure). */
@@ -75,8 +77,9 @@ export interface BashToolDetails {
75
77
  timedOut: boolean;
76
78
  pid?: number;
77
79
  };
80
+ /** Background job behind the call; `cancelled`: killed (`proc://<id>/kill`) before it finished. */
78
81
  async?: {
79
- state: "running" | "completed" | "failed";
82
+ state: "running" | "completed" | "failed" | "cancelled";
80
83
  jobId: string;
81
84
  type: "bash";
82
85
  };
@@ -208,6 +211,9 @@ function formatWallTimeSeconds(wallTimeMs: number): string {
208
211
  return (wallTimeMs / 1000).toFixed(2);
209
212
  }
210
213
 
214
+ /** Minimum duration for a model-facing wall-time notice; shorter runs keep timing only in details. */
215
+ export const WALL_TIME_NOTICE_MIN_MS = 1000;
216
+
211
217
  /** Formats the model-facing command duration notice. */
212
218
  export function formatWallTimeNotice(wallTimeMs: number): string {
213
219
  return `Wall time: ${formatWallTimeSeconds(wallTimeMs)} seconds`;
@@ -318,7 +324,7 @@ function stripBashNotices(
318
324
  ? strippedOutput
319
325
  : stripTrailingNotice(strippedOutput, formatExitCodeNotice(details.exitCode));
320
326
  const withoutWall =
321
- details?.wallTimeMs === undefined
327
+ details?.wallTimeMs === undefined || details.wallTimeNoticeEmitted === false
322
328
  ? withoutExit
323
329
  : stripTrailingNotice(withoutExit, formatWallTimeNotice(details.wallTimeMs));
324
330
  return stripRawOutputArtifactNotice(withoutWall);
@@ -426,9 +432,9 @@ function shellRunState(
426
432
  cancelled: boolean,
427
433
  ): RunState {
428
434
  if (cancelled) return "cancelled";
435
+ if (details?.async?.state === "running") return "background";
429
436
  if (isPartial) return "running";
430
437
  if (details?.timedOut === true) return "timed-out";
431
- if (details?.async?.state === "running") return "background";
432
438
  if (isError || (details?.exitCode !== undefined && details.exitCode !== 0)) return "failed";
433
439
  return "done";
434
440
  }
@@ -677,13 +683,13 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
677
683
  const command = formatBashCommandText(toBashRenderArgs(args, config));
678
684
  const previewLines = renderContext?.previewLines ?? BASH_DEFAULT_PREVIEW_LINES;
679
685
  const state = shellRunState(details, isError, isPartial, options.cancelled === true);
680
- // Settled: the measured wall time wins. A background job outlives the call: its run time is unknown here.
681
- const elapsedMs =
682
- state === "background"
686
+ // Live (a running call, or a parked job still streaming): the call's clock. Settled: the
687
+ // measured wall time wins. A frozen background snapshot's run time is unknown here.
688
+ const elapsedMs = isPartial
689
+ ? options.elapsedMs
690
+ : state === "background"
683
691
  ? undefined
684
- : isPartial
685
- ? options.elapsedMs
686
- : (details?.wallTimeMs ?? options.elapsedMs);
692
+ : (details?.wallTimeMs ?? options.elapsedMs);
687
693
  const meta = details?.meta;
688
694
  const box = runBox(
689
695
  [
@@ -10,6 +10,7 @@ import {
10
10
  replaceTabs,
11
11
  TRUNCATE_LENGTHS,
12
12
  truncateToWidth,
13
+ clipCollapsed,
13
14
  } from "../render/render-utils";
14
15
  import type { NativeToolView, RenderResultOptions, ToolRenderer, ToolRenderResult } from "./renderer";
15
16
  import { ansi, kv } from "../native/describe";
@@ -210,7 +211,7 @@ export const debugToolRenderer = {
210
211
  const previewLimit = options.expanded ? PREVIEW_LIMITS.EXPANDED_LINES : PREVIEW_LIMITS.COLLAPSED_LINES;
211
212
  const displayedLines = rawLines
212
213
  .slice(0, previewLimit)
213
- .map(line => truncateToWidth(line, TRUNCATE_LENGTHS.LINE));
214
+ .map(line => clipCollapsed(line, TRUNCATE_LENGTHS.LINE, options.expanded));
214
215
  const remaining = rawLines.length - displayedLines.length;
215
216
  if (remaining > 0) {
216
217
  displayedLines.push(
@@ -7,7 +7,7 @@ import type { Theme } from "../theme/theme";
7
7
  import { formatOutputPaneLines, styleToolOutputLine } from "../render/output-pane";
8
8
  import { renderStatusLine, type StatusLineOptions } from "../render/status-line";
9
9
  import { plainToolCard, type ToolCardPhase } from "../render/tool-card";
10
- import { truncateToWidth } from "../render/render-utils";
10
+ import { clipCollapsed } from "../render/render-utils";
11
11
  import {
12
12
  describeJsonTree,
13
13
  formatArgsInline,
@@ -133,7 +133,8 @@ function buildDefaultToolBody(input: DefaultToolRenderInput, uiTheme: Theme, con
133
133
  expanded: options.expanded,
134
134
  collapsedMaxLines: 4,
135
135
  expandedMaxLines: 12,
136
- styleLine: line => truncateToWidth(styleToolOutputLine(line, uiTheme), contentWidth),
136
+ styleLine: line =>
137
+ clipCollapsed(styleToolOutputLine(line, uiTheme), contentWidth, options.expanded),
137
138
  showExpandHintWhenUncapped: true,
138
139
  },
139
140
  uiTheme,
@@ -133,6 +133,8 @@ export interface EditRenderContext {
133
133
  renderDiff?: (diffText: string, options?: {
134
134
  filePath?: string;
135
135
  }) => string;
136
+ /** Unsanitized result metadata, used only for filesystem link targets. */
137
+ rawDetails?: unknown;
136
138
  }
137
139
  /** Render replacement text until a computed diff preview is available. */
138
140
  export declare function renderStreamingFallback(editMode: EditMode, args: unknown, theme: Theme): string;
package/src/tools/edit.ts CHANGED
@@ -198,6 +198,8 @@ export interface EditRenderContext {
198
198
  editStreamingFallback?: string;
199
199
  /** Function to render diff text with syntax highlighting */
200
200
  renderDiff?: (diffText: string, options?: { filePath?: string }) => string;
201
+ /** Unsanitized result metadata, used only for filesystem link targets. */
202
+ rawDetails?: unknown;
201
203
  }
202
204
 
203
205
  /**
@@ -297,7 +299,7 @@ function truncateEditTitlePath(displayPath: string, maxWidth: number | undefined
297
299
  }
298
300
 
299
301
  function formatEditTitlePath(pathValue: string, maxWidth?: number): string {
300
- return truncateEditTitlePath(replaceTabs(shortenPath(pathValue)), maxWidth);
302
+ return truncateEditTitlePath(replaceTabs(shortenPath(sanitizeText(pathValue))), maxWidth);
301
303
  }
302
304
 
303
305
  function formatEditPathDisplay(
@@ -357,6 +359,7 @@ function renderEditHeader(
357
359
  rename?: string;
358
360
  firstChangedLine?: number;
359
361
  linkPath?: string;
362
+ renameLinkPath?: string;
360
363
  statsSuffix?: string;
361
364
  extraSuffix?: string;
362
365
  title?: string;
@@ -367,6 +370,7 @@ function renderEditHeader(
367
370
  rename: options.rename,
368
371
  firstChangedLine: options.firstChangedLine,
369
372
  linkPath: options.linkPath,
373
+ renameLinkPath: options.renameLinkPath,
370
374
  };
371
375
  const formatted = formatEditDescription(options.rawPath, uiTheme, descriptionOptions);
372
376
  const suffix = `${options.statsSuffix ?? ""}${options.extraSuffix ?? ""}`;
@@ -402,7 +406,14 @@ function renderEditHeader(
402
406
  */
403
407
  function renderInlineEditRow(
404
408
  uiTheme: Theme,
405
- opts: { op?: Operation; rename?: string; rawPath: string; linkPath?: string; pending: boolean },
409
+ opts: {
410
+ op?: Operation;
411
+ rename?: string;
412
+ rawPath: string;
413
+ linkPath?: string;
414
+ renameLinkPath?: string;
415
+ pending: boolean;
416
+ },
406
417
  ): Component {
407
418
  const isDelete = opts.op === "delete";
408
419
  return new WidthAwareText(
@@ -417,6 +428,7 @@ function renderInlineEditRow(
417
428
  rawPath: opts.rawPath,
418
429
  rename: opts.rename,
419
430
  linkPath: opts.linkPath,
431
+ renameLinkPath: opts.renameLinkPath,
420
432
  }),
421
433
  0,
422
434
  0,
@@ -481,6 +493,21 @@ interface StreamingDiffTail {
481
493
  hidden: boolean;
482
494
  }
483
495
 
496
+ /**
497
+ * Visual rows a raw diff line occupies once `renderDiff` gives it a code-frame
498
+ * gutter and `wrapEditRendererLine` hangs its continuations beneath it.
499
+ * Canonical `+12|` rows render a padded `max(3, digits) + 1` gutter plus `│`;
500
+ * rows already carrying a `│` rail keep their own prefix. Content wraps at the
501
+ * width left after that gutter; unnumbered rows wrap whole.
502
+ */
503
+ function streamingDiffLineRows(rawLine: string, innerWidth: number): number {
504
+ const numbered = /^([+\-\s]\s*(\d+)[|│])(.*)$/s.exec(rawLine) ?? /^([+\-\s]\s*(\d+)\s)(.*)$/s.exec(rawLine);
505
+ if (!numbered) return Math.max(1, wrapTextWithAnsi(replaceTabs(rawLine), innerWidth).length);
506
+ const gutterWidth = Math.max(visibleWidth(numbered[1]!), Math.max(3, numbered[2]!.length) + 2);
507
+ const contentWidth = Math.max(1, innerWidth - gutterWidth);
508
+ return Math.max(1, wrapTextWithAnsi(replaceTabs(numbered[3] ?? ""), contentWidth).length);
509
+ }
510
+
484
511
  /**
485
512
  * Select the trailing physical lines that fit the live preview budget.
486
513
  *
@@ -500,7 +527,7 @@ function sliceStreamingDiffTail(diff: string, innerWidth: number, budget: number
500
527
  while (cursor >= 0) {
501
528
  const newline = cursor > 0 ? diff.lastIndexOf("\n", cursor - 1) : -1;
502
529
  const lineStart = newline + 1;
503
- const lineRows = Math.max(1, wrapTextWithAnsi(replaceTabs(diff.slice(lineStart, cursor)), innerWidth).length);
530
+ const lineRows = streamingDiffLineRows(diff.slice(lineStart, cursor), innerWidth);
504
531
  if (visualRows > 0 && visualRows + lineRows > rowLimit) break;
505
532
  visualRows += lineRows;
506
533
  start = lineStart;
@@ -1142,7 +1169,10 @@ export const editToolRenderer = {
1142
1169
  while (bodyLines.length > 0 && bodyLines[0].trim() === "") bodyLines.shift();
1143
1170
  return {
1144
1171
  header,
1145
- sections: bodyLines.length > 0 ? [{ content: bodyLines }] : [],
1172
+ sections:
1173
+ bodyLines.length > 0
1174
+ ? [{ content: bodyLines.flatMap(line => wrapEditRendererLine(line, Math.max(1, width - 2))) }]
1175
+ : [],
1146
1176
  phase: applyPatchError ? "error" : "pending",
1147
1177
  borderColor: applyPatchError ? "error" : "borderMuted",
1148
1178
  contentPaddingLeft: 0,
@@ -1192,7 +1222,7 @@ export const editToolRenderer = {
1192
1222
  diffText = args.previewDiff ?? args.diff ?? "";
1193
1223
  body.push(editDiff(diffText, rawPath));
1194
1224
  } else if (args.diff || args.newText || args.patch) {
1195
- body.push(code(args.diff ?? args.newText ?? args.patch ?? "", { lang: getLanguageFromPath(rawPath) }));
1225
+ body.push(code(args.diff ?? args.newText ?? args.patch ?? "", { path: rawPath || undefined }));
1196
1226
  } else if (renderContext?.editDiffPreview) {
1197
1227
  const preview = renderContext.editDiffPreview;
1198
1228
  if ("error" in preview && preview.error) body.push(errorText(preview.error));
@@ -1230,8 +1260,18 @@ export const editToolRenderer = {
1230
1260
  let added = 0;
1231
1261
  let removed = 0;
1232
1262
  // Files are borderless sections inside the one tool frame.
1233
- const body: NativeChild[] = perFileResults.map(fileResult => {
1234
- const file = editFileParts({ content: [], details: fileResult, isError: fileResult.isError }, options);
1263
+ const body: NativeChild[] = perFileResults.map((fileResult, index) => {
1264
+ const rawDetails = options.renderContext?.rawDetails;
1265
+ const rawPerFile =
1266
+ rawDetails && typeof rawDetails === "object" && "perFileResults" in rawDetails
1267
+ ? rawDetails.perFileResults
1268
+ : undefined;
1269
+ const file = editFileParts(
1270
+ { content: [], details: fileResult, isError: fileResult.isError },
1271
+ options,
1272
+ undefined,
1273
+ Array.isArray(rawPerFile) ? rawPerFile[index] : undefined,
1274
+ );
1235
1275
  added += file.added;
1236
1276
  removed += file.removed;
1237
1277
  return fileDiffSection(file, file.body, {
@@ -1304,13 +1344,13 @@ function editToolHead(facts: EditHeadFacts): NativeToolHead {
1304
1344
  const multi = (facts.files ?? 0) > 1;
1305
1345
  const meta = compact([
1306
1346
  diffStatsMeta(facts.added, facts.removed),
1307
- !multi && facts.rename ? `→ ${displayPath(facts.rename)}` : undefined,
1347
+ !multi && facts.rename ? `→ ${displayPath(sanitizeText(facts.rename))}` : undefined,
1308
1348
  ]);
1309
1349
  const badge = facts.diagnostics && diagnosticsBadge(facts.diagnostics);
1310
1350
  const target = multi
1311
1351
  ? `${facts.files} files`
1312
1352
  : facts.path
1313
- ? `${displayPath(facts.path)}${facts.line ? `:${facts.line}` : ""}`
1353
+ ? `${displayPath(sanitizeText(facts.path))}${facts.line ? `:${facts.line}` : ""}`
1314
1354
  : undefined;
1315
1355
  return {
1316
1356
  title: multi ? "Edit" : getOperationTitle(facts.op),
@@ -1322,9 +1362,9 @@ function editToolHead(facts: EditHeadFacts): NativeToolHead {
1322
1362
  };
1323
1363
  }
1324
1364
 
1325
- /** A file's diff: hunks highlighted by the path's language, no path header (the head or section names the file). */
1365
+ /** A file's diff: Tern highlights the hunks by the file's grammar and draws no path header (the head or section names the file). */
1326
1366
  function editDiff(diffText: string, filePath: string): NativeNode {
1327
- return nativeDiff(diffText, { lang: filePath ? getLanguageFromPath(filePath) : undefined });
1367
+ return nativeDiff(diffText, { path: filePath || undefined });
1328
1368
  }
1329
1369
 
1330
1370
  /** One file's edit result for native views: head facts plus its body (diff or error, then diagnostics). */
@@ -1337,6 +1377,15 @@ interface EditFileParts extends Omit<EditHeadFacts, "diagnostics"> {
1337
1377
  body: NativeChild[];
1338
1378
  }
1339
1379
 
1380
+ /** Raw metadata paths are reserved for link targets; visible content comes from sanitized details. */
1381
+ function rawEditPaths(value: unknown): { path?: string; sourcePath?: string } {
1382
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return {};
1383
+ return {
1384
+ path: "path" in value && typeof value.path === "string" ? value.path : undefined,
1385
+ sourcePath: "sourcePath" in value && typeof value.sourcePath === "string" ? value.sourcePath : undefined,
1386
+ };
1387
+ }
1388
+
1340
1389
  function editFileParts(
1341
1390
  result: {
1342
1391
  content: Array<{ type: string; text?: string }>;
@@ -1345,8 +1394,10 @@ function editFileParts(
1345
1394
  },
1346
1395
  options: RenderResultOptions & { renderContext?: EditRenderContext },
1347
1396
  args?: EditRenderArgs,
1397
+ rawFileDetails?: unknown,
1348
1398
  ): EditFileParts {
1349
1399
  const details = result.details;
1400
+ const rawPaths = rawEditPaths(rawFileDetails ?? options.renderContext?.rawDetails);
1350
1401
  const isError = result.isError ?? (details && "isError" in details ? details.isError : false) ?? false;
1351
1402
  const firstEdit = Array.isArray(args?.edits) ? args.edits[0] : undefined;
1352
1403
  const firstHashlineInputEntry = getHashlineInputRenderSummary(args ?? {}, options.renderContext?.editMode)
@@ -1373,7 +1424,7 @@ function editFileParts(
1373
1424
  const firstChangedLine =
1374
1425
  (editDiffPreview && "firstChangedLine" in editDiffPreview ? editDiffPreview.firstChangedLine : undefined) ||
1375
1426
  (details && !isError ? details.firstChangedLine : undefined);
1376
- const shownPath = displayPath(detailPath ?? rawPath);
1427
+ const shownPath = displayPath(sanitizeText(detailPath ?? rawPath));
1377
1428
  const body: NativeChild[] = [];
1378
1429
  if (isError) {
1379
1430
  const displayErrorText = details && "displayErrorText" in details ? details.displayErrorText : undefined;
@@ -1392,10 +1443,10 @@ function editFileParts(
1392
1443
  const stats = diffText ? getDiffStats(diffText) : undefined;
1393
1444
  return {
1394
1445
  op,
1395
- path: rawPath,
1446
+ path: sanitizeText(rawPath),
1396
1447
  line: firstChangedLine,
1397
- rename,
1398
- resolvedPath: detailPath,
1448
+ rename: rename === undefined ? undefined : sanitizeText(rename),
1449
+ resolvedPath: rawPaths.path ?? detailPath,
1399
1450
  added: stats?.added ?? 0,
1400
1451
  removed: stats?.removed ?? 0,
1401
1452
  isError: Boolean(isError),
@@ -1415,6 +1466,7 @@ function renderSingleFileResult(
1415
1466
  args?: EditRenderArgs,
1416
1467
  ): Component {
1417
1468
  const details = result.details;
1469
+ const rawPaths = rawEditPaths(options.renderContext?.rawDetails);
1418
1470
  const isError = result.isError ?? (details && "isError" in details ? details.isError : false);
1419
1471
  const edits = Array.isArray(args?.edits) ? args.edits : undefined;
1420
1472
  const firstEdit = edits?.[0];
@@ -1449,8 +1501,17 @@ function renderSingleFileResult(
1449
1501
  // container. Errors, no-ops, creates, move-with-edits, and anything with
1450
1502
  // diagnostics keep the framed block below.
1451
1503
  if (!isError && !details?.diff && !details?.diagnostics && (op === "delete" || rename)) {
1452
- const linkPath = details && "path" in details ? details.path : undefined;
1453
- return renderInlineEditRow(uiTheme, { op, rename, rawPath, linkPath, pending: false });
1504
+ const linkPath = rename
1505
+ ? (rawPaths.sourcePath ?? moveSource ?? rawPaths.path ?? detailPath)
1506
+ : (rawPaths.path ?? detailPath);
1507
+ return renderInlineEditRow(uiTheme, {
1508
+ op,
1509
+ rename,
1510
+ rawPath,
1511
+ linkPath,
1512
+ renameLinkPath: rename ? (rawPaths.path ?? detailPath) : undefined,
1513
+ pending: false,
1514
+ });
1454
1515
  }
1455
1516
 
1456
1517
  const renderFallbackDiff = (diffText: string, diffOptions?: { filePath?: string }): string =>
@@ -1481,7 +1542,9 @@ function renderSingleFileResult(
1481
1542
  const firstChangedLine =
1482
1543
  (editDiffPreview && "firstChangedLine" in editDiffPreview ? editDiffPreview.firstChangedLine : undefined) ||
1483
1544
  (details && !isError ? details.firstChangedLine : undefined);
1484
- const linkPath = details && "path" in details ? details.path : undefined;
1545
+ const linkPath = rename
1546
+ ? (rawPaths.sourcePath ?? moveSource ?? rawPaths.path ?? detailPath)
1547
+ : (rawPaths.path ?? detailPath);
1485
1548
 
1486
1549
  // Change stats ride inline on the header bar next to the path.
1487
1550
  const previewDiff = editDiffPreview && !("error" in editDiffPreview) ? editDiffPreview.diff : undefined;
@@ -1499,6 +1562,7 @@ function renderSingleFileResult(
1499
1562
  rename,
1500
1563
  firstChangedLine,
1501
1564
  linkPath,
1565
+ renameLinkPath: rename ? (rawPaths.path ?? detailPath) : undefined,
1502
1566
  statsSuffix,
1503
1567
  });
1504
1568
  const innerWidth = Math.max(1, width - 2);
@@ -1523,7 +1587,11 @@ function renderSingleFileResult(
1523
1587
  // (Delete / `src → dst`); only a true no-op needs an explanatory
1524
1588
  // body so an empty card isn't mistaken for a stalled edit.
1525
1589
  if (op !== "delete" && op !== "create" && !rename) {
1526
- const noChangePath = linkPath ? shortenPath(linkPath) : rawPath ? shortenPath(rawPath) : "";
1590
+ const noChangePath = detailPath
1591
+ ? shortenPath(detailPath)
1592
+ : rawPath
1593
+ ? shortenPath(sanitizeText(rawPath))
1594
+ : "";
1527
1595
  body = uiTheme.fg("dim", `No changes were made${noChangePath ? ` to ${noChangePath}` : ""}.`);
1528
1596
  }
1529
1597
  } else if (editDiffPreview) {
@@ -1575,8 +1643,19 @@ function renderMultiFileResult(
1575
1643
  options: RenderResultOptions & { renderContext?: EditRenderContext },
1576
1644
  uiTheme: Theme,
1577
1645
  ): Component {
1578
- const fileComponents = perFileResults.map(fileResult =>
1579
- renderSingleFileResult({ content: [], details: fileResult, isError: fileResult.isError }, options, uiTheme),
1646
+ const rawDetails = options.renderContext?.rawDetails;
1647
+ const rawPerFile =
1648
+ rawDetails && typeof rawDetails === "object" && "perFileResults" in rawDetails
1649
+ ? rawDetails.perFileResults
1650
+ : undefined;
1651
+ const fileComponents = perFileResults.map((fileResult, index) =>
1652
+ renderSingleFileResult(
1653
+ { content: [], details: fileResult, isError: fileResult.isError },
1654
+ Array.isArray(rawPerFile)
1655
+ ? { ...options, renderContext: { ...options.renderContext, rawDetails: rawPerFile[index] } }
1656
+ : options,
1657
+ uiTheme,
1658
+ ),
1580
1659
  );
1581
1660
  const remaining = Math.max(0, totalFiles - perFileResults.length);
1582
1661
 
@@ -13,25 +13,60 @@ export interface EvalStatusEvent {
13
13
  resolvedThinkingLevel?: ConfiguredThinkingLevel;
14
14
  [key: string]: unknown;
15
15
  }
16
+ /** One column of a table view. */
17
+ export interface EvalTableColumn {
18
+ head: string;
19
+ /** Numeric values: renderers right-align the column. */
20
+ numeric?: boolean;
21
+ }
22
+ /**
23
+ * A pretty rendering of something a cell printed, shown to the user in place
24
+ * of its text; the model only ever sees the text.
25
+ * - `table`: a data frame or `console.table`; leading index columns are plain
26
+ * columns, and a truncated frame's elided rows/columns are `…` cells.
27
+ * - `json`: a structured value (dict/list, object/array) as a collapsible tree.
28
+ * - `markdown`: an object's Markdown representation.
29
+ */
30
+ export type EvalView = {
31
+ kind: "table";
32
+ columns: EvalTableColumn[];
33
+ rows: string[][];
34
+ } | {
35
+ kind: "json";
36
+ value: unknown;
37
+ } | {
38
+ kind: "markdown";
39
+ text: string;
40
+ };
41
+ /**
42
+ * A view and the exact printed text it stands for. Renderers locate `text` in
43
+ * the cell's `output` (entries in print order) and show the view there; an
44
+ * entry whose text is no longer in the output (truncated) is skipped.
45
+ */
46
+ export interface EvalViewEntry {
47
+ text: string;
48
+ view: EvalView;
49
+ }
16
50
  /** Per-cell execution result for transcript rendering. */
17
51
  export interface EvalCellResult {
18
52
  index: number;
19
53
  title?: string;
20
54
  code: string;
21
55
  language?: EvalLanguage;
56
+ /** What the model saw: the cell's printed text plus image notes. */
22
57
  output: string;
58
+ /** Pretty views of spans of `output`, in print order. */
59
+ views?: EvalViewEntry[];
23
60
  status: "pending" | "running" | "complete" | "error";
24
61
  durationMs?: number;
25
62
  exitCode?: number;
26
63
  statusEvents?: EvalStatusEvent[];
27
64
  /** Discrete events dropped from the front of `statusEvents` (see {@link recordStatusEvent}). */
28
65
  statusEventsElided?: number;
29
- hasMarkdown?: boolean;
30
66
  }
31
67
  /** Tool result detail object surfaced to the UI/transcript. */
32
68
  export interface EvalToolDetails {
33
69
  cells?: EvalCellResult[];
34
- jsonOutputs?: unknown[];
35
70
  images?: ImageContent[];
36
71
  statusEvents?: EvalStatusEvent[];
37
72
  /** Discrete events dropped from the front of `statusEvents` (see {@link recordStatusEvent}). */
@@ -44,9 +79,9 @@ export interface EvalToolDetails {
44
79
  languages?: EvalLanguage[];
45
80
  /** Optional human-readable notice (e.g. fallback explanation). */
46
81
  notice?: string;
47
- /** Present when the cell was auto-backgrounded as an async job. */
82
+ /** Present when the cell was auto-backgrounded as an async job; `cancelled`: killed before it finished. */
48
83
  async?: {
49
- state: "running" | "completed" | "failed";
84
+ state: "running" | "completed" | "failed" | "cancelled";
50
85
  jobId: string;
51
86
  type: "eval";
52
87
  };
@@ -100,6 +135,20 @@ export declare function upsertStatusEvent(events: EvalStatusEvent[], event: Eval
100
135
  * events behind an "… N earlier" row.
101
136
  */
102
137
  export declare function recordStatusEvent(log: StatusEventLog, event: EvalStatusEvent): void;
138
+ /** One ordered piece of a cell's output: printed text, or a view shown in place of the text it matched. */
139
+ export type EvalOutputPart = {
140
+ kind: "text";
141
+ text: string;
142
+ } | {
143
+ kind: "view";
144
+ view: EvalView;
145
+ };
146
+ /**
147
+ * Split `output` around its views, in order: each view's `text` is searched
148
+ * from the end of the previous match, unmatched entries are skipped, and
149
+ * blank text between views is dropped (newlines bordering a view trimmed).
150
+ */
151
+ export declare function splitEvalOutput(output: string, views: readonly EvalViewEntry[] | undefined): EvalOutputPart[];
103
152
  /** Render eval code cells, structured display output, and progress events. */
104
153
  export declare const evalToolRenderer: {
105
154
  animatedPendingPreview: true;