@oh-my-pi/pi-coding-agent 9.6.0 → 9.6.3

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 (41) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/docs/theme.md +9 -12
  3. package/package.json +7 -7
  4. package/src/exa/render.ts +12 -12
  5. package/src/ipy/gateway-coordinator.ts +1 -1
  6. package/src/ipy/kernel.ts +5 -0
  7. package/src/ipy/runtime.ts +2 -6
  8. package/src/lsp/render.ts +16 -30
  9. package/src/modes/components/bash-execution.ts +2 -4
  10. package/src/modes/components/custom-message.ts +1 -1
  11. package/src/modes/components/footer.ts +1 -1
  12. package/src/modes/components/history-search.ts +3 -2
  13. package/src/modes/components/hook-message.ts +1 -1
  14. package/src/modes/components/python-execution.ts +2 -4
  15. package/src/modes/components/read-tool-group.ts +1 -1
  16. package/src/modes/components/session-selector.ts +7 -11
  17. package/src/modes/components/status-line/segments.ts +1 -1
  18. package/src/modes/components/status-line.ts +1 -1
  19. package/src/modes/components/tool-execution.ts +3 -3
  20. package/src/modes/components/ttsr-notification.ts +1 -1
  21. package/src/modes/components/welcome.ts +2 -2
  22. package/src/modes/controllers/event-controller.ts +3 -3
  23. package/src/modes/interactive-mode.ts +1 -1
  24. package/src/modes/theme/theme.ts +0 -11
  25. package/src/patch/shared.ts +7 -10
  26. package/src/task/executor.ts +1 -1
  27. package/src/task/render.ts +31 -33
  28. package/src/tools/bash.ts +3 -3
  29. package/src/tools/calculator.ts +3 -3
  30. package/src/tools/fetch.ts +4 -6
  31. package/src/tools/python.ts +22 -31
  32. package/src/tools/read.ts +1 -1
  33. package/src/tools/render-utils.ts +12 -21
  34. package/src/tools/review.ts +1 -7
  35. package/src/tools/ssh.ts +6 -8
  36. package/src/tools/write.ts +5 -5
  37. package/src/tui/code-cell.ts +2 -2
  38. package/src/tui/output-block.ts +2 -2
  39. package/src/tui/tree-list.ts +1 -3
  40. package/src/tui/utils.ts +3 -5
  41. package/src/web/search/render.ts +14 -24
