@linxiraos/pi-tui 1.2.1 → 1.3.1
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.
- package/CHANGELOG.md +245 -224
- package/README.md +36 -0
- package/THIRD-PARTY-NOTICES.txt +55 -5
- package/dist/types/app-keybindings.d.ts +18 -8
- package/dist/types/apps/git/diff-pane.d.ts +5 -0
- package/dist/types/apps/git/git-tui.d.ts +16 -2
- package/dist/types/apps/git/state.d.ts +11 -0
- package/dist/types/autocomplete.d.ts +2 -0
- package/dist/types/charts/chart-svg.d.ts +7 -13
- package/dist/types/chat/advisor-message.d.ts +4 -1
- package/dist/types/chat/assistant-message.d.ts +9 -4
- package/dist/types/chat/figure-markdown.d.ts +7 -2
- package/dist/types/chat/messages.d.ts +3 -1
- package/dist/types/chat/read-tool-group.d.ts +2 -2
- package/dist/types/chat/table-chart.d.ts +11 -15
- package/dist/types/chat/tool-execution.d.ts +32 -20
- package/dist/types/chat/user-message.d.ts +3 -2
- package/dist/types/chrome/countdown-timer.d.ts +3 -2
- package/dist/types/chrome/diff.d.ts +2 -3
- package/dist/types/chrome/status-notice.d.ts +2 -0
- package/dist/types/chrome/transcript-container.d.ts +2 -0
- package/dist/types/components/image.d.ts +2 -0
- package/dist/types/components/markdown.d.ts +49 -3
- package/dist/types/components/text.d.ts +2 -0
- package/dist/types/debug-server.d.ts +2 -0
- package/dist/types/keybinding-matchers.d.ts +6 -0
- package/dist/types/multiplexers/tmux.d.ts +1 -0
- package/dist/types/multiplexers/types.d.ts +6 -0
- package/dist/types/native/backend.d.ts +5 -4
- package/dist/types/native/blobs.d.ts +24 -12
- package/dist/types/overlays/agent-hub-projection.d.ts +10 -5
- package/dist/types/overlays/annotation-overlay.d.ts +19 -0
- package/dist/types/overlays/approval-dialog.d.ts +79 -0
- package/dist/types/overlays/ask-dialog.d.ts +11 -6
- package/dist/types/overlays/btw-history-panel.d.ts +2 -6
- package/dist/types/overlays/hook-editor.d.ts +7 -2
- package/dist/types/overlays/hook-input.d.ts +12 -4
- package/dist/types/overlays/job-tree.d.ts +50 -0
- package/dist/types/overlays/jobs-panel.d.ts +25 -11
- package/dist/types/overlays/login-dialog.d.ts +8 -3
- package/dist/types/overlays/mcp-add-wizard.d.ts +9 -1
- package/dist/types/overlays/model-browser.d.ts +6 -1
- package/dist/types/overlays/model-picker.d.ts +2 -0
- package/dist/types/overlays/pause-screen.d.ts +5 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +4 -0
- package/dist/types/overlays/report-panel.d.ts +5 -1
- package/dist/types/overlays/reset-usage-selector.d.ts +7 -5
- package/dist/types/overlays/session-account-selector.d.ts +4 -0
- package/dist/types/overlays/session-info-overlay.d.ts +7 -3
- package/dist/types/overlays/session-selector.d.ts +3 -1
- package/dist/types/overlays/usage-dashboard.d.ts +6 -6
- package/dist/types/prompt/composer.d.ts +23 -20
- package/dist/types/prompt/custom-editor.d.ts +20 -10
- package/dist/types/prompt/image-references.d.ts +22 -0
- package/dist/types/prompt/internal-url-autocomplete.d.ts +1 -0
- package/dist/types/prompt/macos-spelling.d.ts +2 -1
- package/dist/types/prompt/prose-gate.d.ts +3 -0
- package/dist/types/prompt/queued-messages.d.ts +1 -1
- package/dist/types/prompt/typing-word.d.ts +25 -0
- package/dist/types/prompt/welcome.d.ts +26 -6
- package/dist/types/render/hanging-wrap.d.ts +7 -0
- package/dist/types/render/hyperlink.d.ts +2 -0
- package/dist/types/render/render-utils.d.ts +9 -1
- package/dist/types/render/utils.d.ts +1 -1
- package/dist/types/setup/scenes/splash.d.ts +6 -5
- package/dist/types/status-line/context-usage.d.ts +4 -1
- package/dist/types/status-line/host.d.ts +6 -4
- package/dist/types/status-line/startup.d.ts +6 -6
- package/dist/types/status-line/types.d.ts +3 -0
- package/dist/types/stdin-buffer.d.ts +7 -0
- package/dist/types/terminal-capabilities.d.ts +16 -1
- package/dist/types/terminal-multiplexer.d.ts +7 -0
- package/dist/types/terminal.d.ts +8 -4
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme.d.ts +11 -0
- package/dist/types/thinking.d.ts +5 -0
- package/dist/types/tmux.d.ts +39 -4
- package/dist/types/tools/ast-edit.d.ts +2 -2
- package/dist/types/tools/ast-grep.d.ts +2 -2
- package/dist/types/tools/bash.d.ts +6 -1
- package/dist/types/tools/edit.d.ts +2 -0
- package/dist/types/tools/eval.d.ts +53 -4
- package/dist/types/tools/find.d.ts +14 -4
- package/dist/types/tools/glob.d.ts +3 -3
- package/dist/types/tools/grep.d.ts +2 -2
- package/dist/types/tools/grouped-file-output.d.ts +9 -4
- package/dist/types/tools/native-view.d.ts +1 -1
- package/dist/types/tools/read.d.ts +32 -10
- package/dist/types/tools/renderer.d.ts +2 -0
- package/dist/types/tools/task.d.ts +31 -0
- package/dist/types/tools/web-search-types.d.ts +1 -0
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tui.d.ts +20 -3
- package/package.json +10 -10
- package/src/app-keybindings.d.ts +18 -8
- package/src/app-keybindings.ts +20 -8
- package/src/apps/autoresearch-dashboard.ts +444 -27
- package/src/apps/debug/raw-sse.ts +2 -2
- package/src/apps/git/diff-pane.ts +39 -41
- package/src/apps/git/git-tui.ts +80 -14
- package/src/apps/git/sidebar.ts +10 -1
- package/src/apps/git/state.ts +12 -0
- package/src/autocomplete.d.ts +2 -0
- package/src/autocomplete.ts +21 -19
- package/src/bracketed-paste.ts +9 -2
- package/src/charts/chart-svg.d.ts +7 -13
- package/src/charts/chart-svg.ts +19 -22
- package/src/charts/table-data.ts +17 -6
- package/src/chat/advisor-message.d.ts +4 -1
- package/src/chat/advisor-message.ts +77 -24
- package/src/chat/assistant-message.d.ts +9 -4
- package/src/chat/assistant-message.ts +127 -31
- package/src/chat/bash-execution.ts +7 -2
- package/src/chat/figure-markdown.d.ts +7 -2
- package/src/chat/figure-markdown.ts +10 -3
- package/src/chat/image-loading.ts +9 -6
- package/src/chat/messages.d.ts +3 -1
- package/src/chat/messages.ts +4 -1
- package/src/chat/read-tool-group.d.ts +2 -2
- package/src/chat/read-tool-group.ts +97 -42
- package/src/chat/svg-figure.ts +3 -1
- package/src/chat/svg-source.ts +6 -3
- package/src/chat/table-chart.d.ts +11 -15
- package/src/chat/table-chart.ts +24 -57
- package/src/chat/tool-execution.d.ts +32 -20
- package/src/chat/tool-execution.ts +103 -36
- package/src/chat/user-message.d.ts +3 -2
- package/src/chat/user-message.ts +54 -34
- package/src/chrome/countdown-timer.ts +3 -3
- package/src/chrome/diff.d.ts +2 -3
- package/src/chrome/diff.ts +4 -9
- package/src/chrome/status-notice.ts +5 -0
- package/src/chrome/transcript-container.d.ts +2 -0
- package/src/chrome/transcript-container.ts +212 -28
- package/src/components/editor.ts +12 -12
- package/src/components/image.d.ts +2 -0
- package/src/components/image.ts +54 -20
- package/src/components/markdown.d.ts +49 -3
- package/src/components/markdown.ts +482 -12
- package/src/components/settings-list.ts +2 -4
- package/src/components/text.d.ts +2 -0
- package/src/components/text.ts +11 -1
- package/src/debug-server.d.ts +2 -0
- package/src/debug-server.ts +51 -69
- package/src/hotkeys-markdown.ts +2 -0
- package/src/keybinding-matchers.d.ts +6 -0
- package/src/keybinding-matchers.ts +19 -0
- package/src/multiplexers/tmux.d.ts +1 -0
- package/src/multiplexers/tmux.ts +3 -0
- package/src/multiplexers/types.d.ts +6 -0
- package/src/multiplexers/types.ts +6 -0
- package/src/native/backend.d.ts +5 -4
- package/src/native/backend.ts +13 -5
- package/src/native/blobs.d.ts +24 -12
- package/src/native/blobs.ts +56 -7
- package/src/native/reconcile.ts +43 -21
- package/src/overlays/advisor-config.ts +259 -8
- package/src/overlays/agent-hub-projection.d.ts +10 -5
- package/src/overlays/agent-hub-projection.ts +63 -12
- package/src/overlays/annotation-overlay.ts +422 -168
- package/src/overlays/approval-dialog.ts +389 -0
- package/src/overlays/ask-dialog.ts +244 -105
- package/src/overlays/btw-history-panel.ts +208 -147
- package/src/overlays/extensions/extension-list.ts +4 -6
- package/src/overlays/hook-editor.ts +90 -32
- package/src/overlays/hook-input.ts +105 -19
- package/src/overlays/job-tree.ts +177 -0
- package/src/overlays/jobs-panel.ts +222 -142
- package/src/overlays/login-dialog.ts +201 -91
- package/src/overlays/mcp-add-wizard.ts +562 -279
- package/src/overlays/model-browser.ts +32 -7
- package/src/overlays/model-hub.ts +4 -10
- package/src/overlays/model-picker.ts +58 -21
- package/src/overlays/pause-screen.ts +81 -23
- package/src/overlays/plan-review-overlay.ts +16 -0
- package/src/overlays/plugin-selector.ts +17 -1
- package/src/overlays/queue-mode-selector.ts +10 -0
- package/src/overlays/report-panel.ts +6 -2
- package/src/overlays/reset-usage-selector.ts +130 -21
- package/src/overlays/session-account-selector.ts +36 -0
- package/src/overlays/session-info-overlay.ts +199 -76
- package/src/overlays/session-selector.ts +15 -10
- package/src/overlays/settings-selector.ts +3 -0
- package/src/overlays/show-images-selector.ts +1 -0
- package/src/overlays/snapcompact-shape-preview.ts +13 -6
- package/src/overlays/theme-selector.ts +6 -1
- package/src/overlays/thinking-selector.ts +8 -4
- package/src/overlays/tree-selector.ts +117 -2
- package/src/overlays/usage-dashboard.ts +498 -144
- package/src/prompt/composer-cache.ts +4 -1
- package/src/prompt/composer.ts +200 -74
- package/src/prompt/custom-editor.ts +168 -65
- package/src/prompt/image-references.d.ts +22 -0
- package/src/prompt/image-references.ts +79 -2
- package/src/prompt/internal-url-autocomplete.ts +1 -0
- package/src/prompt/macos-spelling.ts +28 -5
- package/src/prompt/prose-gate.ts +3 -0
- package/src/prompt/queued-messages.ts +9 -2
- package/src/prompt/tips.txt +1 -2
- package/src/prompt/typing-word.ts +59 -0
- package/src/prompt/welcome.ts +97 -45
- package/src/render/code-cell.ts +18 -5
- package/src/render/hanging-wrap.d.ts +7 -0
- package/src/render/hanging-wrap.ts +58 -0
- package/src/render/hyperlink.d.ts +2 -0
- package/src/render/hyperlink.ts +5 -0
- package/src/render/render-utils.d.ts +9 -1
- package/src/render/render-utils.ts +16 -2
- package/src/render/utils.d.ts +1 -1
- package/src/render/utils.ts +1 -1
- package/src/render/width-aware-text.ts +6 -5
- package/src/setup/scenes/splash.ts +22 -8
- package/src/status-line/component.ts +79 -11
- package/src/status-line/context-usage.ts +176 -73
- package/src/status-line/footer.ts +18 -35
- package/src/status-line/host.ts +6 -1
- package/src/status-line/segments.ts +16 -5
- package/src/status-line/startup.ts +6 -6
- package/src/status-line/types.ts +1 -0
- package/src/stdin-buffer.d.ts +7 -0
- package/src/stdin-buffer.ts +300 -62
- package/src/terminal-capabilities.d.ts +16 -1
- package/src/terminal-capabilities.ts +66 -28
- package/src/terminal-multiplexer.d.ts +7 -0
- package/src/terminal-multiplexer.ts +14 -0
- package/src/terminal.d.ts +8 -4
- package/src/terminal.ts +190 -63
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +1 -0
- package/src/theme/theme-class.ts +1 -0
- package/src/theme/theme.d.ts +11 -0
- package/src/theme/theme.ts +46 -14
- package/src/thinking.d.ts +5 -0
- package/src/thinking.ts +9 -1
- package/src/tmux.d.ts +39 -4
- package/src/tmux.ts +110 -16
- package/src/tools/ask.ts +20 -9
- package/src/tools/ast-edit.d.ts +2 -2
- package/src/tools/ast-edit.ts +30 -7
- package/src/tools/ast-grep.d.ts +2 -2
- package/src/tools/ast-grep.ts +39 -35
- package/src/tools/bash.d.ts +6 -1
- package/src/tools/bash.ts +15 -9
- package/src/tools/debug.ts +2 -1
- package/src/tools/default-renderer.ts +3 -2
- package/src/tools/edit.d.ts +2 -0
- package/src/tools/edit.ts +100 -21
- package/src/tools/eval.d.ts +53 -4
- package/src/tools/eval.ts +173 -137
- package/src/tools/find.d.ts +14 -4
- package/src/tools/find.ts +71 -18
- package/src/tools/glob.d.ts +3 -3
- package/src/tools/glob.ts +51 -17
- package/src/tools/grep.d.ts +2 -2
- package/src/tools/grep.ts +99 -22
- package/src/tools/grouped-file-output.d.ts +9 -4
- package/src/tools/grouped-file-output.ts +21 -6
- package/src/tools/lsp.ts +30 -18
- package/src/tools/mcp.ts +2 -2
- package/src/tools/native-view.d.ts +1 -1
- package/src/tools/native-view.ts +2 -2
- package/src/tools/read.d.ts +32 -10
- package/src/tools/read.ts +89 -43
- package/src/tools/renderer.d.ts +2 -0
- package/src/tools/renderer.ts +2 -0
- package/src/tools/task.d.ts +31 -0
- package/src/tools/task.ts +40 -7
- package/src/tools/todo.ts +3 -2
- package/src/tools/wait.ts +16 -30
- package/src/tools/web-search-types.d.ts +1 -0
- package/src/tools/web-search-types.ts +1 -0
- package/src/tools/web-search.ts +2 -1
- package/src/tools/write.d.ts +2 -0
- package/src/tools/write.ts +36 -12
- package/src/tools/yield.ts +2 -2
- package/src/tui.d.ts +20 -3
- package/src/tui.ts +160 -43
- package/src/utils.ts +5 -3
- package/dist/types/tools/eval-format/code-mode-declarations.d.ts +0 -5
- package/src/tools/eval-format/code-mode-declarations.ts +0 -61
package/src/tools/lsp.ts
CHANGED
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
} from "../render/render-utils";
|
|
29
29
|
import { renderStatusLine } from "../render";
|
|
30
30
|
import { framedToolCard } from "../render/tool-card";
|
|
31
|
+
import { wrapGuidedLine, wrapPrefixedLine } from "../render/hanging-wrap";
|
|
32
|
+
import { visibleWidth } from "../utils";
|
|
31
33
|
import { ansi, code, col, compact, keyed, md, node, row, span, text } from "../native/describe";
|
|
32
34
|
import type { NativeNode } from "../native/node";
|
|
33
35
|
import { OwnerMemo } from "../native/memo";
|
|
@@ -245,7 +247,7 @@ export function renderResult(
|
|
|
245
247
|
return { label: "Response", state: "success", bodyLines: renderGeneric(text, lines, expanded, theme) };
|
|
246
248
|
};
|
|
247
249
|
|
|
248
|
-
return framedToolCard(theme, () => {
|
|
250
|
+
return framedToolCard(theme, ({ contentWidth }) => {
|
|
249
251
|
// Read mutable state at render time
|
|
250
252
|
const { expanded, isPartial, spinnerFrame } = options;
|
|
251
253
|
const slot = expanded ? 1 : 0;
|
|
@@ -263,12 +265,22 @@ export function renderResult(
|
|
|
263
265
|
: formatStatusIcon(isPartial ? "running" : "error", theme, spinnerFrame);
|
|
264
266
|
const header = `${icon} LSP ${actionLabel}`;
|
|
265
267
|
|
|
268
|
+
// Hover code hangs under its rail; expanded rows hang under their tree
|
|
269
|
+
// connector or guide rails so Ctrl+O reveals full text instead of clipping.
|
|
270
|
+
const rail = ` ${theme.fg("mdCodeBlockBorder", theme.boxRound.vertical)} `;
|
|
271
|
+
const contentLines = codeBlockMatch
|
|
272
|
+
? bodyLines.flatMap(line =>
|
|
273
|
+
line.startsWith(rail) ? wrapPrefixedLine(rail, rail, line.slice(rail.length), contentWidth) : [line],
|
|
274
|
+
)
|
|
275
|
+
: expanded
|
|
276
|
+
? bodyLines.flatMap(line => wrapGuidedLine(line, contentWidth, theme))
|
|
277
|
+
: bodyLines;
|
|
266
278
|
return {
|
|
267
279
|
header,
|
|
268
280
|
phase: isPartial ? "partial" : state,
|
|
269
281
|
sections: [
|
|
270
282
|
...(requestLines.length > 0 ? [{ content: requestLines }] : []),
|
|
271
|
-
{ label: theme.fg("toolTitle", "Response"), content:
|
|
283
|
+
{ label: theme.fg("toolTitle", "Response"), content: contentLines },
|
|
272
284
|
],
|
|
273
285
|
applyBg: false,
|
|
274
286
|
};
|
|
@@ -413,10 +425,7 @@ function renderDiagnostics(
|
|
|
413
425
|
)}`;
|
|
414
426
|
const message = formatDiagnosticMessage(item);
|
|
415
427
|
if (message) {
|
|
416
|
-
output += `\n ${theme.fg("dim", detailPrefix)}${theme.fg(
|
|
417
|
-
"muted",
|
|
418
|
-
truncateToWidth(message, TRUNCATE_LENGTHS.LINE),
|
|
419
|
-
)}`;
|
|
428
|
+
output += `\n ${theme.fg("dim", detailPrefix)}${theme.fg("muted", message)}`;
|
|
420
429
|
}
|
|
421
430
|
}
|
|
422
431
|
return output.split("\n");
|
|
@@ -427,7 +436,10 @@ function renderDiagnostics(
|
|
|
427
436
|
parsedDiagnostics.length > 0 ? parsedDiagnostics.slice(0, 3) : fallbackDiagnostics.slice(0, 3);
|
|
428
437
|
const remaining =
|
|
429
438
|
(parsedDiagnostics.length > 0 ? parsedDiagnostics.length : fallbackDiagnostics.length) - previewItems.length;
|
|
430
|
-
const
|
|
439
|
+
const clipped = previewItems.some(
|
|
440
|
+
item => !("raw" in item) && visibleWidth(formatDiagnosticMessage(item)) > TRUNCATE_LENGTHS.CONTENT,
|
|
441
|
+
);
|
|
442
|
+
const expandHint = formatExpandHint(theme, expanded, remaining > 0 || clipped);
|
|
431
443
|
let output = `${icon} ${theme.fg("dim", meta.join(theme.sep.dot))}${expandHint}`;
|
|
432
444
|
for (let i = 0; i < previewItems.length; i++) {
|
|
433
445
|
const item = previewItems[i];
|
|
@@ -479,8 +491,9 @@ function renderReferences(refMatch: RegExpMatchArray, lines: string[], expanded:
|
|
|
479
491
|
|
|
480
492
|
const files = Array.from(byFile.keys());
|
|
481
493
|
|
|
482
|
-
const renderGrouped = (maxFiles: number, maxLocsPerFile: number
|
|
483
|
-
const
|
|
494
|
+
const renderGrouped = (maxFiles: number, maxLocsPerFile: number): string => {
|
|
495
|
+
const hidden = files.length > maxFiles || files.some(file => byFile.get(file)!.length > maxLocsPerFile);
|
|
496
|
+
const expandHint = formatExpandHint(theme, expanded, hidden);
|
|
484
497
|
let output = `${icon} ${theme.fg("dim", `${refCount} found`)}${expandHint}`;
|
|
485
498
|
|
|
486
499
|
const filesToShow = files.slice(0, maxFiles);
|
|
@@ -506,10 +519,9 @@ function renderReferences(refMatch: RegExpMatchArray, lines: string[], expanded:
|
|
|
506
519
|
`line ${line}, col ${col}`,
|
|
507
520
|
)}`;
|
|
508
521
|
if (expanded) {
|
|
509
|
-
const context = `at ${file}:${line}:${col}`;
|
|
510
522
|
output += `\n ${theme.fg("dim", fileCont)}${theme.fg("dim", locCont)}${theme.fg(
|
|
511
523
|
"muted",
|
|
512
|
-
|
|
524
|
+
`at ${file}:${line}:${col}`,
|
|
513
525
|
)}`;
|
|
514
526
|
}
|
|
515
527
|
}
|
|
@@ -532,11 +544,7 @@ function renderReferences(refMatch: RegExpMatchArray, lines: string[], expanded:
|
|
|
532
544
|
return output;
|
|
533
545
|
};
|
|
534
546
|
|
|
535
|
-
|
|
536
|
-
return renderGrouped(files.length, 3, false).split("\n");
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
return renderGrouped(3, 1, true).split("\n");
|
|
547
|
+
return (expanded ? renderGrouped(files.length, Number.POSITIVE_INFINITY) : renderGrouped(3, 1)).split("\n");
|
|
540
548
|
}
|
|
541
549
|
|
|
542
550
|
// =============================================================================
|
|
@@ -658,13 +666,17 @@ function renderGeneric(text: string, lines: string[], expanded: boolean, theme:
|
|
|
658
666
|
for (let i = 0; i < lines.length; i++) {
|
|
659
667
|
const isLast = i === lines.length - 1;
|
|
660
668
|
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
661
|
-
output += `\n ${theme.fg("dim", branch)} ${
|
|
669
|
+
output += `\n ${theme.fg("dim", branch)} ${replaceTabs(lines[i])}`;
|
|
662
670
|
}
|
|
663
671
|
return output.split("\n");
|
|
664
672
|
}
|
|
665
673
|
|
|
666
674
|
const firstLine = lines[0] || "No output";
|
|
667
|
-
const expandHint = formatExpandHint(
|
|
675
|
+
const expandHint = formatExpandHint(
|
|
676
|
+
theme,
|
|
677
|
+
expanded,
|
|
678
|
+
lines.length > 1 || visibleWidth(firstLine) > TRUNCATE_LENGTHS.TITLE,
|
|
679
|
+
);
|
|
668
680
|
let output = `${icon} ${theme.fg("dim", truncateToWidth(firstLine, TRUNCATE_LENGTHS.TITLE))}${expandHint}`;
|
|
669
681
|
|
|
670
682
|
if (lines.length > 1) {
|
package/src/tools/mcp.ts
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
renderJsonTreeLines,
|
|
27
27
|
} from "./json-tree";
|
|
28
28
|
import { formatStyledTruncationWarning, stripOutputNotice } from "./output-meta";
|
|
29
|
-
import {
|
|
29
|
+
import { clipCollapsed, formatExpandHint } from "../render/render-utils";
|
|
30
30
|
import { formatOutputPaneLines, styleToolOutputLine } from "../render/output-pane";
|
|
31
31
|
import type { StatusLineOptions } from "../render/status-line";
|
|
32
32
|
import { plainToolCard, type ToolCardPhase } from "../render/tool-card";
|
|
@@ -267,7 +267,7 @@ export function renderMCPResult(
|
|
|
267
267
|
expanded,
|
|
268
268
|
collapsedMaxLines: 4,
|
|
269
269
|
expandedMaxLines: 12,
|
|
270
|
-
styleLine: line =>
|
|
270
|
+
styleLine: line => clipCollapsed(styleToolOutputLine(line, theme), contentWidth, expanded),
|
|
271
271
|
showExpandHintWhenUncapped: true,
|
|
272
272
|
},
|
|
273
273
|
theme,
|
|
@@ -85,7 +85,7 @@ export interface RunFootInput {
|
|
|
85
85
|
readonly state: RunState;
|
|
86
86
|
/** Exit code of a `failed` run; a non-zero code names the state `exit N`. */
|
|
87
87
|
readonly exitCode?: number;
|
|
88
|
-
/** Milliseconds run so far (`running`) or in total;
|
|
88
|
+
/** Milliseconds run so far (`running`, a live `background` job) or in total; omitted when undefined. */
|
|
89
89
|
readonly elapsedMs?: number;
|
|
90
90
|
/** Quiet facts (job id, service state, artifact), muted and ` · `-joined. */
|
|
91
91
|
readonly facts?: readonly string[];
|
package/src/tools/native-view.ts
CHANGED
|
@@ -216,7 +216,7 @@ export interface RunFootInput {
|
|
|
216
216
|
readonly state: RunState;
|
|
217
217
|
/** Exit code of a `failed` run; a non-zero code names the state `exit N`. */
|
|
218
218
|
readonly exitCode?: number;
|
|
219
|
-
/** Milliseconds run so far (`running`) or in total;
|
|
219
|
+
/** Milliseconds run so far (`running`, a live `background` job) or in total; omitted when undefined. */
|
|
220
220
|
readonly elapsedMs?: number;
|
|
221
221
|
/** Quiet facts (job id, service state, artifact), muted and ` · `-joined. */
|
|
222
222
|
readonly facts?: readonly string[];
|
|
@@ -250,7 +250,7 @@ export function runFoot(input: RunFootInput): NativeNode {
|
|
|
250
250
|
? undefined
|
|
251
251
|
: node(
|
|
252
252
|
"elapsed",
|
|
253
|
-
input.state === "running"
|
|
253
|
+
input.state === "running" || input.state === "background"
|
|
254
254
|
? { role: "zeta.run.time", age: ms, format: "short" }
|
|
255
255
|
: { role: "zeta.run.time", age: ms, stopped: ms, format: "short" },
|
|
256
256
|
undefined,
|
package/src/tools/read.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SummaryResult } from "@linxiraos/pi-natives";
|
|
2
2
|
import type { Component } from "../tui";
|
|
3
|
-
import type { NativeToolView, RenderResultOptions, ToolActivityContext, ToolActivitySummary } from "./renderer";
|
|
3
|
+
import type { RenderResultContextOptions, NativeToolView, RenderResultOptions, ToolActivityContext, ToolActivitySummary } from "./renderer";
|
|
4
4
|
import type { NativeNode } from "../native/node";
|
|
5
5
|
import type { Theme } from "../theme/theme";
|
|
6
6
|
import type { OutputMeta } from "./output-meta";
|
|
@@ -20,6 +20,12 @@ export interface ReadToolDetails {
|
|
|
20
20
|
truncation?: ReadTruncationStats;
|
|
21
21
|
isDirectory?: boolean;
|
|
22
22
|
resolvedPath?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Absolute path of the disk file whose image this read returned. A native TSP host that
|
|
25
|
+
* supports `media` shows the file by path instead of the uploaded image bytes. Unset for
|
|
26
|
+
* screenshots, URL reads and other images not backed by a file. Display-only.
|
|
27
|
+
*/
|
|
28
|
+
mediaPath?: string;
|
|
23
29
|
suffixResolution?: {
|
|
24
30
|
from: string;
|
|
25
31
|
to: string;
|
|
@@ -34,6 +40,11 @@ export interface ReadToolDetails {
|
|
|
34
40
|
fileSize?: number;
|
|
35
41
|
/** Full source line count when the read reached EOF and the count is exact. */
|
|
36
42
|
totalLines?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Whether the text's rows carry line-number prefixes (`N:` hashline, `N|` line numbers). Set where a
|
|
45
|
+
* file read is formatted; a raw read sets `false`. Snapcompact reads it to tell a `12:` prefix from content.
|
|
46
|
+
*/
|
|
47
|
+
rowPrefixes?: boolean;
|
|
37
48
|
/** Raw text + start line for user-visible TUI rendering, set when content is text-like.
|
|
38
49
|
* Mirrors the same lines the model receives but without hashline/line-number prefixes,
|
|
39
50
|
* so the TUI can render the file content with its own gutter without re-parsing the formatted text. */
|
|
@@ -49,7 +60,10 @@ export interface ReadToolDetails {
|
|
|
49
60
|
};
|
|
50
61
|
/** Number of unresolved git conflicts surfaced by this read (TUI uses for inline `⚠ N` badge). */
|
|
51
62
|
conflictCount?: number;
|
|
52
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Paths recovered from a delimited read argument. The TUI renders one call as multiple read rows; snapcompact
|
|
65
|
+
* keeps such a composite result as text.
|
|
66
|
+
*/
|
|
53
67
|
displayReadTargets?: string[];
|
|
54
68
|
/**
|
|
55
69
|
* Resolved filesystem link target for each {@link displayReadTargets} entry, aligned by index; `null` when a
|
|
@@ -107,14 +121,22 @@ export declare function pendingFileLinkPath(inputPath: string): string;
|
|
|
107
121
|
* target when the structured `resolvedPath` isn't set (the common plain-file and
|
|
108
122
|
* image reads only record the path in `meta.source`). URL/internal sources are
|
|
109
123
|
* not fs paths, so only `type: "path"` qualifies. */
|
|
110
|
-
export declare function readSourceFsPath(details:
|
|
124
|
+
export declare function readSourceFsPath(details: unknown): string | undefined;
|
|
125
|
+
/**
|
|
126
|
+
* Whether a read result renders as a Markdown preview rather than numbered source:
|
|
127
|
+
* the read tool tagged it `text/markdown` (`read.renderMarkdown`, or a protocol
|
|
128
|
+
* resource) and the call did not ask for `:raw` source. Shared by the standalone
|
|
129
|
+
* read card and the read group's content previews so both honor the setting.
|
|
130
|
+
*/
|
|
131
|
+
export declare function readResultIsMarkdown(details: ReadToolDetails | undefined, args: ReadRenderArgs | undefined): boolean;
|
|
111
132
|
/**
|
|
112
|
-
* Read content as numbered `code` (no path header: the head names
|
|
113
|
-
*
|
|
114
|
-
* `null` for an elided
|
|
115
|
-
* every line, else count
|
|
133
|
+
* Read content as numbered, wrapped `code` (no path header: the head names
|
|
134
|
+
* the file), matching the ANSI preview's hanging wrap. Numbers come from
|
|
135
|
+
* `lineNumbers` (`displayContent`'s per-line numbers, `null` for an elided
|
|
136
|
+
* block; runs split with `…` between) when they cover every line, else count
|
|
137
|
+
* up from `start`.
|
|
116
138
|
*/
|
|
117
|
-
export declare function readContentCode(content: string, start: number, numbers: readonly (number | null)[] | undefined,
|
|
139
|
+
export declare function readContentCode(content: string, start: number, numbers: readonly (number | null)[] | undefined, path: string | undefined): NativeNode[];
|
|
118
140
|
/** Render file, image, and URL reads in the transcript. */
|
|
119
141
|
export declare const readToolRenderer: {
|
|
120
142
|
activitySummary(args: unknown, _context: ToolActivityContext): ToolActivitySummary;
|
|
@@ -126,7 +148,7 @@ export declare const readToolRenderer: {
|
|
|
126
148
|
}>;
|
|
127
149
|
details?: ReadToolDetails;
|
|
128
150
|
isError?: boolean;
|
|
129
|
-
}, options:
|
|
151
|
+
}, options: RenderResultContextOptions, uiTheme: Theme, args?: ReadRenderArgs): Component;
|
|
130
152
|
describeCall(args: ReadRenderArgs): NativeToolView;
|
|
131
153
|
/**
|
|
132
154
|
* Inline (§7.3 read): the head names the file and range; the content
|
|
@@ -140,7 +162,7 @@ export declare const readToolRenderer: {
|
|
|
140
162
|
}>;
|
|
141
163
|
details?: ReadToolDetails;
|
|
142
164
|
isError?: boolean;
|
|
143
|
-
},
|
|
165
|
+
}, options: RenderResultContextOptions, args?: ReadRenderArgs): NativeToolView;
|
|
144
166
|
mergeCallAndResult: true;
|
|
145
167
|
};
|
|
146
168
|
/** Inclusive line range describing one elided span in a structural summary. */
|
package/src/tools/read.ts
CHANGED
|
@@ -4,10 +4,12 @@ import { LINE_RANGE_CHUNK_SOURCE, parseLineRanges } from "./line-ranges";
|
|
|
4
4
|
import * as os from "node:os";
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { parseArchivePathCandidates } from "@linxiraos/pi-utils/ar";
|
|
7
|
+
import { isRecord, sanitizeText } from "@linxiraos/pi-utils";
|
|
7
8
|
import type { Component } from "../tui";
|
|
8
9
|
import { Text } from "../components/text";
|
|
9
10
|
import type {
|
|
10
11
|
NativeToolHead,
|
|
12
|
+
RenderResultContextOptions,
|
|
11
13
|
NativeToolView,
|
|
12
14
|
RenderResultOptions,
|
|
13
15
|
ToolActivityContext,
|
|
@@ -15,7 +17,7 @@ import type {
|
|
|
15
17
|
ToolRenderer,
|
|
16
18
|
} from "./renderer";
|
|
17
19
|
import { ansi, compact, md } from "../native/describe";
|
|
18
|
-
import { base64ImageNode } from "../native/blobs";
|
|
20
|
+
import { base64ImageNode, nativeMediaNode, readMediaPath } from "../native/blobs";
|
|
19
21
|
import type { NativeChild, NativeNode } from "../native/node";
|
|
20
22
|
import { fileHref, footnoteText, inlineErrorView, resultText } from "./native-view";
|
|
21
23
|
import { numberedCode } from "./grouped-file-output";
|
|
@@ -54,6 +56,12 @@ export interface ReadToolDetails {
|
|
|
54
56
|
truncation?: ReadTruncationStats;
|
|
55
57
|
isDirectory?: boolean;
|
|
56
58
|
resolvedPath?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Absolute path of the disk file whose image this read returned. A native TSP host that
|
|
61
|
+
* supports `media` shows the file by path instead of the uploaded image bytes. Unset for
|
|
62
|
+
* screenshots, URL reads and other images not backed by a file. Display-only.
|
|
63
|
+
*/
|
|
64
|
+
mediaPath?: string;
|
|
57
65
|
suffixResolution?: { from: string; to: string };
|
|
58
66
|
url?: string;
|
|
59
67
|
finalUrl?: string;
|
|
@@ -65,6 +73,11 @@ export interface ReadToolDetails {
|
|
|
65
73
|
fileSize?: number;
|
|
66
74
|
/** Full source line count when the read reached EOF and the count is exact. */
|
|
67
75
|
totalLines?: number;
|
|
76
|
+
/**
|
|
77
|
+
* Whether the text's rows carry line-number prefixes (`N:` hashline, `N|` line numbers). Set where a
|
|
78
|
+
* file read is formatted; a raw read sets `false`. Snapcompact reads it to tell a `12:` prefix from content.
|
|
79
|
+
*/
|
|
80
|
+
rowPrefixes?: boolean;
|
|
68
81
|
/** Raw text + start line for user-visible TUI rendering, set when content is text-like.
|
|
69
82
|
* Mirrors the same lines the model receives but without hashline/line-number prefixes,
|
|
70
83
|
* so the TUI can render the file content with its own gutter without re-parsing the formatted text. */
|
|
@@ -76,7 +89,10 @@ export interface ReadToolDetails {
|
|
|
76
89
|
summary?: { lines: number; elidedSpans: number; elidedLines: number };
|
|
77
90
|
/** Number of unresolved git conflicts surfaced by this read (TUI uses for inline `⚠ N` badge). */
|
|
78
91
|
conflictCount?: number;
|
|
79
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* Paths recovered from a delimited read argument. The TUI renders one call as multiple read rows; snapcompact
|
|
94
|
+
* keeps such a composite result as text.
|
|
95
|
+
*/
|
|
80
96
|
displayReadTargets?: string[];
|
|
81
97
|
/**
|
|
82
98
|
* Resolved filesystem link target for each {@link displayReadTargets} entry, aligned by index; `null` when a
|
|
@@ -296,9 +312,17 @@ function firstReadSelectorLine(sel: string | undefined): number | undefined {
|
|
|
296
312
|
* target when the structured `resolvedPath` isn't set (the common plain-file and
|
|
297
313
|
* image reads only record the path in `meta.source`). URL/internal sources are
|
|
298
314
|
* not fs paths, so only `type: "path"` qualifies. */
|
|
299
|
-
export function readSourceFsPath(details:
|
|
300
|
-
|
|
301
|
-
|
|
315
|
+
export function readSourceFsPath(details: unknown): string | undefined {
|
|
316
|
+
if (!isRecord(details) || !isRecord(details.meta) || !isRecord(details.meta.source)) return undefined;
|
|
317
|
+
const source = details.meta.source;
|
|
318
|
+
return source.type === "path" && typeof source.value === "string" ? source.value : undefined;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function readLinkTarget(details: unknown): string | undefined {
|
|
322
|
+
if (!isRecord(details)) return undefined;
|
|
323
|
+
if (typeof details.resolvedPath === "string") return details.resolvedPath;
|
|
324
|
+
if (typeof details.displayTarget === "string") return details.displayTarget;
|
|
325
|
+
return readSourceFsPath(details);
|
|
302
326
|
}
|
|
303
327
|
|
|
304
328
|
function formatReadPathLink(
|
|
@@ -314,9 +338,11 @@ function formatReadPathLink(
|
|
|
314
338
|
const split = splitReadRenderPath(rawPath);
|
|
315
339
|
const basePath = split.path || rawPath;
|
|
316
340
|
const selectorSuffix = split.sel ? `:${split.sel}` : "";
|
|
317
|
-
const plainDisplayPath =
|
|
318
|
-
|
|
319
|
-
|
|
341
|
+
const plainDisplayPath = sanitizeText(
|
|
342
|
+
options.suffixResolution
|
|
343
|
+
? shortenPath(options.suffixResolution.to)
|
|
344
|
+
: shortenPath(basePath || options.resolvedPath || options.fallbackLabel || rawPath),
|
|
345
|
+
);
|
|
320
346
|
// Calls render before the tool has resolved a filesystem target. Preserve
|
|
321
347
|
// protocol resources as plain text, but resolve direct relative file paths
|
|
322
348
|
// so terminals receive an explicit file: link rather than guessing HTTPS.
|
|
@@ -335,12 +361,29 @@ function readRawPath(args: ReadRenderArgs | undefined): string {
|
|
|
335
361
|
return typeof args?.file_path === "string" ? args.file_path : typeof args?.path === "string" ? args.path : "";
|
|
336
362
|
}
|
|
337
363
|
|
|
364
|
+
/**
|
|
365
|
+
* Whether a read result renders as a Markdown preview rather than numbered source:
|
|
366
|
+
* the read tool tagged it `text/markdown` (`read.renderMarkdown`, or a protocol
|
|
367
|
+
* resource) and the call did not ask for `:raw` source. Shared by the standalone
|
|
368
|
+
* read card and the read group's content previews so both honor the setting.
|
|
369
|
+
*/
|
|
370
|
+
export function readResultIsMarkdown(details: ReadToolDetails | undefined, args: ReadRenderArgs | undefined): boolean {
|
|
371
|
+
if (details?.contentType !== "text/markdown" || args?.raw === true) return false;
|
|
372
|
+
const sel = splitReadRenderPath(readRawPath(args)).sel;
|
|
373
|
+
return sel?.split(":").some(chunk => chunk.toLowerCase() === "raw") !== true;
|
|
374
|
+
}
|
|
375
|
+
|
|
338
376
|
/**
|
|
339
377
|
* Native read head (§7.3 read): `Read path:13-36` with the path as the
|
|
340
378
|
* target (shortened, plus the selector / legacy offset range), a `file://`
|
|
341
379
|
* link when the fs path is known, and a suffix correction as meta.
|
|
342
380
|
*/
|
|
343
|
-
function readNativeHead(
|
|
381
|
+
function readNativeHead(
|
|
382
|
+
rawPath: string,
|
|
383
|
+
args: ReadRenderArgs | undefined,
|
|
384
|
+
details?: ReadToolDetails,
|
|
385
|
+
linkPath?: string,
|
|
386
|
+
): NativeToolHead {
|
|
344
387
|
const split = splitReadRenderPath(rawPath);
|
|
345
388
|
const suffix = details?.suffixResolution;
|
|
346
389
|
let target = suffix ? shortenPath(suffix.to) : shortenPath(split.path || rawPath);
|
|
@@ -359,9 +402,9 @@ function readNativeHead(rawPath: string, args: ReadRenderArgs | undefined, detai
|
|
|
359
402
|
const conflicts = details?.conflictCount ?? 0;
|
|
360
403
|
return {
|
|
361
404
|
title: "Read",
|
|
362
|
-
target: target || "…",
|
|
405
|
+
target: sanitizeText(target) || "…",
|
|
363
406
|
targetKind: "path",
|
|
364
|
-
href: fileHref(details?.resolvedPath ?? details?.displayTarget ?? readSourceFsPath(details)),
|
|
407
|
+
href: fileHref(linkPath ?? details?.resolvedPath ?? details?.displayTarget ?? readSourceFsPath(details)),
|
|
365
408
|
meta,
|
|
366
409
|
badges:
|
|
367
410
|
conflicts > 0 ? [{ text: `${conflicts} conflict${conflicts === 1 ? "" : "s"}`, tone: "warning" }] : undefined,
|
|
@@ -378,21 +421,22 @@ function readRangeStart(rawPath: string, args: ReadRenderArgs | undefined): numb
|
|
|
378
421
|
}
|
|
379
422
|
|
|
380
423
|
/**
|
|
381
|
-
* Read content as numbered `code` (no path header: the head names
|
|
382
|
-
*
|
|
383
|
-
* `null` for an elided
|
|
384
|
-
* every line, else count
|
|
424
|
+
* Read content as numbered, wrapped `code` (no path header: the head names
|
|
425
|
+
* the file), matching the ANSI preview's hanging wrap. Numbers come from
|
|
426
|
+
* `lineNumbers` (`displayContent`'s per-line numbers, `null` for an elided
|
|
427
|
+
* block; runs split with `…` between) when they cover every line, else count
|
|
428
|
+
* up from `start`.
|
|
385
429
|
*/
|
|
386
430
|
export function readContentCode(
|
|
387
431
|
content: string,
|
|
388
432
|
start: number,
|
|
389
433
|
numbers: readonly (number | null)[] | undefined,
|
|
390
|
-
|
|
434
|
+
path: string | undefined,
|
|
391
435
|
): NativeNode[] {
|
|
392
436
|
const lines = content.split("\n");
|
|
393
437
|
return numberedCode(
|
|
394
438
|
lines.map((text, i) => ({ n: numbers && numbers.length === lines.length ? numbers[i]! : start + i, text })),
|
|
395
|
-
{
|
|
439
|
+
{ path, wrap: true },
|
|
396
440
|
);
|
|
397
441
|
}
|
|
398
442
|
|
|
@@ -435,7 +479,7 @@ export const readToolRenderer = {
|
|
|
435
479
|
typeof input?.file_path === "string" ? input.file_path : typeof input?.path === "string" ? input.path : "";
|
|
436
480
|
const routed = readUrlCard(rawPath);
|
|
437
481
|
if (routed) return { label: routed.card.label, detail: routed.target || routed.card.rootDetail };
|
|
438
|
-
return { label: "Read", detail: shortenPath(rawPath) };
|
|
482
|
+
return { label: "Read", detail: sanitizeText(shortenPath(rawPath)) };
|
|
439
483
|
},
|
|
440
484
|
renderCall(args: ReadRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
441
485
|
const rawPath =
|
|
@@ -462,10 +506,11 @@ export const readToolRenderer = {
|
|
|
462
506
|
|
|
463
507
|
renderResult(
|
|
464
508
|
result: { content: Array<{ type: string; text?: string }>; details?: ReadToolDetails; isError?: boolean },
|
|
465
|
-
options:
|
|
509
|
+
options: RenderResultContextOptions,
|
|
466
510
|
uiTheme: Theme,
|
|
467
511
|
args?: ReadRenderArgs,
|
|
468
512
|
): Component {
|
|
513
|
+
const linkPath = readLinkTarget(options.renderContext?.rawDetails);
|
|
469
514
|
const urlDetails = result.details as ReadUrlToolDetails | undefined;
|
|
470
515
|
const baseRawPathForKind =
|
|
471
516
|
typeof args?.file_path === "string" ? args.file_path : typeof args?.path === "string" ? args.path : "";
|
|
@@ -492,8 +537,8 @@ export const readToolRenderer = {
|
|
|
492
537
|
const filePath =
|
|
493
538
|
formatReadPathLink(rawPath, {
|
|
494
539
|
offset: args?.offset,
|
|
495
|
-
sourcePath: result.details?.displayTarget ?? readSourceFsPath(result.details),
|
|
496
|
-
}) || shortenPath(rawPath);
|
|
540
|
+
sourcePath: linkPath ?? result.details?.displayTarget ?? readSourceFsPath(result.details),
|
|
541
|
+
}) || sanitizeText(shortenPath(rawPath));
|
|
497
542
|
let title = filePath ? `Read ${filePath}` : "Read";
|
|
498
543
|
if (args?.offset !== undefined || args?.limit !== undefined) {
|
|
499
544
|
const startLine = args.offset ?? 1;
|
|
@@ -525,7 +570,9 @@ export const readToolRenderer = {
|
|
|
525
570
|
const truncation = details?.meta?.truncation;
|
|
526
571
|
const fallback = details?.truncation;
|
|
527
572
|
if (details?.resolvedPath) {
|
|
528
|
-
warningLines.push(
|
|
573
|
+
warningLines.push(
|
|
574
|
+
uiTheme.fg("dim", wrapBrackets(`Resolved path: ${shortenPath(details.resolvedPath)}`, uiTheme)),
|
|
575
|
+
);
|
|
529
576
|
}
|
|
530
577
|
if (truncation) {
|
|
531
578
|
if (fallback?.firstLineExceedsLimit) {
|
|
@@ -543,7 +590,7 @@ export const readToolRenderer = {
|
|
|
543
590
|
if (imageContent) {
|
|
544
591
|
const suffix = details?.suffixResolution;
|
|
545
592
|
const displayPath = formatReadPathLink(rawPath, {
|
|
546
|
-
resolvedPath: details?.resolvedPath,
|
|
593
|
+
resolvedPath: linkPath ?? details?.resolvedPath,
|
|
547
594
|
sourcePath: details?.displayTarget ?? readSourceFsPath(details),
|
|
548
595
|
suffixResolution: suffix,
|
|
549
596
|
fallbackLabel: "image",
|
|
@@ -575,7 +622,7 @@ export const readToolRenderer = {
|
|
|
575
622
|
// reads only record the absolute path in meta.source, so fall back to that
|
|
576
623
|
// to keep the title clickable.
|
|
577
624
|
const displayPath = formatReadPathLink(rawPath, {
|
|
578
|
-
resolvedPath: details?.resolvedPath,
|
|
625
|
+
resolvedPath: linkPath ?? details?.resolvedPath,
|
|
579
626
|
sourcePath: details?.displayTarget ?? readSourceFsPath(details),
|
|
580
627
|
suffixResolution: suffix,
|
|
581
628
|
offset: args?.offset,
|
|
@@ -594,9 +641,7 @@ export const readToolRenderer = {
|
|
|
594
641
|
const n = details.conflictCount;
|
|
595
642
|
title += ` ${uiTheme.fg("warning", `(⚠ ${n} conflict${n === 1 ? "" : "s"})`)}`;
|
|
596
643
|
}
|
|
597
|
-
const
|
|
598
|
-
args?.raw === true || renderPath.sel?.split(":").some(chunk => chunk.toLowerCase() === "raw") === true;
|
|
599
|
-
const isMarkdown = details?.contentType === "text/markdown" && !rawRequested;
|
|
644
|
+
const isMarkdown = readResultIsMarkdown(details, args);
|
|
600
645
|
let cachedWidth: number | undefined;
|
|
601
646
|
let cachedExpanded: boolean | undefined;
|
|
602
647
|
let cachedLines: string[] | undefined;
|
|
@@ -659,9 +704,10 @@ export const readToolRenderer = {
|
|
|
659
704
|
*/
|
|
660
705
|
describeResult(
|
|
661
706
|
result: { content: Array<{ type: string; text?: string }>; details?: ReadToolDetails; isError?: boolean },
|
|
662
|
-
|
|
707
|
+
options: RenderResultContextOptions,
|
|
663
708
|
args?: ReadRenderArgs,
|
|
664
709
|
): NativeToolView {
|
|
710
|
+
const linkPath = readLinkTarget(options.renderContext?.rawDetails);
|
|
665
711
|
const details = result.details;
|
|
666
712
|
const rawPath = readRawPath(args);
|
|
667
713
|
const routed = readUrlCard(rawPath, details);
|
|
@@ -677,11 +723,11 @@ export const readToolRenderer = {
|
|
|
677
723
|
|
|
678
724
|
if (result.isError) {
|
|
679
725
|
const message = (resultText(result) || "Unknown error").replace(/^Error:\s*/, "");
|
|
680
|
-
return inlineErrorView(readNativeHead(rawPath, args, details), message);
|
|
726
|
+
return inlineErrorView(readNativeHead(rawPath, args, details, linkPath), message);
|
|
681
727
|
}
|
|
682
728
|
|
|
683
729
|
const suffix = details?.suffixResolution;
|
|
684
|
-
const head = readNativeHead(rawPath, args, details);
|
|
730
|
+
const head = readNativeHead(rawPath, args, details, linkPath);
|
|
685
731
|
const truncation = details?.meta?.truncation;
|
|
686
732
|
const fallback = details?.truncation;
|
|
687
733
|
const firstLineTooLong = truncation !== undefined && fallback?.firstLineExceedsLimit === true;
|
|
@@ -695,6 +741,9 @@ export const readToolRenderer = {
|
|
|
695
741
|
);
|
|
696
742
|
|
|
697
743
|
const contentText = details?.displayContent?.text ?? stripOutputNotice(resultText(result), details?.meta);
|
|
744
|
+
// A file Tern's media viewer shows (image, model, PDF, video, audio): the host loads it by path.
|
|
745
|
+
const mediaPath = readMediaPath(options.renderContext?.rawDetails) ?? details?.mediaPath;
|
|
746
|
+
const media = options.media && mediaPath ? nativeMediaNode(mediaPath, "media") : undefined;
|
|
698
747
|
const imageBlock = result.content?.find(isImageBlock);
|
|
699
748
|
if (imageBlock) {
|
|
700
749
|
const detailText = contentText.trim();
|
|
@@ -703,7 +752,7 @@ export const readToolRenderer = {
|
|
|
703
752
|
tone: suffix ? "warning" : undefined,
|
|
704
753
|
inline: true,
|
|
705
754
|
body: compact<NativeChild>([
|
|
706
|
-
describeReadImage(imageBlock, shortenPath(suffix?.to ?? splitReadRenderPath(rawPath).path)),
|
|
755
|
+
media ?? describeReadImage(imageBlock, shortenPath(suffix?.to ?? splitReadRenderPath(rawPath).path)),
|
|
707
756
|
detailText.length > 0 && ansi(detailText),
|
|
708
757
|
foot,
|
|
709
758
|
]),
|
|
@@ -711,18 +760,15 @@ export const readToolRenderer = {
|
|
|
711
760
|
}
|
|
712
761
|
|
|
713
762
|
const renderPath = splitReadRenderPath(rawPath);
|
|
714
|
-
const
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
getLanguageFromPath(renderPath.path),
|
|
724
|
-
);
|
|
725
|
-
return { tool: head, inline: true, body: compact<NativeChild>([...content, foot]) };
|
|
763
|
+
const content: NativeChild[] = readResultIsMarkdown(details, args)
|
|
764
|
+
? [md(contentText)]
|
|
765
|
+
: readContentCode(
|
|
766
|
+
contentText,
|
|
767
|
+
details?.displayContent?.startLine ?? readRangeStart(rawPath, args),
|
|
768
|
+
details?.displayContent?.lineNumbers,
|
|
769
|
+
renderPath.path,
|
|
770
|
+
);
|
|
771
|
+
return { tool: head, inline: true, body: compact<NativeChild>([media, ...content, foot]) };
|
|
726
772
|
},
|
|
727
773
|
mergeCallAndResult: true,
|
|
728
774
|
} satisfies ToolRenderer<ReadRenderArgs, ReadToolDetails>;
|
package/src/tools/renderer.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ export interface RenderResultOptions {
|
|
|
33
33
|
elapsedMs?: number;
|
|
34
34
|
/** The call was abandoned without a final result (turn aborted, sealed). Native describe hooks only. */
|
|
35
35
|
cancelled?: boolean;
|
|
36
|
+
/** The native host shows files by path (`cx.supports("media")`). Native describe hooks only. */
|
|
37
|
+
media?: boolean;
|
|
36
38
|
}
|
|
37
39
|
/** Render options for a result, plus the tool-specific context the transcript threads through. */
|
|
38
40
|
export type RenderResultContextOptions = RenderResultOptions & {
|
package/src/tools/renderer.ts
CHANGED
|
@@ -34,6 +34,8 @@ export interface RenderResultOptions {
|
|
|
34
34
|
elapsedMs?: number;
|
|
35
35
|
/** The call was abandoned without a final result (turn aborted, sealed). Native describe hooks only. */
|
|
36
36
|
cancelled?: boolean;
|
|
37
|
+
/** The native host shows files by path (`cx.supports("media")`). Native describe hooks only. */
|
|
38
|
+
media?: boolean;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
/** Render options for a result, plus the tool-specific context the transcript threads through. */
|
package/src/tools/task.d.ts
CHANGED
|
@@ -101,6 +101,10 @@ export interface TaskItem {
|
|
|
101
101
|
name?: string;
|
|
102
102
|
/** Agent type to run this item (e.g. "scout"). Defaults to the spawn policy's default agent. */
|
|
103
103
|
agent?: string;
|
|
104
|
+
/** Historical source session selected by the bundled consult agent. */
|
|
105
|
+
sourceSession?: string;
|
|
106
|
+
/** Optional model selector override for the historical source session. */
|
|
107
|
+
model?: string | string[];
|
|
104
108
|
/** The work; required by the schema. */
|
|
105
109
|
task?: string;
|
|
106
110
|
/** How open-ended the work is; required by the schema and the child's sole `auto` thinking classification input. */
|
|
@@ -127,6 +131,10 @@ export interface TaskParams {
|
|
|
127
131
|
name?: string;
|
|
128
132
|
/** Agent type to spawn (flat form); omitted values resolve from the session spawn policy. */
|
|
129
133
|
agent?: string;
|
|
134
|
+
/** Historical source session selected by the bundled consult agent. */
|
|
135
|
+
sourceSession?: string;
|
|
136
|
+
/** Optional model selector override for the historical source session. */
|
|
137
|
+
model?: string | string[];
|
|
130
138
|
/** The work (flat form). */
|
|
131
139
|
task?: string;
|
|
132
140
|
/** How open-ended the work is (flat form); see {@link TaskItem.solutionSpace}. */
|
|
@@ -383,6 +391,29 @@ export interface SingleResult {
|
|
|
383
391
|
attempt: number;
|
|
384
392
|
errorMessage: string;
|
|
385
393
|
};
|
|
394
|
+
/**
|
|
395
|
+
* Coarse category of a non-zero exit — `usage-limit`, `auth`,
|
|
396
|
+
* `account-policy`, `context-overflow`, `transient`, `aborted`, or
|
|
397
|
+
* `unknown`. A parent agent reads this to tell "swap the model" (the first
|
|
398
|
+
* three) from "fix the request" instead of parsing provider prose.
|
|
399
|
+
*/
|
|
400
|
+
failureClass?: "usage-limit" | "auth" | "account-policy" | "context-overflow" | "transient" | "aborted" | "unknown";
|
|
401
|
+
/**
|
|
402
|
+
* Other models this session could run instead (selectors accepted by the
|
|
403
|
+
* task `model` parameter, including `@role` aliases). Empty when nothing
|
|
404
|
+
* else is configured or every alternative is already in cooldown.
|
|
405
|
+
*/
|
|
406
|
+
fallbackCandidates?: string[];
|
|
407
|
+
/**
|
|
408
|
+
* Set when a caller-supplied `model` replaced the model this run would
|
|
409
|
+
* otherwise have used — the audit trail of an agent-driven switch.
|
|
410
|
+
*/
|
|
411
|
+
modelChange?: {
|
|
412
|
+
source: "spawn-param" | "wake-param";
|
|
413
|
+
/** The model the run would have used without the override, when resolvable. */
|
|
414
|
+
from?: string;
|
|
415
|
+
to: string;
|
|
416
|
+
};
|
|
386
417
|
/** Output metadata for agent:// URL integration */
|
|
387
418
|
outputMeta?: {
|
|
388
419
|
lineCount: number;
|