@esso0428/pi-subagents 0.17.6 → 0.17.7

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 (260) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/CONTRIBUTING.md +4 -0
  3. package/dist/abortable.d.ts +13 -0
  4. package/dist/abortable.d.ts.map +1 -0
  5. package/dist/abortable.js +43 -0
  6. package/dist/abortable.js.map +1 -0
  7. package/dist/agent-color.d.ts +36 -0
  8. package/dist/agent-color.d.ts.map +1 -0
  9. package/dist/agent-color.js +124 -0
  10. package/dist/agent-color.js.map +1 -0
  11. package/dist/agent-file-toggle.d.ts +126 -0
  12. package/dist/agent-file-toggle.d.ts.map +1 -0
  13. package/dist/agent-file-toggle.js +259 -0
  14. package/dist/agent-file-toggle.js.map +1 -0
  15. package/dist/agent-history.d.ts +4 -0
  16. package/dist/agent-history.d.ts.map +1 -1
  17. package/dist/agent-history.js +47 -1
  18. package/dist/agent-history.js.map +1 -1
  19. package/dist/agent-manager.d.ts +370 -56
  20. package/dist/agent-manager.d.ts.map +1 -1
  21. package/dist/agent-manager.js +1123 -409
  22. package/dist/agent-manager.js.map +1 -1
  23. package/dist/agent-runner.d.ts +100 -10
  24. package/dist/agent-runner.d.ts.map +1 -1
  25. package/dist/agent-runner.js +166 -21
  26. package/dist/agent-runner.js.map +1 -1
  27. package/dist/agent-types.d.ts +57 -5
  28. package/dist/agent-types.d.ts.map +1 -1
  29. package/dist/agent-types.js +164 -32
  30. package/dist/agent-types.js.map +1 -1
  31. package/dist/child-context.d.ts +3 -0
  32. package/dist/child-context.d.ts.map +1 -0
  33. package/dist/child-context.js +13 -0
  34. package/dist/child-context.js.map +1 -0
  35. package/dist/cross-extension-rpc.d.ts +23 -3
  36. package/dist/cross-extension-rpc.d.ts.map +1 -1
  37. package/dist/cross-extension-rpc.js +79 -17
  38. package/dist/cross-extension-rpc.js.map +1 -1
  39. package/dist/custom-agents.d.ts +38 -1
  40. package/dist/custom-agents.d.ts.map +1 -1
  41. package/dist/custom-agents.js +164 -12
  42. package/dist/custom-agents.js.map +1 -1
  43. package/dist/index.d.ts +34 -0
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +1908 -495
  46. package/dist/index.js.map +1 -1
  47. package/dist/invocation-config.d.ts +87 -2
  48. package/dist/invocation-config.d.ts.map +1 -1
  49. package/dist/invocation-config.js +71 -3
  50. package/dist/invocation-config.js.map +1 -1
  51. package/dist/mention-clone.d.ts +88 -0
  52. package/dist/mention-clone.d.ts.map +1 -0
  53. package/dist/mention-clone.js +154 -0
  54. package/dist/mention-clone.js.map +1 -0
  55. package/dist/mention.d.ts +82 -0
  56. package/dist/mention.d.ts.map +1 -0
  57. package/dist/mention.js +132 -0
  58. package/dist/mention.js.map +1 -0
  59. package/dist/model-resolver.d.ts +17 -0
  60. package/dist/model-resolver.d.ts.map +1 -1
  61. package/dist/model-resolver.js +15 -0
  62. package/dist/model-resolver.js.map +1 -1
  63. package/dist/model-scope.d.ts +50 -0
  64. package/dist/model-scope.d.ts.map +1 -0
  65. package/dist/model-scope.js +49 -0
  66. package/dist/model-scope.js.map +1 -0
  67. package/dist/nested-tools.d.ts +57 -0
  68. package/dist/nested-tools.d.ts.map +1 -0
  69. package/dist/nested-tools.js +301 -0
  70. package/dist/nested-tools.js.map +1 -0
  71. package/dist/output-file.d.ts +22 -3
  72. package/dist/output-file.d.ts.map +1 -1
  73. package/dist/output-file.js +58 -7
  74. package/dist/output-file.js.map +1 -1
  75. package/dist/prompts.d.ts +23 -0
  76. package/dist/prompts.d.ts.map +1 -1
  77. package/dist/prompts.js +20 -2
  78. package/dist/prompts.js.map +1 -1
  79. package/dist/schedule.d.ts.map +1 -1
  80. package/dist/schedule.js +36 -15
  81. package/dist/schedule.js.map +1 -1
  82. package/dist/settings.d.ts +228 -2
  83. package/dist/settings.d.ts.map +1 -1
  84. package/dist/settings.js +94 -0
  85. package/dist/settings.js.map +1 -1
  86. package/dist/status-note.d.ts +49 -1
  87. package/dist/status-note.d.ts.map +1 -1
  88. package/dist/status-note.js +62 -1
  89. package/dist/status-note.js.map +1 -1
  90. package/dist/structured-output.d.ts +62 -0
  91. package/dist/structured-output.d.ts.map +1 -0
  92. package/dist/structured-output.js +113 -0
  93. package/dist/structured-output.js.map +1 -0
  94. package/dist/types.d.ts +176 -10
  95. package/dist/types.d.ts.map +1 -1
  96. package/dist/ui/agent-mention.d.ts +83 -0
  97. package/dist/ui/agent-mention.d.ts.map +1 -0
  98. package/dist/ui/agent-mention.js +188 -0
  99. package/dist/ui/agent-mention.js.map +1 -0
  100. package/dist/ui/agent-widget.d.ts +96 -75
  101. package/dist/ui/agent-widget.d.ts.map +1 -1
  102. package/dist/ui/agent-widget.js +397 -420
  103. package/dist/ui/agent-widget.js.map +1 -1
  104. package/dist/ui/conversation-blocks.d.ts.map +1 -1
  105. package/dist/ui/conversation-blocks.js +6 -0
  106. package/dist/ui/conversation-blocks.js.map +1 -1
  107. package/dist/ui/conversation-timeline.d.ts +10 -2
  108. package/dist/ui/conversation-timeline.d.ts.map +1 -1
  109. package/dist/ui/conversation-timeline.js +130 -23
  110. package/dist/ui/conversation-timeline.js.map +1 -1
  111. package/dist/ui/conversation-viewer.d.ts +15 -5
  112. package/dist/ui/conversation-viewer.d.ts.map +1 -1
  113. package/dist/ui/conversation-viewer.js +202 -50
  114. package/dist/ui/conversation-viewer.js.map +1 -1
  115. package/dist/ui/fleet-list.d.ts +198 -0
  116. package/dist/ui/fleet-list.d.ts.map +1 -0
  117. package/dist/ui/fleet-list.js +487 -0
  118. package/dist/ui/fleet-list.js.map +1 -0
  119. package/dist/ui/schedule-menu.d.ts.map +1 -1
  120. package/dist/ui/schedule-menu.js +6 -7
  121. package/dist/ui/schedule-menu.js.map +1 -1
  122. package/dist/ui/select-item.d.ts +28 -0
  123. package/dist/ui/select-item.d.ts.map +1 -0
  124. package/dist/ui/select-item.js +35 -0
  125. package/dist/ui/select-item.js.map +1 -0
  126. package/dist/ui/workflow-card.d.ts +176 -0
  127. package/dist/ui/workflow-card.d.ts.map +1 -0
  128. package/dist/ui/workflow-card.js +333 -0
  129. package/dist/ui/workflow-card.js.map +1 -0
  130. package/dist/ui/workflow-dialog.d.ts +306 -0
  131. package/dist/ui/workflow-dialog.d.ts.map +1 -0
  132. package/dist/ui/workflow-dialog.js +844 -0
  133. package/dist/ui/workflow-dialog.js.map +1 -0
  134. package/dist/ui/workflow-menu.d.ts +61 -0
  135. package/dist/ui/workflow-menu.d.ts.map +1 -0
  136. package/dist/ui/workflow-menu.js +148 -0
  137. package/dist/ui/workflow-menu.js.map +1 -0
  138. package/dist/usage.d.ts +86 -1
  139. package/dist/usage.d.ts.map +1 -1
  140. package/dist/usage.js +72 -1
  141. package/dist/usage.js.map +1 -1
  142. package/dist/workflow/collisions.d.ts +96 -0
  143. package/dist/workflow/collisions.d.ts.map +1 -0
  144. package/dist/workflow/collisions.js +89 -0
  145. package/dist/workflow/collisions.js.map +1 -0
  146. package/dist/workflow/entry.d.ts +33 -0
  147. package/dist/workflow/entry.d.ts.map +1 -0
  148. package/dist/workflow/entry.js +30 -0
  149. package/dist/workflow/entry.js.map +1 -0
  150. package/dist/workflow/host.d.ts +63 -0
  151. package/dist/workflow/host.d.ts.map +1 -0
  152. package/dist/workflow/host.js +363 -0
  153. package/dist/workflow/host.js.map +1 -0
  154. package/dist/workflow/journal.d.ts +98 -0
  155. package/dist/workflow/journal.d.ts.map +1 -0
  156. package/dist/workflow/journal.js +121 -0
  157. package/dist/workflow/journal.js.map +1 -0
  158. package/dist/workflow/json-schema.d.ts +52 -0
  159. package/dist/workflow/json-schema.d.ts.map +1 -0
  160. package/dist/workflow/json-schema.js +112 -0
  161. package/dist/workflow/json-schema.js.map +1 -0
  162. package/dist/workflow/meta.d.ts +68 -0
  163. package/dist/workflow/meta.d.ts.map +1 -0
  164. package/dist/workflow/meta.js +318 -0
  165. package/dist/workflow/meta.js.map +1 -0
  166. package/dist/workflow/progress.d.ts +225 -0
  167. package/dist/workflow/progress.d.ts.map +1 -0
  168. package/dist/workflow/progress.js +362 -0
  169. package/dist/workflow/progress.js.map +1 -0
  170. package/dist/workflow/runtime.d.ts +335 -0
  171. package/dist/workflow/runtime.d.ts.map +1 -0
  172. package/dist/workflow/runtime.js +831 -0
  173. package/dist/workflow/runtime.js.map +1 -0
  174. package/dist/workflow/saved.d.ts +91 -0
  175. package/dist/workflow/saved.d.ts.map +1 -0
  176. package/dist/workflow/saved.js +204 -0
  177. package/dist/workflow/saved.js.map +1 -0
  178. package/dist/workflow/task.d.ts +137 -0
  179. package/dist/workflow/task.d.ts.map +1 -0
  180. package/dist/workflow/task.js +208 -0
  181. package/dist/workflow/task.js.map +1 -0
  182. package/dist/workflow/tool-description.d.ts +39 -0
  183. package/dist/workflow/tool-description.d.ts.map +1 -0
  184. package/dist/workflow/tool-description.js +200 -0
  185. package/dist/workflow/tool-description.js.map +1 -0
  186. package/dist/workflow/worker-source.d.ts +48 -0
  187. package/dist/workflow/worker-source.d.ts.map +1 -0
  188. package/dist/workflow/worker-source.js +779 -0
  189. package/dist/workflow/worker-source.js.map +1 -0
  190. package/dist/worktree.d.ts +10 -3
  191. package/dist/worktree.d.ts.map +1 -1
  192. package/dist/worktree.js +58 -54
  193. package/dist/worktree.js.map +1 -1
  194. package/dist/xml.d.ts +11 -0
  195. package/dist/xml.d.ts.map +1 -0
  196. package/dist/xml.js +13 -0
  197. package/dist/xml.js.map +1 -0
  198. package/docs/rpc.md +183 -0
  199. package/docs/superpowers/plans/2026-09-30-upstream-event-workflow-partial-history.md +195 -0
  200. package/docs/superpowers/specs/2026-09-30-upstream-event-workflow-partial-history-design.md +49 -0
  201. package/docs/workflows.md +437 -0
  202. package/examples/agent-tool-description.md +7 -7
  203. package/examples/workflows/compose.js +51 -0
  204. package/examples/workflows/fan-out-audit.js +47 -0
  205. package/examples/workflows/gated-fix.js +60 -0
  206. package/examples/workflows/lib/count-child.js +27 -0
  207. package/examples/workflows/review-panel.js +63 -0
  208. package/examples/workflows/structured-findings.js +78 -0
  209. package/package.json +1 -1
  210. package/src/abortable.ts +43 -0
  211. package/src/agent-color.ts +161 -0
  212. package/src/agent-file-toggle.ts +269 -0
  213. package/src/agent-history.ts +54 -2
  214. package/src/agent-manager.ts +1263 -402
  215. package/src/agent-runner.ts +251 -27
  216. package/src/agent-types.ts +188 -32
  217. package/src/child-context.ts +15 -0
  218. package/src/cross-extension-rpc.ts +96 -20
  219. package/src/custom-agents.ts +170 -13
  220. package/src/index.ts +2024 -537
  221. package/src/invocation-config.ts +118 -3
  222. package/src/mention-clone.ts +196 -0
  223. package/src/mention.ts +141 -0
  224. package/src/model-resolver.ts +18 -0
  225. package/src/model-scope.ts +70 -0
  226. package/src/nested-tools.ts +424 -0
  227. package/src/output-file.ts +61 -6
  228. package/src/prompts.ts +45 -2
  229. package/src/schedule.ts +35 -14
  230. package/src/settings.ts +312 -2
  231. package/src/status-note.ts +66 -1
  232. package/src/structured-output.ts +130 -0
  233. package/src/types.ts +177 -10
  234. package/src/ui/agent-mention.ts +216 -0
  235. package/src/ui/agent-widget.ts +389 -441
  236. package/src/ui/conversation-blocks.ts +6 -0
  237. package/src/ui/conversation-timeline.ts +139 -25
  238. package/src/ui/conversation-viewer.ts +212 -48
  239. package/src/ui/fleet-list.ts +558 -0
  240. package/src/ui/schedule-menu.ts +9 -8
  241. package/src/ui/select-item.ts +45 -0
  242. package/src/ui/workflow-card.ts +470 -0
  243. package/src/ui/workflow-dialog.ts +1115 -0
  244. package/src/ui/workflow-menu.ts +193 -0
  245. package/src/usage.ts +109 -2
  246. package/src/workflow/collisions.ts +123 -0
  247. package/src/workflow/entry.ts +47 -0
  248. package/src/workflow/host.ts +403 -0
  249. package/src/workflow/journal.ts +164 -0
  250. package/src/workflow/json-schema.ts +128 -0
  251. package/src/workflow/meta.ts +325 -0
  252. package/src/workflow/progress.ts +550 -0
  253. package/src/workflow/runtime.ts +1219 -0
  254. package/src/workflow/saved.ts +217 -0
  255. package/src/workflow/task.ts +302 -0
  256. package/src/workflow/tool-description.ts +200 -0
  257. package/src/workflow/worker-source.ts +781 -0
  258. package/src/worktree.ts +69 -55
  259. package/src/xml.ts +13 -0
  260. package/vitest.config.ts +0 -18