package/src/tools/read.ts CHANGED
@@ -1003,7 +1003,7 @@ export const readToolRenderer = {
1003
1003
  const offset = args.offset;
1004
1004
  const limit = args.limit;
1005
1005
 
1006
- let pathDisplay = filePath || uiTheme.format.ellipsis;
1006
+ let pathDisplay = filePath || "…";
1007
1007
  if (offset !== undefined || limit !== undefined) {
1008
1008
  const startLine = offset ?? 1;
1009
1009
  const endLine = limit !== undefined ? startLine + limit - 1 : "";
@@ -5,9 +5,12 @@
5
5
  * tool renderers to ensure a unified TUI experience.
6
6
  */
7
7
  import * as os from "node:os";
8
+ import { type Ellipsis, truncateToWidth } from "@oh-my-pi/pi-tui";
8
9
  import type { Theme } from "../modes/theme/theme";
9
10
  import { getTreeBranch } from "../tui/utils";
10
11
 
12
+ export { Ellipsis, truncateToWidth } from "@oh-my-pi/pi-tui";
13
+
11
14
  // =============================================================================
12
15
  // Standardized Display Constants
13
16
  // =============================================================================
@@ -47,22 +50,12 @@ export const EXPAND_HINT = "(Ctrl+O for more)";
47
50
  // Text Truncation Utilities
48
51
  // =============================================================================
49
52
 
50
- /**
51
- * Truncate text to max length with ellipsis.
52
- * The most commonly duplicated utility across renderers.
53
- */
54
- export function truncate(text: string, maxLen: number, ellipsis: string): string {
55
- if (text.length <= maxLen) return text;
56
- const sliceLen = Math.max(0, maxLen - ellipsis.length);
57
- return `${text.slice(0, sliceLen)}${ellipsis}`;
58
- }
59
-
60
53
  /**
61
54
  * Get first N lines of text as preview, with each line truncated.
62
55
  */
63
- export function getPreviewLines(text: string, maxLines: number, maxLineLen: number, ellipsis: string): string[] {
56
+ export function getPreviewLines(text: string, maxLines: number, maxLineLen: number, ellipsis?: Ellipsis): string[] {
64
57
  const lines = text.split("\n").filter(l => l.trim());
65
- return lines.slice(0, maxLines).map(l => truncate(l.trim(), maxLineLen, ellipsis));
58
+ return lines.slice(0, maxLines).map(l => truncateToWidth(l.trim(), maxLineLen, ellipsis));
66
59
  }
67
60
 
68
61
  // =============================================================================
@@ -194,9 +187,9 @@ export function formatBadge(label: string, color: ToolUIColor, theme: Theme): st
194
187
  * Build a "more items" suffix line for truncated lists.
195
188
  * Uses consistent wording pattern.
196
189
  */
197
- export function formatMoreItems(remaining: number, itemType: string, theme: Theme): string {
190
+ export function formatMoreItems(remaining: number, itemType: string): string {
198
191
  const safeRemaining = Number.isFinite(remaining) ? remaining : 0;
199
- return `${theme.format.ellipsis} ${safeRemaining} more ${pluralize(itemType, safeRemaining)}`;
192
+ return `… ${safeRemaining} more ${pluralize(itemType, safeRemaining)}`;
200
193
  }
201
194
 
202
195
  export function formatMeta(meta: string[], theme: Theme): string {
@@ -252,7 +245,7 @@ export class ToolUIKit {
252
245
  }
253
246
 
254
247
  moreItems(remaining: number, itemType: string): string {
255
- return formatMoreItems(remaining, itemType, this.theme);
248
+ return formatMoreItems(remaining, itemType);
256
249
  }
257
250
 
258
251
  expandHint(expanded: boolean, hasMore: boolean): string {
@@ -288,11 +281,11 @@ export class ToolUIKit {
288
281
  }
289
282
 
290
283
  truncate(text: string, maxLen: number): string {
291
- return truncate(text, maxLen, this.theme.format.ellipsis);
284
+ return truncateToWidth(text, maxLen);
292
285
  }
293
286
 
294
287
  previewLines(text: string, maxLines: number, maxLineLen: number): string[] {
295
- return getPreviewLines(text, maxLines, maxLineLen, this.theme.format.ellipsis);
288
+ return getPreviewLines(text, maxLines, maxLineLen);
296
289
  }
297
290
 
298
291
  formatBytes(bytes: number): string {
@@ -457,7 +450,7 @@ export function formatDiagnostics(
457
450
  const remaining = totalDiags - diagsShown;
458
451
  output += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
459
452
  "muted",
460
- `${theme.format.ellipsis} ${remaining} more`,
453
+ `… ${remaining} more`,
461
454
  )} ${formatExpandHint(theme)}`;
462
455
  }
463
456
 
@@ -697,9 +690,7 @@ export function renderTreeList<T>(
697
690
 
698
691
  if (!expanded && items.length > maxCollapsed) {
699
692
  const remaining = items.length - maxCollapsed;
700
- lines.push(
701
- ` ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", formatMoreItems(remaining, itemType, theme))}`,
702
- );
693
+ lines.push(` ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", formatMoreItems(remaining, itemType))}`);
703
694
  }
704
695
 
705
696
  return lines;
@@ -181,13 +181,7 @@ subprocessToolRegistry.register<ReportFindingDetails>("report_finding", {
181
181
  }
182
182
 
183
183
  if (allData.length > displayCount) {
184
- container.addChild(
185
- new Text(
186
- theme.fg("dim", ` ${theme.format.ellipsis} ${allData.length - displayCount} more findings`),
187
- 0,
188
- 0,
189
- ),
190
- );
184
+ container.addChild(new Text(theme.fg("dim", ` … ${allData.length - displayCount} more findings`), 0, 0));
191
185
  }
192
186
 
193
187
  return container;
package/src/tools/ssh.ts CHANGED
@@ -237,8 +237,8 @@ interface SshRenderContext {
237
237
 
238
238
  export const sshToolRenderer = {
239
239
  renderCall(args: SshRenderArgs, uiTheme: Theme): Component {
240
- const host = args.host || uiTheme.format.ellipsis;
241
- const command = args.command || uiTheme.format.ellipsis;
240
+ const host = args.host || "…";
241
+ const command = args.command || "…";
242
242
  const text = renderStatusLine({ icon: "pending", title: "SSH", description: `[${host}] $ ${command}` }, uiTheme);
243
243
  return new Text(text, 0, 0);
244
244
  },
@@ -254,8 +254,8 @@ export const sshToolRenderer = {
254
254
  ): Component {
255
255
  const { expanded, renderContext } = options;
256
256
  const details = result.details;
257
- const host = args?.host || uiTheme.format.ellipsis;
258
- const command = args?.command || uiTheme.format.ellipsis;
257
+ const host = args?.host || "…";
258
+ const command = args?.command || "…";
259
259
  const header = renderStatusLine(
260
260
  { icon: "success", title: "SSH", description: `[${host}] $ ${command}` },
261
261
  uiTheme,
@@ -274,7 +274,7 @@ export const sshToolRenderer = {
274
274
  outputLines.push(
275
275
  uiTheme.fg(
276
276
  "dim",
277
- `${uiTheme.format.ellipsis} (${skippedCount} earlier lines, showing ${visualLines.length} of ${totalVisualLines}) (ctrl+o to expand)`,
277
+ `… (${skippedCount} earlier lines, showing ${visualLines.length} of ${totalVisualLines}) (ctrl+o to expand)`,
278
278
  ),
279
279
  );
280
280
  }
@@ -289,9 +289,7 @@ export const sshToolRenderer = {
289
289
  const remaining = outputLinesRaw.length - maxLines;
290
290
  outputLines.push(...displayLines.map(line => uiTheme.fg("toolOutput", line)));
291
291
  if (remaining > 0) {
292
- outputLines.push(
293
- uiTheme.fg("dim", `${uiTheme.format.ellipsis} (${remaining} more lines) (ctrl+o to expand)`),
294
- );
292
+ outputLines.push(uiTheme.fg("dim", `… (${remaining} more lines) (ctrl+o to expand)`));
295
293
  }
296
294
  }
297
295
  }
@@ -155,12 +155,12 @@ function formatStreamingContent(content: string, uiTheme: Theme, ui: ToolUIKit):
155
155
 
156
156
  let text = "\n\n";
157
157
  if (hidden > 0) {
158
- text += uiTheme.fg("dim", `${uiTheme.format.ellipsis} (${hidden} earlier lines)\n`);
158
+ text += uiTheme.fg("dim", `… (${hidden} earlier lines)\n`);
159
159
  }
160
160
  for (const line of displayLines) {
161
161
  text += `${uiTheme.fg("toolOutput", ui.truncate(line, 80))}\n`;
162
162
  }
163
- text += uiTheme.fg("dim", `${uiTheme.format.ellipsis} (streaming)`);
163
+ text += uiTheme.fg("dim", `… (streaming)`);
164
164
  return text;
165
165
  }
166
166
 
@@ -177,7 +177,7 @@ function renderContentPreview(content: string, expanded: boolean, uiTheme: Theme
177
177
  }
178
178
  if (!expanded && hidden > 0) {
179
179
  const hint = formatExpandHint(uiTheme, expanded, hidden > 0);
180
- const moreLine = `${formatMoreItems(hidden, "line", uiTheme)}${hint ? ` ${hint}` : ""}`;
180
+ const moreLine = `${formatMoreItems(hidden, "line")}${hint ? ` ${hint}` : ""}`;
181
181
  text += uiTheme.fg("dim", moreLine);
182
182
  }
183
183
  return text;
@@ -190,7 +190,7 @@ export const writeToolRenderer = {
190
190
  const filePath = shortenPath(rawPath);
191
191
  const lang = getLanguageFromPath(rawPath) ?? "text";
192
192
  const langIcon = uiTheme.fg("muted", uiTheme.getLangIcon(lang));
193
- const pathDisplay = filePath ? uiTheme.fg("accent", filePath) : uiTheme.fg("toolOutput", uiTheme.format.ellipsis);
193
+ const pathDisplay = filePath ? uiTheme.fg("accent", filePath) : uiTheme.fg("toolOutput", "…");
194
194
  const spinner =
195
195
  options?.spinnerFrame !== undefined ? formatStatusIcon("running", uiTheme, options.spinnerFrame) : "";
196
196
 
@@ -218,7 +218,7 @@ export const writeToolRenderer = {
218
218
  const fileContent = args?.content || "";
219
219
  const lang = getLanguageFromPath(rawPath);
220
220
  const langIcon = uiTheme.fg("muted", uiTheme.getLangIcon(lang));
221
- const pathDisplay = filePath ? uiTheme.fg("accent", filePath) : uiTheme.fg("toolOutput", uiTheme.format.ellipsis);
221
+ const pathDisplay = filePath ? uiTheme.fg("accent", filePath) : uiTheme.fg("toolOutput", "…");
222
222
  const lineCount = countLines(fileContent);
223
223
 
224
224
  // Build header with status icon
@@ -80,7 +80,7 @@ export function renderCodeCell(options: CodeCellOptions, theme: Theme): string[]
80
80
  const hiddenCodeLines = rawCodeLines.length - codeLines.length;
81
81
  if (hiddenCodeLines > 0) {
82
82
  const hint = formatExpandHint(theme, expanded, hiddenCodeLines > 0);
83
- const moreLine = `${formatMoreItems(hiddenCodeLines, "line", theme)}${hint ? ` ${hint}` : ""}`;
83
+ const moreLine = `${formatMoreItems(hiddenCodeLines, "line")}${hint ? ` ${hint}` : ""}`;
84
84
  codeLines.push(theme.fg("dim", moreLine));
85
85
  }
86
86
 
@@ -95,7 +95,7 @@ export function renderCodeCell(options: CodeCellOptions, theme: Theme): string[]
95
95
  const remaining = rawLines.length - maxLines;
96
96
  if (remaining > 0) {
97
97
  const hint = formatExpandHint(theme, expanded, remaining > 0);
98
- const moreLine = `${formatMoreItems(remaining, "line", theme)}${hint ? ` ${hint}` : ""}`;
98
+ const moreLine = `${formatMoreItems(remaining, "line")}${hint ? ` ${hint}` : ""}`;
99
99
  outputLines.push(theme.fg("dim", moreLine));
100
100
  }
101
101
  }
@@ -42,7 +42,7 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
42
42
  const leftWidth = visibleWidth(left);
43
43
  const rightWidth = visibleWidth(right);
44
44
  const maxLabelWidth = Math.max(0, lineWidth - leftWidth - rightWidth);
45
- const trimmedLabel = truncateToWidth(rawLabel, maxLabelWidth, theme.format.ellipsis);
45
+ const trimmedLabel = truncateToWidth(rawLabel, maxLabelWidth);
46
46
  const labelWidth = visibleWidth(trimmedLabel);
47
47
  const fillCount = Math.max(0, lineWidth - leftWidth - labelWidth - rightWidth);
48
48
  return `${left}${trimmedLabel}${border(h.repeat(fillCount))}${right}`;
@@ -69,7 +69,7 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
69
69
  }
70
70
  const allLines = section.lines.flatMap(l => l.split("\n"));
71
71
  for (const line of allLines) {
72
- const text = truncateToWidth(line, contentWidth, theme.format.ellipsis);
72
+ const text = truncateToWidth(line, contentWidth);
73
73
  const innerPadding = padding(Math.max(0, contentWidth - visibleWidth(text)));
74
74
  const fullLine = `${contentPrefix}${text}${innerPadding}${contentSuffix}`;
75
75
  lines.push(padToWidth(fullLine, lineWidth, bgFn));
@@ -46,9 +46,7 @@ export function renderTreeList<T>(options: TreeListOptions<T>, theme: Theme): st
46
46
 
47
47
  if (!expanded && items.length > maxItems) {
48
48
  const remaining = items.length - maxItems;
49
- lines.push(
50
- `${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", formatMoreItems(remaining, itemType, theme))}`,
51
- );
49
+ lines.push(`${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", formatMoreItems(remaining, itemType))}`);
52
50
  }
53
51
 
54
52
  return lines;
package/src/tui/utils.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * Shared helpers for tool-rendered UI components.
3
3
  */
4
- import { padding, truncateToWidth as truncateToWidthBase, visibleWidth } from "@oh-my-pi/pi-tui";
4
+ import { padding, visibleWidth } from "@oh-my-pi/pi-tui";
5
5
  import type { Theme, ThemeBg } from "../modes/theme/theme";
6
6
  import type { IconType, State } from "./types";
7
7
 
8
+ export { truncateToWidth } from "@oh-my-pi/pi-tui";
9
+
8
10
  export function buildTreePrefix(ancestors: boolean[], theme: Theme): string {
9
11
  return ancestors.map(hasNext => (hasNext ? `${theme.tree.vertical} ` : " ")).join("");
10
12
  }
@@ -17,10 +19,6 @@ export function getTreeContinuePrefix(isLast: boolean, theme: Theme): string {
17
19
  return isLast ? " " : `${theme.tree.vertical} `;
18
20
  }
19
21
 
20
- export function truncateToWidth(text: string, width: number, ellipsis: string): string {
21
- return truncateToWidthBase(text, width, ellipsis);
22
- }
23
-
24
22
  export function padToWidth(text: string, width: number, bgFn?: (s: string) => string): string {
25
23
  if (width <= 0) return bgFn ? bgFn(text) : text;
26
24
  const paddingNeeded = Math.max(0, width - visibleWidth(text));
@@ -17,7 +17,7 @@ import {
17
17
  getPreviewLines,
18
18
  PREVIEW_LIMITS,
19
19
  TRUNCATE_LENGTHS,
20
- truncate,
20
+ truncateToWidth,
21
21
  } from "../../tools/render-utils";
22
22
  import { renderOutputBlock, renderStatusLine, renderTreeList } from "../../tui";
23
23
  import type { WebSearchResponse } from "./types";
@@ -35,7 +35,7 @@ const MAX_REQUEST_ID_LEN = 36;
35
35
  function renderFallbackText(contentText: string, expanded: boolean, theme: Theme): Component {
36
36
  const lines = contentText.split("\n").filter(line => line.trim());
37
37
  const maxLines = expanded ? lines.length : 6;
38
- const displayLines = lines.slice(0, maxLines).map(line => truncate(line.trim(), 110, theme.format.ellipsis));
38
+ const displayLines = lines.slice(0, maxLines).map(line => truncateToWidth(line.trim(), 110));
39
39
  const remaining = lines.length - displayLines.length;
40
40
 
41
41
  const headerIcon = formatStatusIcon("warning", theme);
@@ -54,7 +54,7 @@ function renderFallbackText(contentText: string, expanded: boolean, theme: Theme
54
54
  }
55
55
 
56
56
  if (!expanded && remaining > 0) {
57
- text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", formatMoreItems(remaining, "line", theme))}`;
57
+ text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", formatMoreItems(remaining, "line"))}`;
58
58
  }
59
59
 
60
60
  return new Text(text, 0, 0);
@@ -104,9 +104,7 @@ export function renderWebSearchResult(
104
104
  const contentText = answerText || rawText;
105
105
  const totalAnswerLines = contentText ? contentText.split("\n").filter(l => l.trim()).length : 0;
106
106
  const answerLimit = expanded ? MAX_EXPANDED_ANSWER_LINES : MAX_COLLAPSED_ANSWER_LINES;
107
- const answerPreview = contentText
108
- ? getPreviewLines(contentText, answerLimit, MAX_ANSWER_LINE_LEN, theme.format.ellipsis)
109
- : [];
107
+ const answerPreview = contentText ? getPreviewLines(contentText, answerLimit, MAX_ANSWER_LINE_LEN) : [];
110
108
 
111
109
  const providerLabel =
112
110
  provider === "anthropic"
@@ -117,9 +115,9 @@ export function renderWebSearchResult(
117
115
  ? "Exa"
118
116
  : "Unknown";
119
117
  const queryPreview = args?.query
120
- ? truncate(args.query, 80, theme.format.ellipsis)
118
+ ? truncateToWidth(args.query, 80)
121
119
  : searchQueries[0]
122
- ? truncate(searchQueries[0], 80, theme.format.ellipsis)
120
+ ? truncateToWidth(searchQueries[0], 80)
123
121
  : undefined;
124
122
  const header = renderStatusLine(
125
123
  {
@@ -144,7 +142,7 @@ export function renderWebSearchResult(
144
142
  theme,
145
143
  );
146
144
  if (remainingAnswer > 0) {
147
- answerTree.push(theme.fg("muted", formatMoreItems(remainingAnswer, "line", theme)));
145
+ answerTree.push(theme.fg("muted", formatMoreItems(remainingAnswer, "line")));
148
146
  }
149
147
 
150
148
  const sourceTree = renderTreeList(
@@ -160,7 +158,7 @@ export function renderWebSearchResult(
160
158
  : typeof src.url === "string" && src.url.trim()
161
159
  ? src.url
162
160
  : "Untitled";
163
- const title = truncate(titleText, 70, theme.format.ellipsis);
161
+ const title = truncateToWidth(titleText, 70);
164
162
  const url = typeof src.url === "string" ? src.url : "";
165
163
  const domain = url ? getDomain(url) : "";
166
164
  const age = formatAge(src.ageSeconds) || (typeof src.publishedDate === "string" ? src.publishedDate : "");
@@ -173,12 +171,7 @@ export function renderWebSearchResult(
173
171
  const lines: string[] = [`${theme.fg("accent", title)}${metaSuffix}`];
174
172
  const snippetText = typeof src.snippet === "string" ? src.snippet : "";
175
173
  if (snippetText.trim()) {
176
- const snippetLines = getPreviewLines(
177
- snippetText,
178
- MAX_SNIPPET_LINES,
179
- MAX_SNIPPET_LINE_LEN,
180
- theme.format.ellipsis,
181
- );
174
+ const snippetLines = getPreviewLines(snippetText, MAX_SNIPPET_LINES, MAX_SNIPPET_LINE_LEN);
182
175
  for (const snippetLine of snippetLines) {
183
176
  lines.push(theme.fg("muted", `${theme.format.dash} ${snippetLine}`));
184
177
  }
@@ -202,7 +195,7 @@ export function renderWebSearchResult(
202
195
  theme,
203
196
  );
204
197
  if (!expanded && relatedCount > MAX_COLLAPSED_ITEMS) {
205
- relatedTree.push(theme.fg("muted", formatMoreItems(relatedCount - MAX_COLLAPSED_ITEMS, "question", theme)));
198
+ relatedTree.push(theme.fg("muted", formatMoreItems(relatedCount - MAX_COLLAPSED_ITEMS, "question")));
206
199
  }
207
200
 
208
201
  const metaLines: string[] = [];
@@ -223,16 +216,13 @@ export function renderWebSearchResult(
223
216
  }
224
217
  if (response.requestId) {
225
218
  metaLines.push(
226
- `${theme.fg("muted", "Request:")} ${theme.fg(
227
- "text",
228
- truncate(response.requestId, MAX_REQUEST_ID_LEN, theme.format.ellipsis),
229
- )}`,
219
+ `${theme.fg("muted", "Request:")} ${theme.fg("text", truncateToWidth(response.requestId, MAX_REQUEST_ID_LEN))}`,
230
220
  );
231
221
  }
232
222
  if (searchQueries.length > 0) {
233
223
  const queriesPreview = searchQueries.slice(0, MAX_QUERY_PREVIEW);
234
- const queryList = queriesPreview.map(q => truncate(q, MAX_QUERY_LEN, theme.format.ellipsis));
235
- const suffix = searchQueries.length > queriesPreview.length ? theme.format.ellipsis : "";
224
+ const queryList = queriesPreview.map(q => truncateToWidth(q, MAX_QUERY_LEN));
225
+ const suffix = searchQueries.length > queriesPreview.length ? "…" : "";
236
226
  metaLines.push(`${theme.fg("muted", "Queries:")} ${theme.fg("text", queryList.join("; "))}${suffix}`);
237
227
  }
238
228
 
@@ -274,7 +264,7 @@ export function renderWebSearchCall(
274
264
  theme: Theme,
275
265
  ): Component {
276
266
  const provider = args.provider ?? "auto";
277
- const query = truncate(args.query, 80, theme.format.ellipsis);
267
+ const query = truncateToWidth(args.query, 80);
278
268
  const text = renderStatusLine({ icon: "pending", title: "Web Search", description: query, meta: [provider] }, theme);
279
269
  return new Text(text, 0, 0);
280
270
  }