@quandev104/pi-style 0.1.2 → 0.1.4
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 +21 -0
- package/README.md +1 -2
- package/dist/extensions/pi-style.js +5919 -4966
- package/dist/extensions/pi-style.js.map +1 -1
- package/extension-src/pi-style/app/command-service.ts +2 -2
- package/extension-src/pi-style/app/index.ts +0 -1
- package/extension-src/pi-style/domain/config-authorization.ts +1 -2
- package/extension-src/pi-style/domain/config-normalization.ts +3 -3
- package/extension-src/pi-style/domain/config-types.ts +2 -2
- package/extension-src/pi-style/domain/status.ts +1 -1
- package/extension-src/pi-style/domain/theme.ts +3 -0
- package/extension-src/pi-style/features/messages/index.ts +2 -8
- package/extension-src/pi-style/features/tools/boxed/bash.ts +663 -45
- package/extension-src/pi-style/features/tools/boxed/batch.ts +459 -0
- package/extension-src/pi-style/features/tools/boxed/edit.ts +28 -2
- package/extension-src/pi-style/features/tools/boxed/fallback.ts +47 -4
- package/extension-src/pi-style/features/tools/boxed/find.ts +48 -48
- package/extension-src/pi-style/features/tools/boxed/grep.ts +161 -89
- package/extension-src/pi-style/features/tools/boxed/index.ts +4 -0
- package/extension-src/pi-style/features/tools/boxed/ls.ts +39 -47
- package/extension-src/pi-style/features/tools/boxed/output-tree.ts +368 -0
- package/extension-src/pi-style/features/tools/boxed/quick-edit.ts +25 -4
- package/extension-src/pi-style/features/tools/boxed/read.ts +32 -189
- package/extension-src/pi-style/features/tools/boxed/session-config.ts +70 -4
- package/extension-src/pi-style/features/tools/boxed/shared.ts +41 -1
- package/extension-src/pi-style/features/tools/boxed/write.ts +105 -47
- package/extension-src/pi-style/features/tools/index.ts +14 -0
- package/extension-src/pi-style/pi/compatibility-coordinator.ts +4 -26
- package/extension-src/pi-style/pi/compatibility-probe.ts +3 -33
- package/extension-src/pi-style/pi/compatibility-registry.ts +0 -1
- package/extension-src/pi-style/pi/config-session.ts +0 -2
- package/extension-src/pi-style/pi/index.ts +35 -1
- package/extension-src/pi-style/pi/session-coordinator.ts +47 -3
- package/extension-src/pi-style/shared/box.ts +117 -26
- package/extension-src/pi-style/shared/theme-extras.ts +0 -2
- package/package.json +1 -1
|
@@ -2,8 +2,16 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
|
|
|
2
2
|
import { type KeyId, matchesKey } from "@earendil-works/pi-tui";
|
|
3
3
|
import type { ConfigFilePort } from "../app/config-storage.js";
|
|
4
4
|
import { createPiStyleApp, type PiStyleApp } from "../app/index.js";
|
|
5
|
+
import { resolveTheme } from "../domain/theme.js";
|
|
5
6
|
import { setSpecialBlockTheme } from "../features/messages/special-blocks.js";
|
|
6
|
-
import {
|
|
7
|
+
import { resetBashTreeRegistry } from "../features/tools/boxed/bash.js";
|
|
8
|
+
import { resetBatchRegistry } from "../features/tools/boxed/batch.js";
|
|
9
|
+
import { resetGrepRegistry } from "../features/tools/boxed/grep.js";
|
|
10
|
+
import {
|
|
11
|
+
setToolsRenderConfig,
|
|
12
|
+
stopAllElapsedTickers,
|
|
13
|
+
type ToolsRenderConfig,
|
|
14
|
+
} from "../features/tools/boxed/session-config.js";
|
|
7
15
|
import { createCompatibilityCoordinator } from "./compatibility-coordinator.js";
|
|
8
16
|
import {
|
|
9
17
|
type CompatibilityCleanupResult,
|
|
@@ -45,6 +53,8 @@ export function createPiStyleSessionCoordinator(pi: ExtensionAPI, hooks: Compati
|
|
|
45
53
|
let active = false;
|
|
46
54
|
let tuiSession = false;
|
|
47
55
|
let terminalInputUnsubscribe: (() => void) | undefined;
|
|
56
|
+
let sessionTheme: unknown;
|
|
57
|
+
let sessionUi: import("@earendil-works/pi-coding-agent").ExtensionUIContext | undefined;
|
|
48
58
|
const source = createConfigSourceAdapter(
|
|
49
59
|
pi,
|
|
50
60
|
filePort,
|
|
@@ -68,6 +78,22 @@ export function createPiStyleSessionCoordinator(pi: ExtensionAPI, hooks: Compati
|
|
|
68
78
|
),
|
|
69
79
|
);
|
|
70
80
|
};
|
|
81
|
+
/** Render-scoped tool config: line budgets + the resolved open-tree glyph. */
|
|
82
|
+
const applyToolsRenderConfig = (config: import("../domain/config-types.js").NormalizedPiStyleConfig) => {
|
|
83
|
+
setToolsRenderConfig({
|
|
84
|
+
...config.tools,
|
|
85
|
+
batchOpenGlyph: resolveTheme(sessionTheme as never, config, process.env).glyph("batchOpen"),
|
|
86
|
+
nerdFonts: resolveTheme(sessionTheme as never, config, process.env).mode === "nerd",
|
|
87
|
+
} satisfies ToolsRenderConfig);
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Hide Pi's "Thinking..." placeholder label: an empty label renders zero
|
|
91
|
+
* lines, so the thinking block leaves no trace while content stays hidden.
|
|
92
|
+
* Passing undefined restores the default label.
|
|
93
|
+
*/
|
|
94
|
+
const applyMessagesConfig = (config: import("../domain/config-types.js").NormalizedPiStyleConfig) => {
|
|
95
|
+
sessionUi?.setHiddenThinkingLabel?.(config.messages.hideThinkingLabel ? "" : undefined);
|
|
96
|
+
};
|
|
71
97
|
const app: PiStyleApp = createPiStyleApp(
|
|
72
98
|
undefined,
|
|
73
99
|
{
|
|
@@ -79,6 +105,10 @@ export function createPiStyleSessionCoordinator(pi: ExtensionAPI, hooks: Compati
|
|
|
79
105
|
(config) => {
|
|
80
106
|
productGate = app.productPolicy.corePatchGate;
|
|
81
107
|
if (!active) return;
|
|
108
|
+
// Apply render-scoped tool config live so `/pi-style set tools.*` takes
|
|
109
|
+
// effect immediately (line budgets, dimOutput, open-tree glyph, …).
|
|
110
|
+
applyToolsRenderConfig(config);
|
|
111
|
+
applyMessagesConfig(config);
|
|
82
112
|
if (compatibility.report) {
|
|
83
113
|
const cleanup = compatibility.dispose();
|
|
84
114
|
if (!cleanup.complete) {
|
|
@@ -100,7 +130,6 @@ export function createPiStyleSessionCoordinator(pi: ExtensionAPI, hooks: Compati
|
|
|
100
130
|
authorization = readSessionAuthorization(pi);
|
|
101
131
|
compatibility.captureAuthorization(
|
|
102
132
|
authorization.core,
|
|
103
|
-
authorization.user,
|
|
104
133
|
authorization.assistant,
|
|
105
134
|
authorization.specialBlocks,
|
|
106
135
|
authorization.tools,
|
|
@@ -120,6 +149,14 @@ export function createPiStyleSessionCoordinator(pi: ExtensionAPI, hooks: Compati
|
|
|
120
149
|
tuiSession = ctx.mode === "tui";
|
|
121
150
|
app.setProjectTrusted(ctx.isProjectTrusted());
|
|
122
151
|
source.setSession(cwd, ctx.isProjectTrusted());
|
|
152
|
+
// Drop any batch state carried over from the previous session (Pi renders
|
|
153
|
+
// the restored chat between session_shutdown and the next session_start).
|
|
154
|
+
resetBatchRegistry();
|
|
155
|
+
resetGrepRegistry();
|
|
156
|
+
resetBashTreeRegistry();
|
|
157
|
+
// Stop any 1s elapsed re-render ticker left by a tool that was still
|
|
158
|
+
// running when the session ended.
|
|
159
|
+
stopAllElapsedTickers();
|
|
123
160
|
active = false;
|
|
124
161
|
await app.reload();
|
|
125
162
|
productGate = app.productPolicy.corePatchGate;
|
|
@@ -127,7 +164,10 @@ export function createPiStyleSessionCoordinator(pi: ExtensionAPI, hooks: Compati
|
|
|
127
164
|
compatibility.install(app.config, ctx.mode === "tui", productGate);
|
|
128
165
|
// Session-scoped render configuration for the boxed tool/message surfaces.
|
|
129
166
|
// Populated once per session (never inside render).
|
|
130
|
-
|
|
167
|
+
sessionTheme = ctx.ui?.theme as never;
|
|
168
|
+
sessionUi = ctx.ui as import("@earendil-works/pi-coding-agent").ExtensionUIContext | undefined;
|
|
169
|
+
applyToolsRenderConfig(app.config);
|
|
170
|
+
applyMessagesConfig(app.config);
|
|
131
171
|
if (ctx.ui?.theme) setSpecialBlockTheme(ctx.ui.theme as never);
|
|
132
172
|
const toolDetails = collectToolDetails(pi.getActiveTools?.(), pi.getAllTools?.());
|
|
133
173
|
app.sessionStart(
|
|
@@ -180,6 +220,10 @@ export function createPiStyleSessionCoordinator(pi: ExtensionAPI, hooks: Compati
|
|
|
180
220
|
tuiSession = false;
|
|
181
221
|
terminalInputUnsubscribe?.();
|
|
182
222
|
terminalInputUnsubscribe = undefined;
|
|
223
|
+
resetBatchRegistry();
|
|
224
|
+
resetGrepRegistry();
|
|
225
|
+
resetBashTreeRegistry();
|
|
226
|
+
stopAllElapsedTickers();
|
|
183
227
|
app.sessionShutdown();
|
|
184
228
|
// Tier C prototype patches stay installed across session switches. Pi renders
|
|
185
229
|
// the restored chat (renderBeforeBind) AFTER session_shutdown but BEFORE the
|
|
@@ -43,10 +43,26 @@ export interface BoxedRenderOptions {
|
|
|
43
43
|
isError?: boolean;
|
|
44
44
|
isPartial?: boolean;
|
|
45
45
|
isPending?: boolean;
|
|
46
|
+
/** Execution has started but the tool is still running (title `◌` instead of `✓`). */
|
|
47
|
+
running?: boolean;
|
|
48
|
+
/** A result renderer already produced a continuation for this call, so the call
|
|
49
|
+
* leaves the box open instead of closing it with a pending label. */
|
|
50
|
+
resultSeen?: boolean;
|
|
46
51
|
pendingText?: string;
|
|
52
|
+
/** Verbatim bottom-border label for the pending/running card (overrides the
|
|
53
|
+
* `… ${pendingText}` default, e.g. a live `◌ Running · 12.4s` status). */
|
|
54
|
+
pendingLabel?: string;
|
|
47
55
|
state?: Record<string, unknown>;
|
|
48
56
|
/** Wall-clock elapsed override (used when metrics are not in result.details). */
|
|
49
57
|
elapsedMs?: number;
|
|
58
|
+
/** Width-dependent content lines rendered between the top border and the
|
|
59
|
+
* footer border of a compact box (replaces the blank breathing line).
|
|
60
|
+
* Each returned line is truncated to the box inner width by the renderer.
|
|
61
|
+
* Returns an empty array for no body (blank line preserved). */
|
|
62
|
+
bodyLines?: (contentWidth: number) => string[];
|
|
63
|
+
/** Right-side label embedded in the compact box bottom border before the
|
|
64
|
+
* corner (e.g. an expand hint such as `Ctrl+O for more`). */
|
|
65
|
+
bottomRightLabel?: string;
|
|
50
66
|
}
|
|
51
67
|
|
|
52
68
|
export function isExpanded(options: { expanded?: boolean } | undefined): boolean {
|
|
@@ -133,7 +149,7 @@ export function countWords(text: string): number {
|
|
|
133
149
|
return count;
|
|
134
150
|
}
|
|
135
151
|
|
|
136
|
-
function formatCompactCount(value: number): string {
|
|
152
|
+
export function formatCompactCount(value: number): string {
|
|
137
153
|
if (value < 1000) return `${Math.round(value)}`;
|
|
138
154
|
if (value < 10000) return `${(value / 1000).toFixed(1)}k`;
|
|
139
155
|
if (value < 1000000) return `${Math.round(value / 1000)}k`;
|
|
@@ -323,17 +339,47 @@ function formatBoxedStatusIcon(theme: BoxTheme, isError?: boolean): string {
|
|
|
323
339
|
return theme.fg(isError ? "error" : "success", icon);
|
|
324
340
|
}
|
|
325
341
|
|
|
326
|
-
|
|
327
|
-
|
|
342
|
+
/**
|
|
343
|
+
* Colored `➔ Name` prefix for tool titles (identity color). The status glyph
|
|
344
|
+
* (✓/◌/✗) is appended separately by formatBoxedToolTitle.
|
|
345
|
+
*/
|
|
346
|
+
export function formatToolTitlePrefix(theme: BoxTheme, name: string): string {
|
|
347
|
+
return colorFromExtra(theme, "bashPromptColor", "bashMode", `➔ ${name}`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export type BoxedTitleStatus = "running" | "pending";
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Boxed tool title: `➔ Name ✓` when settled, `➔ Name ◌` while running, plain
|
|
354
|
+
* `➔ Name` while pending, and a fully error-colored `➔ Name ✗` on failure.
|
|
355
|
+
* The ✓/◌ glyphs are never shown before the tool settles, so a card that is
|
|
356
|
+
* still executing never reads as finished.
|
|
357
|
+
*/
|
|
358
|
+
export function formatBoxedToolTitle(
|
|
359
|
+
theme: BoxTheme,
|
|
360
|
+
name: string,
|
|
361
|
+
isError?: boolean,
|
|
362
|
+
status?: BoxedTitleStatus,
|
|
363
|
+
): string {
|
|
328
364
|
// On failure the whole title turns error-colored (not just the ✗) so a failed
|
|
329
365
|
// tool reads instantly; on success the tool keeps its identity color and only
|
|
330
366
|
// the ✓ carries the success color.
|
|
331
367
|
const coloredTitle = isError
|
|
332
|
-
? theme.fg("error",
|
|
333
|
-
:
|
|
368
|
+
? theme.fg("error", `➔ ${name} ✗`)
|
|
369
|
+
: status === "running"
|
|
370
|
+
? `${formatToolTitlePrefix(theme, name)} ${theme.fg("text", "◌")}`
|
|
371
|
+
: status === "pending"
|
|
372
|
+
? formatToolTitlePrefix(theme, name)
|
|
373
|
+
: `${formatToolTitlePrefix(theme, name)} ${formatBoxedStatusIcon(theme, false)}`;
|
|
334
374
|
return typeof theme?.bold === "function" ? theme.bold(coloredTitle) : coloredTitle;
|
|
335
375
|
}
|
|
336
376
|
|
|
377
|
+
/** Live running status label for pending/running cards and streaming footers. */
|
|
378
|
+
export function formatBoxedRunningStatus(theme: BoxTheme, elapsedMs: number | undefined): string {
|
|
379
|
+
const elapsed = elapsedMs === undefined ? "" : `${theme.fg("text", ` · ${(elapsedMs / 1000).toFixed(1)}s`)}`;
|
|
380
|
+
return `${theme.fg("dim", "◌ Running")}${elapsed}`;
|
|
381
|
+
}
|
|
382
|
+
|
|
337
383
|
function boxText(theme: BoxTheme, text: string): string {
|
|
338
384
|
return `${RESET_INTENSITY}${theme.fg("borderMuted", text)}`;
|
|
339
385
|
}
|
|
@@ -423,6 +469,22 @@ export function boxBlankLine(theme: BoxTheme, width: number): string {
|
|
|
423
469
|
return `${boxFrameText(theme, BOX_VERTICAL)}${sidePad}${" ".repeat(contentWidth)}${sidePad}${boxFrameText(theme, BOX_VERTICAL)}`;
|
|
424
470
|
}
|
|
425
471
|
|
|
472
|
+
export function boxLineAligned(theme: BoxTheme, left: string, right: string, width: number): string {
|
|
473
|
+
const renderedWidth = boxWidth(width);
|
|
474
|
+
const contentWidth = boxInnerWidth(renderedWidth);
|
|
475
|
+
const rightWidth = safeVisibleWidth(right);
|
|
476
|
+
const sidePad = " ".repeat(BOX_SIDE_PADDING);
|
|
477
|
+
|
|
478
|
+
if (!right || rightWidth >= contentWidth) {
|
|
479
|
+
return boxLine(theme, right || left, renderedWidth);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const maxLeftWidth = Math.max(1, contentWidth - rightWidth - 1);
|
|
483
|
+
const truncatedLeft = safeTruncateToWidth(left, maxLeftWidth, "…");
|
|
484
|
+
const gap = " ".repeat(Math.max(1, contentWidth - safeVisibleWidth(truncatedLeft) - rightWidth));
|
|
485
|
+
return `${boxFrameText(theme, BOX_VERTICAL)}${sidePad}${truncatedLeft}${gap}${right}${sidePad}${boxFrameText(theme, BOX_VERTICAL)}`;
|
|
486
|
+
}
|
|
487
|
+
|
|
426
488
|
export function boxLineWithRight(theme: BoxTheme, left: string, right: string, width: number): string {
|
|
427
489
|
const renderedWidth = boxWidth(width);
|
|
428
490
|
const contentWidth = boxInnerWidth(renderedWidth);
|
|
@@ -540,7 +602,12 @@ export function renderBoxedToolCall(
|
|
|
540
602
|
},
|
|
541
603
|
render(width: number): string[] {
|
|
542
604
|
if (cache?.width === width) return cache.lines;
|
|
543
|
-
const title = formatBoxedToolTitle(
|
|
605
|
+
const title = formatBoxedToolTitle(
|
|
606
|
+
theme,
|
|
607
|
+
toolName,
|
|
608
|
+
options.isError,
|
|
609
|
+
options.isPending ? (options.running ? "running" : "pending") : undefined,
|
|
610
|
+
);
|
|
544
611
|
const headerLabel = options.headerDetail ? `${title} · ${options.headerDetail}` : title;
|
|
545
612
|
const renderedWidth = boxWidth(width);
|
|
546
613
|
const lines = [
|
|
@@ -548,22 +615,26 @@ export function renderBoxedToolCall(
|
|
|
548
615
|
boxBlankLine(theme, renderedWidth),
|
|
549
616
|
...detailLines.flatMap((line) => boxedWrappedLines(theme, line, renderedWidth)),
|
|
550
617
|
];
|
|
551
|
-
if (options.isPending) {
|
|
552
|
-
|
|
618
|
+
if (options.isPending && !options.resultSeen) {
|
|
619
|
+
// Pending/running card: close the box with the status label. Once a
|
|
620
|
+
// result renderer has produced a continuation, the box stays open and
|
|
621
|
+
// that continuation closes it.
|
|
622
|
+
const pendingLabel =
|
|
623
|
+
options.pendingLabel ?? theme.fg("dim", `… ${options.pendingText ?? "Waiting for output…"}`);
|
|
553
624
|
lines.push(
|
|
554
625
|
boxBlankLine(theme, renderedWidth),
|
|
555
626
|
boxLabeledBorder(
|
|
556
627
|
theme,
|
|
557
628
|
BOX_ROUND_BOTTOM_LEFT,
|
|
558
629
|
BOX_ROUND_BOTTOM_RIGHT,
|
|
559
|
-
|
|
630
|
+
pendingLabel,
|
|
560
631
|
undefined,
|
|
561
632
|
renderedWidth,
|
|
562
633
|
),
|
|
563
634
|
);
|
|
564
635
|
} else {
|
|
565
636
|
// Leave the box open with trailing breathing room; the result renderer
|
|
566
|
-
// continues it with the
|
|
637
|
+
// continues it with the result divider.
|
|
567
638
|
lines.push(boxBlankLine(theme, renderedWidth));
|
|
568
639
|
}
|
|
569
640
|
cache = { width, lines };
|
|
@@ -593,15 +664,23 @@ export function renderCompactBoxedToolCall(
|
|
|
593
664
|
invalidate() {},
|
|
594
665
|
render(width: number): string[] {
|
|
595
666
|
const renderedWidth = boxWidth(width);
|
|
596
|
-
const title = formatBoxedToolTitle(
|
|
667
|
+
const title = formatBoxedToolTitle(
|
|
668
|
+
theme,
|
|
669
|
+
toolName,
|
|
670
|
+
options.isError,
|
|
671
|
+
options.isPending ? (options.running ? "running" : "pending") : undefined,
|
|
672
|
+
);
|
|
597
673
|
const headerLabel = detailLine ? `${title} · ${detailLine}` : title;
|
|
598
674
|
const compactFooter =
|
|
599
675
|
typeof options.state?.[COMPACT_FOOTER_KEY] === "string" ? options.state[COMPACT_FOOTER_KEY] : "";
|
|
600
676
|
const _footerIsError = Boolean(options.state?.[COMPACT_FOOTER_ERROR_KEY]);
|
|
601
677
|
const _footerIsPartial = Boolean(options.state?.[COMPACT_FOOTER_PARTIAL_KEY]);
|
|
678
|
+
const bodyLines = options.bodyLines ? options.bodyLines(boxInnerWidth(renderedWidth)) : [];
|
|
602
679
|
const lines = [
|
|
603
680
|
boxLabeledBorder(theme, BOX_ROUND_TOP_LEFT, BOX_ROUND_TOP_RIGHT, headerLabel, undefined, renderedWidth),
|
|
604
|
-
|
|
681
|
+
...(bodyLines.length > 0
|
|
682
|
+
? bodyLines.map((line) => boxLine(theme, line, renderedWidth))
|
|
683
|
+
: [boxBlankLine(theme, renderedWidth)]),
|
|
605
684
|
];
|
|
606
685
|
if (compactFooter) {
|
|
607
686
|
lines.push(
|
|
@@ -610,24 +689,28 @@ export function renderCompactBoxedToolCall(
|
|
|
610
689
|
BOX_ROUND_BOTTOM_LEFT,
|
|
611
690
|
BOX_ROUND_BOTTOM_RIGHT,
|
|
612
691
|
compactFooter,
|
|
613
|
-
|
|
692
|
+
options.bottomRightLabel,
|
|
614
693
|
renderedWidth,
|
|
615
694
|
),
|
|
616
695
|
);
|
|
617
696
|
} else if (options.isPending) {
|
|
618
|
-
const
|
|
697
|
+
const pendingLabel =
|
|
698
|
+
options.pendingLabel ??
|
|
699
|
+
(options.running
|
|
700
|
+
? formatBoxedRunningStatus(theme, undefined)
|
|
701
|
+
: theme.fg("dim", `… ${options.pendingText ?? "Waiting for output…"}`));
|
|
619
702
|
lines.push(
|
|
620
703
|
boxLabeledBorder(
|
|
621
704
|
theme,
|
|
622
705
|
BOX_ROUND_BOTTOM_LEFT,
|
|
623
706
|
BOX_ROUND_BOTTOM_RIGHT,
|
|
624
|
-
|
|
625
|
-
|
|
707
|
+
pendingLabel,
|
|
708
|
+
options.bottomRightLabel,
|
|
626
709
|
renderedWidth,
|
|
627
710
|
),
|
|
628
711
|
);
|
|
629
712
|
} else {
|
|
630
|
-
// No footer yet (transient, or the result opens the
|
|
713
|
+
// No footer yet (transient, or the result opens the result divider):
|
|
631
714
|
// leave the box open so the result renderer continues the same box.
|
|
632
715
|
}
|
|
633
716
|
return lines;
|
|
@@ -655,6 +738,10 @@ export function renderBoxedToolResult(
|
|
|
655
738
|
expandHint?: string;
|
|
656
739
|
isError?: boolean;
|
|
657
740
|
isPartial?: boolean;
|
|
741
|
+
/** Skip the result divider entirely (streaming continuation into an open call box). */
|
|
742
|
+
showDivider?: boolean;
|
|
743
|
+
/** Error state marker prepended to the body (default `✗ Error`). */
|
|
744
|
+
errorLabel?: string;
|
|
658
745
|
} = {},
|
|
659
746
|
): Component {
|
|
660
747
|
let cache: RenderLinesCache | null = null;
|
|
@@ -668,7 +755,7 @@ export function renderBoxedToolResult(
|
|
|
668
755
|
const renderedWidth = boxWidth(width);
|
|
669
756
|
const maxContentWidth = boxInnerWidth(renderedWidth);
|
|
670
757
|
const bodyLines = typeof body === "function" ? body(maxContentWidth) : body.render(maxContentWidth);
|
|
671
|
-
const errorPrefix = options.isError ? [theme.fg("error", "✗ Error")] : [];
|
|
758
|
+
const errorPrefix = options.isError ? [theme.fg("error", options.errorLabel ?? "✗ Error")] : [];
|
|
672
759
|
const outputLines =
|
|
673
760
|
bodyLines.length > 0
|
|
674
761
|
? [...errorPrefix, ...bodyLines]
|
|
@@ -679,14 +766,18 @@ export function renderBoxedToolResult(
|
|
|
679
766
|
? options.dividerLabel(renderedWidth)
|
|
680
767
|
: (options.dividerLabel ?? "Response");
|
|
681
768
|
const rendered = [
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
769
|
+
...(options.showDivider === false
|
|
770
|
+
? []
|
|
771
|
+
: [
|
|
772
|
+
boxLabeledBorder(
|
|
773
|
+
theme,
|
|
774
|
+
BOX_DIVIDER_LEFT,
|
|
775
|
+
BOX_DIVIDER_RIGHT,
|
|
776
|
+
theme.fg("dim", dividerText),
|
|
777
|
+
options.dividerRightLabel ? theme.fg("dim", options.dividerRightLabel) : undefined,
|
|
778
|
+
renderedWidth,
|
|
779
|
+
),
|
|
780
|
+
]),
|
|
690
781
|
boxBlankLine(theme, renderedWidth),
|
|
691
782
|
...renderBoxedOutputLines(theme, outputLines, renderedWidth, options.renderLineBudget ?? outputLines.length),
|
|
692
783
|
boxBlankLine(theme, renderedWidth),
|
|
@@ -20,8 +20,6 @@ const extensionDir = dirname(fileURLToPath(import.meta.url));
|
|
|
20
20
|
export const THEME_EXTRA_DEFAULTS: Readonly<Record<string, string>> = Object.freeze({
|
|
21
21
|
assistantPrefix: "•",
|
|
22
22
|
assistantPrefixColor: "",
|
|
23
|
-
userPrefix: "❯",
|
|
24
|
-
userPrefixColor: "accent",
|
|
25
23
|
dividerChar: "─",
|
|
26
24
|
dividerColor: "",
|
|
27
25
|
showDivider: "true",
|