@@ -251,6 +251,10 @@ function bashBlock(
251
251
  const body = command ? `$ ${command}${output ? `\n${output}` : ""}` : output;
252
252
  const toolLine = `· bash → ${command || "(no command)"}`;
253
253
  const fullText = bound(body, maxChars);
254
+ const result: ConversationToolResultSnapshot = {
255
+ toolName: "bash",
256
+ content: [{ type: "text", text: body }],
257
+ };
254
258
  return {
255
259
  id,
256
260
  kind: "tool",
@@ -261,6 +265,8 @@ function bashBlock(
261
265
  fullText,
262
266
  toolLine,
263
267
  toolName: "bash",
268
+ toolResult: result,
269
+ result,
264
270
  };
265
271
  }
266
272
 
@@ -1,14 +1,15 @@
1
1
  import * as CodingAgent from "@earendil-works/pi-coding-agent";
2
2
  import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
3
+ import type { MarkdownOptions, MarkdownTheme } from "@earendil-works/pi-tui";
3
4
  import {
4
5
  type Component,
5
6
  Markdown,
6
- Text,
7
7
  type TUI,
8
8
  type TuiMouseEvent,
9
9
  truncateToWidth,
10
+ wrapTextWithAnsi,
10
11
  } from "@earendil-works/pi-tui";
11
- import type { AgentRecord } from "../types.js";
12
+ import type { AgentRecord, ViewerMarkdownMode } from "../types.js";
12
13
  import type { Theme } from "./agent-widget.js";
13
14
  import { showMoreHintText } from "./ccstyle/diff/render-utils.js";
14
15
  import {
@@ -73,6 +74,7 @@ export interface ConversationTimelineChange {
73
74
  export interface ConversationTimelineOptions {
74
75
  cwd?: string;
75
76
  record?: AgentRecord;
77
+ markdownMode?: () => ViewerMarkdownMode;
76
78
  onChange?: (change: ConversationTimelineChange) => void;
77
79
  }
78
80
 
@@ -80,12 +82,60 @@ function safeWidth(width: number): number {
80
82
  return Math.max(1, Math.floor(Number.isFinite(width) ? width : 1));
81
83
  }
82
84
 
85
+ const MARKDOWN_OPTIONS: MarkdownOptions = {
86
+ preserveOrderedListMarkers: true,
87
+ preserveBackslashEscapes: true,
88
+ };
89
+
90
+ function fallbackMarkdownTheme(theme: Theme): MarkdownTheme {
91
+ const sgr = (on: number, off: number) => (text: string) => `\x1b[${on}m${text}\x1b[${off}m`;
92
+ return {
93
+ heading: text => theme.bold(theme.fg("accent", text)),
94
+ link: text => theme.fg("accent", text),
95
+ linkUrl: text => theme.fg("muted", text),
96
+ code: text => theme.fg("muted", text),
97
+ codeBlock: text => theme.fg("muted", text),
98
+ codeBlockBorder: text => theme.fg("dim", text),
99
+ quote: text => theme.fg("muted", text),
100
+ quoteBorder: text => theme.fg("dim", text),
101
+ hr: text => theme.fg("dim", text),
102
+ listBullet: text => theme.fg("accent", text),
103
+ bold: text => theme.bold(text),
104
+ italic: sgr(3, 23),
105
+ underline: sgr(4, 24),
106
+ strikethrough: sgr(9, 29),
107
+ };
108
+ }
109
+
110
+ function resolveMarkdownTheme(theme: Theme): MarkdownTheme {
111
+ try {
112
+ const markdownTheme = getMarkdownTheme();
113
+ markdownTheme.heading("probe");
114
+ return markdownTheme;
115
+ } catch {
116
+ return fallbackMarkdownTheme(theme);
117
+ }
118
+ }
119
+
83
120
  function resultContent(result: ConversationToolResultSnapshot): Array<Record<string, unknown>> {
84
121
  if (Array.isArray(result.content)) return result.content as Array<Record<string, unknown>>;
85
122
  if (typeof result.content === "string") return [{ type: "text", text: result.content }];
86
123
  return [];
87
124
  }
88
125
 
126
+ const RESULT_MAX_CHARS = 16_000;
127
+
128
+ function humanCount(n: number): string {
129
+ if (n < 1_000) return `${n}`;
130
+ const thousands = n < 999_950;
131
+ const value = thousands ? n / 1_000 : n / 1_000_000;
132
+ return `${value.toFixed(1).replace(/\.0$/, "")}${thousands ? "k" : "M"}`;
133
+ }
134
+
135
+ function truncationNote(elided: number): string {
136
+ return `... (truncated, ${humanCount(elided)} more character${elided === 1 ? "" : "s"})`;
137
+ }
138
+
89
139
  function fallbackToolLines(block: ConversationBlock, width: number, expanded = false, hovered = false, theme?: Theme): string[] {
90
140
  const summary = block.toolLine || block.fullText || block.toolName || "tool";
91
141
  const output = block.toolResult
@@ -173,6 +223,8 @@ export class ConversationTimeline implements Component {
173
223
  private lastClickedToolCallId: string | undefined;
174
224
  private lastClickedPoint: { x: number; y: number } | undefined;
175
225
  private toolComponents = new Map<string, Component & { handleMouse?: (event: TuiMouseEvent) => unknown }>();
226
+ private markdownCache = new WeakMap<object, { markdown: Markdown; text: string; failed?: boolean }>();
227
+ private lastWidth = 0;
176
228
  private snapshot: ConversationTimelineSnapshot = {
177
229
  lines: [],
178
230
  messageStarts: [],
@@ -181,7 +233,6 @@ export class ConversationTimeline implements Component {
181
233
  blockSpans: [],
182
234
  toolSpans: [],
183
235
  };
184
- private lastWidth = 0;
185
236
 
186
237
  constructor(
187
238
  private readonly tui: TUI,
@@ -223,6 +274,11 @@ export class ConversationTimeline implements Component {
223
274
  return this.showTools;
224
275
  }
225
276
 
277
+ setMarkdownMode(mode: () => ViewerMarkdownMode): void {
278
+ this.options.markdownMode = mode;
279
+ this.invalidate();
280
+ }
281
+
226
282
  setFocusedToolCallId(id: string | undefined): void {
227
283
  // Focus changes are view state. The rendered rows do not change, so keep
228
284
  // the snapshot and its Markdown/tool output cache intact.
@@ -267,8 +323,6 @@ export class ConversationTimeline implements Component {
267
323
 
268
324
  render(width: number): string[] {
269
325
  const requestedWidth = safeWidth(width);
270
- if (this.lastWidth === requestedWidth) return this.snapshot.lines.map((line) => line.text);
271
-
272
326
  const lines: TimelineRenderLine[] = [];
273
327
  const messageStarts: number[] = [];
274
328
  const messageBlocks: ConversationBlock[] = [];
@@ -323,26 +377,12 @@ export class ConversationTimeline implements Component {
323
377
  continue;
324
378
  }
325
379
 
326
- try {
327
- const markdown = new Markdown(block.markdown || "∅", 2, 0, getMarkdownTheme(), {
328
- color: (text) => this.theme.fg(
329
- block.role === "user" ? "userMessageText" : block.role === "meta" ? "muted" : "text",
330
- text,
331
- ),
332
- });
333
- for (const line of markdown.render(requestedWidth)) {
334
- lines.push({ text: truncateToWidth(line, requestedWidth), plain: stripAnsi(line), blockIndex, railable: true });
335
- }
336
- } catch {
337
- try {
338
- for (const line of new Text(block.markdown || "∅").render(requestedWidth)) {
339
- lines.push({ text: truncateToWidth(line, requestedWidth), plain: stripAnsi(line), blockIndex, railable: true });
340
- }
341
- } catch {
342
- for (const line of (block.markdown || "∅").split("\n")) {
343
- lines.push({ text: truncateToWidth(line, requestedWidth), plain: line, blockIndex, railable: true });
344
- }
345
- }
380
+ const mode = this.options.markdownMode?.() ?? "assistant";
381
+ const rendered = mode === "off"
382
+ ? this.renderLiteralText(block.markdown || "∅", requestedWidth)
383
+ : this.renderMarkdown(block, block.markdown || "∅", requestedWidth, false);
384
+ for (const line of rendered) {
385
+ lines.push({ text: truncateToWidth(line, requestedWidth), plain: stripAnsi(line), blockIndex, railable: true });
346
386
  }
347
387
  blockSpans.push({ blockIndex: snapshotBlockIndex, startLine: blockStartLine, height: lines.length - blockStartLine });
348
388
  }
@@ -353,6 +393,13 @@ export class ConversationTimeline implements Component {
353
393
  }
354
394
 
355
395
  private renderToolLines(block: ConversationBlock, width: number): { rendered: string[]; hintLine: number } {
396
+ const mode = this.options.markdownMode?.() ?? "assistant";
397
+ if (block.toolResult && (mode === "all" || block.toolArguments === undefined)) {
398
+ return {
399
+ rendered: mode === "all" ? this.renderToolMarkdownLines(block, width) : this.renderToolRawLines(block, width),
400
+ hintLine: -1,
401
+ };
402
+ }
356
403
  const id = block.toolCallId;
357
404
  let component: (Component & { handleMouse?: (event: TuiMouseEvent) => unknown }) | undefined;
358
405
  if (id) {
@@ -399,6 +446,73 @@ export class ConversationTimeline implements Component {
399
446
  return { rendered, hintLine };
400
447
  }
401
448
 
449
+ private toolOutputText(block: ConversationBlock): string {
450
+ const output = block.toolResult ? resultContent(block.toolResult)
451
+ .filter(item => item.type === "text" && typeof item.text === "string")
452
+ .map(item => item.text as string)
453
+ .join("\n") : "";
454
+ return output || block.fullText || block.toolLine || "(no output)";
455
+ }
456
+
457
+ private renderToolRawLines(block: ConversationBlock, width: number): string[] {
458
+ const source = this.toolOutputText(block);
459
+ const capped = source.length > RESULT_MAX_CHARS
460
+ ? `${source.slice(0, RESULT_MAX_CHARS)}\n${truncationNote(source.length - RESULT_MAX_CHARS)}`
461
+ : source;
462
+ try {
463
+ return wrapTextWithAnsi(capped, width).map(line => this.theme.fg("dim", truncateToWidth(line, width)));
464
+ } catch {
465
+ return capped.split("\n").map(line => this.theme.fg("dim", truncateToWidth(line, width)));
466
+ }
467
+ }
468
+
469
+ private renderToolMarkdownLines(block: ConversationBlock, width: number): string[] {
470
+ const source = this.toolOutputText(block);
471
+ if (source.length <= RESULT_MAX_CHARS) return this.renderMarkdown(block, source, width, true);
472
+ const prefix = source.slice(0, RESULT_MAX_CHARS);
473
+ return [
474
+ ...this.renderMarkdown(block, prefix, width, true),
475
+ this.theme.fg("dim", truncationNote(source.length - RESULT_MAX_CHARS)),
476
+ ];
477
+ }
478
+
479
+ private renderMarkdown(block: ConversationBlock, text: string, width: number, dim: boolean): string[] {
480
+ let entry = this.markdownCache.get(block);
481
+ if (!entry) {
482
+ const defaultStyle = {
483
+ color: (value: string) => this.theme.fg(
484
+ dim ? "dim" : block.role === "user" ? "userMessageText" : block.role === "meta" ? "muted" : "text",
485
+ value,
486
+ ),
487
+ };
488
+ entry = {
489
+ markdown: new Markdown(text, 0, 0, resolveMarkdownTheme(this.theme), defaultStyle, MARKDOWN_OPTIONS),
490
+ text,
491
+ };
492
+ this.markdownCache.set(block, entry);
493
+ } else if (entry.text !== text) {
494
+ const shouldRetry = !text.startsWith(entry.text);
495
+ entry.markdown.setText(text);
496
+ entry.text = text;
497
+ if (shouldRetry) entry.failed = false;
498
+ }
499
+ if (entry.failed) return dim ? this.renderToolRawLines(block, width) : this.renderLiteralText(text, width);
500
+ try {
501
+ return entry.markdown.render(width).map(line => truncateToWidth(line, width));
502
+ } catch {
503
+ entry.failed = true;
504
+ return dim ? this.renderToolRawLines(block, width) : this.renderLiteralText(text, width);
505
+ }
506
+ }
507
+
508
+ private renderLiteralText(text: string, width: number): string[] {
509
+ try {
510
+ return wrapTextWithAnsi(text || "∅", width).map(line => truncateToWidth(line, width));
511
+ } catch {
512
+ return text.split("\n").map(line => truncateToWidth(line, width));
513
+ }
514
+ }
515
+
402
516
  /**
403
517
  * Hover only changes styling in a tool card. Re-render those rows in place
404
518
  * and leave the Markdown rows and their spans untouched.