@quandev104/pi-style 0.2.7 → 0.2.10
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 +23 -0
- package/README.md +2 -2
- package/dist/extensions/pi-style.js +8900 -8464
- package/dist/extensions/pi-style.js.map +1 -1
- package/extension-src/pi-style/app/command-service.ts +2 -0
- package/extension-src/pi-style/domain/config-normalization.ts +3 -0
- package/extension-src/pi-style/domain/config-types.ts +8 -0
- package/extension-src/pi-style/features/messages/index.ts +451 -37
- package/extension-src/pi-style/features/messages/special-blocks.ts +2 -22
- package/extension-src/pi-style/features/startup/index.ts +24 -4
- package/extension-src/pi-style/features/startup/logo.ts +22 -18
- package/extension-src/pi-style/features/tools/bash-execution.ts +24 -8
- package/extension-src/pi-style/features/tools/boxed/edit.ts +25 -30
- package/extension-src/pi-style/features/tools/boxed/git.ts +21 -13
- package/extension-src/pi-style/features/tools/boxed/quick-edit.ts +35 -38
- package/extension-src/pi-style/features/tools/boxed/shared.ts +34 -0
- package/extension-src/pi-style/features/tools/boxed/turn-summary.ts +114 -38
- package/extension-src/pi-style/features/tools/boxed/write.ts +1 -0
- package/extension-src/pi-style/features/tools/index.ts +10 -6
- package/extension-src/pi-style/pi/compatibility-coordinator.ts +2 -0
- package/extension-src/pi-style/pi/compatibility-probe.ts +88 -19
- package/extension-src/pi-style/pi/session-coordinator.ts +20 -1
- package/extension-src/pi-style/shared/ansi.ts +45 -0
- package/extension-src/pi-style/shared/box.ts +126 -47
- package/package.json +10 -9
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Two deliberate design points:
|
|
4
4
|
//
|
|
5
|
-
// 1.
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
5
|
+
// 1. A rendered box owns its background: boxed surfaces wrap every line in
|
|
6
|
+
// the semantic status fill (toolPendingBg / toolErrorBg / toolSuccessBg,
|
|
7
|
+
// or customMessageBg for message blocks), while boxless surfaces (quiet
|
|
8
|
+
// tool rows/tree panels, git/gh semantic cards, turn summaries) stay
|
|
9
|
+
// transparent. The native container fill is always neutralized so the
|
|
10
|
+
// tint always comes from the component that draws the frame — one tool
|
|
11
|
+
// can mix both (git: boxless card + boxed diff frames).
|
|
10
12
|
//
|
|
11
13
|
// 2. Theme functions are accessed through a minimal structural interface
|
|
12
14
|
// (BoxTheme) instead of `any`.
|
|
@@ -30,12 +32,31 @@ import { getThemeExtra } from "./theme-extras.js";
|
|
|
30
32
|
/** Minimal structural view of Pi's Theme as used by the boxed renderers. */
|
|
31
33
|
export interface BoxTheme {
|
|
32
34
|
fg(color: string, text: string): string;
|
|
35
|
+
bg?(color: string, text: string): string;
|
|
33
36
|
bold?(text: string): string;
|
|
34
37
|
italic?(text: string): string;
|
|
35
38
|
inverse?(text: string): string;
|
|
36
39
|
getColorMode?(): string;
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
/** Semantic status background name for a tool surface (mirrors Pi's native
|
|
43
|
+
* container fill selection). */
|
|
44
|
+
export function boxedToolBgName(isError?: boolean, isPartial?: boolean): string {
|
|
45
|
+
return isPartial ? "toolPendingBg" : isError ? "toolErrorBg" : "toolSuccessBg";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Wrap every line in the status background fill. Best-effort: themes
|
|
49
|
+
* without a bg() (or without the token) keep their lines untouched, and an
|
|
50
|
+
* empty theme color resolves to a no-op reset escape. */
|
|
51
|
+
export function applyBgTint(theme: BoxTheme, bgName: string, lines: string[]): string[] {
|
|
52
|
+
if (typeof theme.bg !== "function") return lines;
|
|
53
|
+
try {
|
|
54
|
+
return lines.map((line) => theme.bg?.(bgName, line) ?? line);
|
|
55
|
+
} catch {
|
|
56
|
+
return lines;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
39
60
|
const THEME_CACHE_KEYS = new WeakMap<object, number>();
|
|
40
61
|
let nextThemeCacheKey = 1;
|
|
41
62
|
|
|
@@ -49,8 +70,11 @@ export function themeCacheKey(theme: object): number {
|
|
|
49
70
|
|
|
50
71
|
export interface BoxedRenderOptions {
|
|
51
72
|
widthKey?: string;
|
|
52
|
-
/** Detail embedded in the top-border title after the tool name (e.g. the
|
|
53
|
-
|
|
73
|
+
/** Detail embedded in the top-border title after the tool name (e.g. the
|
|
74
|
+
* path). A function form is resolved lazily at render time so the header can
|
|
75
|
+
* pick up state a result renderer published in the same updateDisplay pass
|
|
76
|
+
* (e.g. diff stats: `path · +3 -0`). */
|
|
77
|
+
headerDetail?: string | (() => string);
|
|
54
78
|
isError?: boolean;
|
|
55
79
|
isPartial?: boolean;
|
|
56
80
|
isPending?: boolean;
|
|
@@ -74,6 +98,10 @@ export interface BoxedRenderOptions {
|
|
|
74
98
|
/** Right-side label embedded in the compact box bottom border before the
|
|
75
99
|
* corner (e.g. an expand hint such as `Ctrl+O for more`). */
|
|
76
100
|
bottomRightLabel?: string;
|
|
101
|
+
/** Tint the compact box with the semantic status background. Only framed
|
|
102
|
+
* compact surfaces (e.g. the write preview box) opt in — boxless compact
|
|
103
|
+
* surfaces (quiet-tool rows, tree panels) stay transparent. */
|
|
104
|
+
tint?: boolean;
|
|
77
105
|
}
|
|
78
106
|
|
|
79
107
|
export function isExpanded(options: { expanded?: boolean } | undefined): boolean {
|
|
@@ -461,15 +489,13 @@ export function dimLine(text: string): string {
|
|
|
461
489
|
return `\x1b[2m${text}\x1b[22m`;
|
|
462
490
|
}
|
|
463
491
|
|
|
464
|
-
|
|
465
|
-
|
|
492
|
+
/** Frame chrome (borders, side bars, dividers). Dim by default; error boxes
|
|
493
|
+
* pass a theme color name so the whole frame reads as failed at a glance. */
|
|
494
|
+
function boxText(_theme: BoxTheme, text: string, color?: string): string {
|
|
495
|
+
return color ? _theme.fg(color, text) : dimLine(text);
|
|
466
496
|
}
|
|
467
|
-
function boxFrameText(_theme: BoxTheme, text: string): string {
|
|
468
|
-
return dimLine(text);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
export function boxedToolBgName(isError?: boolean, isPartial?: boolean): string {
|
|
472
|
-
return isPartial ? "toolPendingBg" : isError ? "toolErrorBg" : "toolSuccessBg";
|
|
497
|
+
function boxFrameText(_theme: BoxTheme, text: string, color?: string): string {
|
|
498
|
+
return color ? _theme.fg(color, text) : dimLine(text);
|
|
473
499
|
}
|
|
474
500
|
|
|
475
501
|
export function boxBorder(theme: BoxTheme, left: string, right: string, width: number): string {
|
|
@@ -493,6 +519,7 @@ export function boxLabeledBorder(
|
|
|
493
519
|
leftLabel: string,
|
|
494
520
|
rightLabel: string | undefined,
|
|
495
521
|
width: number,
|
|
522
|
+
frameColor?: string,
|
|
496
523
|
): string {
|
|
497
524
|
const renderedWidth = boxWidth(width);
|
|
498
525
|
let left = leftLabel ?? "";
|
|
@@ -532,22 +559,26 @@ export function boxLabeledBorder(
|
|
|
532
559
|
// applying the border color to the whole line in one wrap would leave every
|
|
533
560
|
// dash after a label in the default color, making one border render with
|
|
534
561
|
// mixed brightness.
|
|
535
|
-
const parts: string[] = [boxFrameText(theme, `${start}${left ? "─ " : ""}
|
|
562
|
+
const parts: string[] = [boxFrameText(theme, `${start}${left ? "─ " : ""}`, frameColor)];
|
|
536
563
|
if (left) parts.push(left);
|
|
537
|
-
parts.push(boxFrameText(theme, `${left ? " " : ""}${BOX_HORIZONTAL.repeat(Math.max(0, fill))}
|
|
564
|
+
parts.push(boxFrameText(theme, `${left ? " " : ""}${BOX_HORIZONTAL.repeat(Math.max(0, fill))}`, frameColor));
|
|
538
565
|
if (right) {
|
|
539
|
-
parts.push(
|
|
566
|
+
parts.push(
|
|
567
|
+
boxFrameText(theme, " ", frameColor),
|
|
568
|
+
right,
|
|
569
|
+
boxFrameText(theme, ` ${BOX_HORIZONTAL.repeat(rightFill)}`, frameColor),
|
|
570
|
+
);
|
|
540
571
|
}
|
|
541
|
-
parts.push(boxFrameText(theme, end));
|
|
572
|
+
parts.push(boxFrameText(theme, end, frameColor));
|
|
542
573
|
return parts.join("");
|
|
543
574
|
}
|
|
544
575
|
|
|
545
576
|
/** Empty content line used for breathing room inside a box. */
|
|
546
|
-
export function boxBlankLine(theme: BoxTheme, width: number): string {
|
|
577
|
+
export function boxBlankLine(theme: BoxTheme, width: number, frameColor?: string): string {
|
|
547
578
|
const renderedWidth = boxWidth(width);
|
|
548
579
|
const contentWidth = boxInnerWidth(renderedWidth);
|
|
549
580
|
const sidePad = " ".repeat(BOX_SIDE_PADDING);
|
|
550
|
-
return `${boxFrameText(theme, BOX_VERTICAL)}${sidePad}${" ".repeat(contentWidth)}${sidePad}${boxFrameText(theme, BOX_VERTICAL)}`;
|
|
581
|
+
return `${boxFrameText(theme, BOX_VERTICAL, frameColor)}${sidePad}${" ".repeat(contentWidth)}${sidePad}${boxFrameText(theme, BOX_VERTICAL, frameColor)}`;
|
|
551
582
|
}
|
|
552
583
|
|
|
553
584
|
export function boxLineAligned(theme: BoxTheme, left: string, right: string, width: number): string {
|
|
@@ -584,19 +615,19 @@ export function boxLineWithRight(theme: BoxTheme, left: string, right: string, w
|
|
|
584
615
|
return `${boxFrameText(theme, BOX_VERTICAL)}${sidePad}${truncatedLeft}${gap}${divider}${right}${sidePad}${boxFrameText(theme, BOX_VERTICAL)}`;
|
|
585
616
|
}
|
|
586
617
|
|
|
587
|
-
export function boxLine(theme: BoxTheme, content: string, width: number): string {
|
|
618
|
+
export function boxLine(theme: BoxTheme, content: string, width: number, frameColor?: string): string {
|
|
588
619
|
const renderedWidth = boxWidth(width);
|
|
589
620
|
const contentWidth = boxInnerWidth(renderedWidth);
|
|
590
621
|
const fastContent = fastBoxLineContent(content, contentWidth);
|
|
591
622
|
const sidePad = " ".repeat(BOX_SIDE_PADDING);
|
|
592
623
|
if (fastContent) {
|
|
593
624
|
const fill = " ".repeat(Math.max(0, contentWidth - fastContent.visibleWidth));
|
|
594
|
-
return `${boxFrameText(theme, BOX_VERTICAL)}${sidePad}${fastContent.text}${fill}${sidePad}${boxFrameText(theme, BOX_VERTICAL)}`;
|
|
625
|
+
return `${boxFrameText(theme, BOX_VERTICAL, frameColor)}${sidePad}${fastContent.text}${fill}${sidePad}${boxFrameText(theme, BOX_VERTICAL, frameColor)}`;
|
|
595
626
|
}
|
|
596
627
|
|
|
597
628
|
const truncated = safeTruncateToWidth(content, contentWidth, "…");
|
|
598
629
|
const fill = " ".repeat(Math.max(0, contentWidth - safeVisibleWidth(truncated)));
|
|
599
|
-
return `${boxFrameText(theme, BOX_VERTICAL)}${sidePad}${truncated}${fill}${sidePad}${boxFrameText(theme, BOX_VERTICAL)}`;
|
|
630
|
+
return `${boxFrameText(theme, BOX_VERTICAL, frameColor)}${sidePad}${truncated}${fill}${sidePad}${boxFrameText(theme, BOX_VERTICAL, frameColor)}`;
|
|
600
631
|
}
|
|
601
632
|
|
|
602
633
|
export function boxInsetDivider(theme: BoxTheme, width: number): string {
|
|
@@ -606,12 +637,12 @@ export function boxInsetDivider(theme: BoxTheme, width: number): string {
|
|
|
606
637
|
return `${boxFrameText(theme, BOX_VERTICAL)}${sidePad}${boxText(theme, BOX_HORIZONTAL.repeat(lineWidth))}${sidePad}${boxFrameText(theme, BOX_VERTICAL)}`;
|
|
607
638
|
}
|
|
608
639
|
|
|
609
|
-
export function boxedWrappedLines(theme: BoxTheme, content: string, width: number): string[] {
|
|
610
|
-
return safeWrapTextWithAnsi(content, boxInnerWidth(width)).map((line) => boxLine(theme, line, width));
|
|
640
|
+
export function boxedWrappedLines(theme: BoxTheme, content: string, width: number, frameColor?: string): string[] {
|
|
641
|
+
return safeWrapTextWithAnsi(content, boxInnerWidth(width)).map((line) => boxLine(theme, line, width, frameColor));
|
|
611
642
|
}
|
|
612
643
|
|
|
613
|
-
function boxedTruncatedLine(theme: BoxTheme, content: string, width: number): string {
|
|
614
|
-
return boxLine(theme, safeTruncateToWidth(content, boxInnerWidth(width), "…"), width);
|
|
644
|
+
function boxedTruncatedLine(theme: BoxTheme, content: string, width: number, frameColor?: string): string {
|
|
645
|
+
return boxLine(theme, safeTruncateToWidth(content, boxInnerWidth(width), "…"), width, frameColor);
|
|
615
646
|
}
|
|
616
647
|
|
|
617
648
|
type RenderLinesCache = {
|
|
@@ -635,6 +666,7 @@ function renderBoxedOutputLines(
|
|
|
635
666
|
outputLines: string[],
|
|
636
667
|
width: number,
|
|
637
668
|
rawLineBudget = DEFAULT_COLLAPSED_RENDER_LINES,
|
|
669
|
+
frameColor?: string,
|
|
638
670
|
): string[] {
|
|
639
671
|
const budget = boxedResultRenderBudget(rawLineBudget);
|
|
640
672
|
const headLimit = Math.max(0, Math.min(budget.headLines, budget.maxRenderedLines));
|
|
@@ -651,7 +683,7 @@ function renderBoxedOutputLines(
|
|
|
651
683
|
const fragments = (outputLines[nextInputIndex] ?? "").split("\n");
|
|
652
684
|
let headExceeded = false;
|
|
653
685
|
for (const fragment of fragments) {
|
|
654
|
-
const line = boxedTruncatedLine(theme, fragment, width);
|
|
686
|
+
const line = boxedTruncatedLine(theme, fragment, width, frameColor);
|
|
655
687
|
if (!pushBoundedLines(head, [line], headLimit)) {
|
|
656
688
|
headExceeded = true;
|
|
657
689
|
break;
|
|
@@ -671,7 +703,7 @@ function renderBoxedOutputLines(
|
|
|
671
703
|
for (let i = tailStart; i < outputLines.length; i++) {
|
|
672
704
|
const fragments = (outputLines[i] ?? "").split("\n");
|
|
673
705
|
for (const fragment of fragments) {
|
|
674
|
-
const line = boxedTruncatedLine(theme, fragment, width);
|
|
706
|
+
const line = boxedTruncatedLine(theme, fragment, width, frameColor);
|
|
675
707
|
tail.push(line);
|
|
676
708
|
if (tail.length > tailLimit) tail.splice(0, tail.length - tailLimit);
|
|
677
709
|
}
|
|
@@ -682,7 +714,7 @@ function renderBoxedOutputLines(
|
|
|
682
714
|
skippedInputLines > 0
|
|
683
715
|
? `… rendered output truncated; ${skippedInputLines} input lines skipped before tail`
|
|
684
716
|
: "… rendered output truncated";
|
|
685
|
-
return [...head, boxLine(theme, theme.fg("muted", skippedText), width), ...tail];
|
|
717
|
+
return [...head, boxLine(theme, theme.fg("muted", skippedText), width, frameColor), ...tail];
|
|
686
718
|
}
|
|
687
719
|
|
|
688
720
|
export function renderBoxedToolCall(
|
|
@@ -704,12 +736,23 @@ export function renderBoxedToolCall(
|
|
|
704
736
|
options.isError,
|
|
705
737
|
options.isPending ? (options.running ? "running" : "pending") : undefined,
|
|
706
738
|
);
|
|
707
|
-
const
|
|
739
|
+
const headerDetail = typeof options.headerDetail === "function" ? options.headerDetail() : options.headerDetail;
|
|
740
|
+
const headerLabel = headerDetail ? `${title} · ${headerDetail}` : title;
|
|
708
741
|
const renderedWidth = boxWidth(width);
|
|
742
|
+
// A failed call renders its whole frame in the error color.
|
|
743
|
+
const frameColor = options.isError ? "error" : undefined;
|
|
709
744
|
const lines = [
|
|
710
|
-
boxLabeledBorder(
|
|
711
|
-
|
|
712
|
-
|
|
745
|
+
boxLabeledBorder(
|
|
746
|
+
theme,
|
|
747
|
+
BOX_ROUND_TOP_LEFT,
|
|
748
|
+
BOX_ROUND_TOP_RIGHT,
|
|
749
|
+
headerLabel,
|
|
750
|
+
undefined,
|
|
751
|
+
renderedWidth,
|
|
752
|
+
frameColor,
|
|
753
|
+
),
|
|
754
|
+
boxBlankLine(theme, renderedWidth, frameColor),
|
|
755
|
+
...detailLines.flatMap((line) => boxedWrappedLines(theme, line, renderedWidth, frameColor)),
|
|
713
756
|
];
|
|
714
757
|
if (options.isPending && !options.resultSeen) {
|
|
715
758
|
// Pending/running card: close the box with the status label. Once a
|
|
@@ -718,7 +761,7 @@ export function renderBoxedToolCall(
|
|
|
718
761
|
const pendingLabel =
|
|
719
762
|
options.pendingLabel ?? theme.fg("dim", `… ${options.pendingText ?? "Waiting for output…"}`);
|
|
720
763
|
lines.push(
|
|
721
|
-
boxBlankLine(theme, renderedWidth),
|
|
764
|
+
boxBlankLine(theme, renderedWidth, frameColor),
|
|
722
765
|
boxLabeledBorder(
|
|
723
766
|
theme,
|
|
724
767
|
BOX_ROUND_BOTTOM_LEFT,
|
|
@@ -726,15 +769,19 @@ export function renderBoxedToolCall(
|
|
|
726
769
|
pendingLabel,
|
|
727
770
|
undefined,
|
|
728
771
|
renderedWidth,
|
|
772
|
+
frameColor,
|
|
729
773
|
),
|
|
730
774
|
);
|
|
731
775
|
} else {
|
|
732
776
|
// Leave the box open with trailing breathing room; the result renderer
|
|
733
777
|
// continues it with the result divider.
|
|
734
|
-
lines.push(boxBlankLine(theme, renderedWidth));
|
|
778
|
+
lines.push(boxBlankLine(theme, renderedWidth, frameColor));
|
|
735
779
|
}
|
|
736
|
-
|
|
737
|
-
|
|
780
|
+
// A rendered call box owns its status tint (the container fill is
|
|
781
|
+
// neutralized — see tighten/neutralize in features/tools).
|
|
782
|
+
const tinted = applyBgTint(theme, boxedToolBgName(options.isError, options.isPartial), lines);
|
|
783
|
+
cache = { width, lines: tinted };
|
|
784
|
+
return tinted;
|
|
738
785
|
},
|
|
739
786
|
};
|
|
740
787
|
}
|
|
@@ -772,11 +819,20 @@ export function renderCompactBoxedToolCall(
|
|
|
772
819
|
const _footerIsError = Boolean(options.state?.[COMPACT_FOOTER_ERROR_KEY]);
|
|
773
820
|
const _footerIsPartial = Boolean(options.state?.[COMPACT_FOOTER_PARTIAL_KEY]);
|
|
774
821
|
const bodyLines = options.bodyLines ? options.bodyLines(boxInnerWidth(renderedWidth)) : [];
|
|
822
|
+
const frameColor = options.isError ? "error" : undefined;
|
|
775
823
|
const lines = [
|
|
776
|
-
boxLabeledBorder(
|
|
824
|
+
boxLabeledBorder(
|
|
825
|
+
theme,
|
|
826
|
+
BOX_ROUND_TOP_LEFT,
|
|
827
|
+
BOX_ROUND_TOP_RIGHT,
|
|
828
|
+
headerLabel,
|
|
829
|
+
undefined,
|
|
830
|
+
renderedWidth,
|
|
831
|
+
frameColor,
|
|
832
|
+
),
|
|
777
833
|
...(bodyLines.length > 0
|
|
778
|
-
? bodyLines.map((line) => boxLine(theme, line, renderedWidth))
|
|
779
|
-
: [boxBlankLine(theme, renderedWidth)]),
|
|
834
|
+
? bodyLines.map((line) => boxLine(theme, line, renderedWidth, frameColor))
|
|
835
|
+
: [boxBlankLine(theme, renderedWidth, frameColor)]),
|
|
780
836
|
];
|
|
781
837
|
if (compactFooter) {
|
|
782
838
|
lines.push(
|
|
@@ -787,6 +843,7 @@ export function renderCompactBoxedToolCall(
|
|
|
787
843
|
compactFooter,
|
|
788
844
|
options.bottomRightLabel,
|
|
789
845
|
renderedWidth,
|
|
846
|
+
frameColor,
|
|
790
847
|
),
|
|
791
848
|
);
|
|
792
849
|
} else if (options.isPending) {
|
|
@@ -803,13 +860,17 @@ export function renderCompactBoxedToolCall(
|
|
|
803
860
|
pendingLabel,
|
|
804
861
|
options.bottomRightLabel,
|
|
805
862
|
renderedWidth,
|
|
863
|
+
frameColor,
|
|
806
864
|
),
|
|
807
865
|
);
|
|
808
866
|
} else {
|
|
809
867
|
// No footer yet (transient, or the result opens the result divider):
|
|
810
868
|
// leave the box open so the result renderer continues the same box.
|
|
811
869
|
}
|
|
812
|
-
|
|
870
|
+
// Compact boxes tint only when the surface is a real framed box (e.g.
|
|
871
|
+
// the write preview) — boxless compact surfaces (quiet-tool rows,
|
|
872
|
+
// tree panels) stay transparent.
|
|
873
|
+
return options.tint ? applyBgTint(theme, boxedToolBgName(options.isError, options.isPartial), lines) : lines;
|
|
813
874
|
},
|
|
814
875
|
};
|
|
815
876
|
}
|
|
@@ -830,6 +891,10 @@ export function renderBoxedToolResult(
|
|
|
830
891
|
dividerLabel?: string | ((width: number) => string);
|
|
831
892
|
/** Right-side label embedded in the divider between the call and the result. */
|
|
832
893
|
dividerRightLabel?: string;
|
|
894
|
+
/** Skip the blank line between the divider (or box top) and the body — used
|
|
895
|
+
* when the enclosing call box already left breathing room, e.g. the
|
|
896
|
+
* divider-less diff results whose stats live in the box header. */
|
|
897
|
+
skipLeadingBlank?: boolean;
|
|
833
898
|
/** Right-side label embedded in the bottom border (e.g. the expand hint). */
|
|
834
899
|
expandHint?: string;
|
|
835
900
|
isError?: boolean;
|
|
@@ -838,6 +903,10 @@ export function renderBoxedToolResult(
|
|
|
838
903
|
showDivider?: boolean;
|
|
839
904
|
/** Error state marker prepended to the body (default `✗ Error`). */
|
|
840
905
|
errorLabel?: string;
|
|
906
|
+
/** Tint the result box with the semantic status background. Default on —
|
|
907
|
+
* every current caller draws a framed box; pass false for a boxless
|
|
908
|
+
* continuation. */
|
|
909
|
+
tint?: boolean;
|
|
841
910
|
} = {},
|
|
842
911
|
): Component {
|
|
843
912
|
let cache: RenderLinesCache | null = null;
|
|
@@ -861,6 +930,8 @@ export function renderBoxedToolResult(
|
|
|
861
930
|
// rows instead of one "line" whose embedded rows break the frame.
|
|
862
931
|
const outputFragments = outputLines.flatMap((line) => line.split("\n"));
|
|
863
932
|
const footerText = (options.footerLines ?? []).join(" · ");
|
|
933
|
+
// A failed result renders its whole frame in the error color.
|
|
934
|
+
const frameColor = options.isError ? "error" : undefined;
|
|
864
935
|
const dividerText =
|
|
865
936
|
typeof options.dividerLabel === "function"
|
|
866
937
|
? options.dividerLabel(renderedWidth)
|
|
@@ -876,16 +947,18 @@ export function renderBoxedToolResult(
|
|
|
876
947
|
theme.fg("dim", dividerText),
|
|
877
948
|
options.dividerRightLabel ? theme.fg("dim", options.dividerRightLabel) : undefined,
|
|
878
949
|
renderedWidth,
|
|
950
|
+
frameColor,
|
|
879
951
|
),
|
|
880
952
|
]),
|
|
881
|
-
boxBlankLine(theme, renderedWidth),
|
|
953
|
+
...(options.skipLeadingBlank ? [] : [boxBlankLine(theme, renderedWidth, frameColor)]),
|
|
882
954
|
...renderBoxedOutputLines(
|
|
883
955
|
theme,
|
|
884
956
|
outputFragments,
|
|
885
957
|
renderedWidth,
|
|
886
958
|
options.renderLineBudget ?? outputFragments.length,
|
|
959
|
+
frameColor,
|
|
887
960
|
),
|
|
888
|
-
boxBlankLine(theme, renderedWidth),
|
|
961
|
+
boxBlankLine(theme, renderedWidth, frameColor),
|
|
889
962
|
boxLabeledBorder(
|
|
890
963
|
theme,
|
|
891
964
|
BOX_ROUND_BOTTOM_LEFT,
|
|
@@ -893,10 +966,16 @@ export function renderBoxedToolResult(
|
|
|
893
966
|
footerText,
|
|
894
967
|
options.expandHint ? theme.fg("dim", options.expandHint) : undefined,
|
|
895
968
|
renderedWidth,
|
|
969
|
+
frameColor,
|
|
896
970
|
),
|
|
897
971
|
];
|
|
898
|
-
|
|
899
|
-
|
|
972
|
+
// The result box owns its status tint (the container fill is neutralized).
|
|
973
|
+
const tinted =
|
|
974
|
+
options.tint === false
|
|
975
|
+
? rendered
|
|
976
|
+
: applyBgTint(theme, boxedToolBgName(options.isError, options.isPartial), rendered);
|
|
977
|
+
cache = { width, lines: tinted };
|
|
978
|
+
return tinted;
|
|
900
979
|
},
|
|
901
980
|
};
|
|
902
981
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quandev104/pi-style",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"description": "A native-layout, cohesive visual style package for Pi.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -42,18 +42,19 @@
|
|
|
42
42
|
"check": "npm run typecheck && npm run lint && npm run depcruise && npm run test && npm run build && npm run package:smoke"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"@earendil-works/pi-agent-core": ">=0.83.0 <0.
|
|
46
|
-
"@earendil-works/pi-ai": ">=0.83.0 <0.
|
|
47
|
-
"@earendil-works/pi-coding-agent": ">=0.83.0 <0.
|
|
48
|
-
"@earendil-works/pi-tui": ">=0.83.0 <0.
|
|
45
|
+
"@earendil-works/pi-agent-core": ">=0.83.0 <0.86.0",
|
|
46
|
+
"@earendil-works/pi-ai": ">=0.83.0 <0.86.0",
|
|
47
|
+
"@earendil-works/pi-coding-agent": ">=0.83.0 <0.86.0",
|
|
48
|
+
"@earendil-works/pi-tui": ">=0.83.0 <0.86.0",
|
|
49
49
|
"typebox": ">=1.3.9 <2.0.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@biomejs/biome": "^2.5.0",
|
|
53
|
-
"@earendil-works/pi-agent-core": "^0.
|
|
54
|
-
"@earendil-works/pi-ai": "^0.
|
|
55
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
56
|
-
"@earendil-works/pi-
|
|
53
|
+
"@earendil-works/pi-agent-core": "^0.85.0",
|
|
54
|
+
"@earendil-works/pi-ai": "^0.85.0",
|
|
55
|
+
"@earendil-works/pi-coding-agent": "^0.85.0",
|
|
56
|
+
"@earendil-works/pi-server": "^0.85.0",
|
|
57
|
+
"@earendil-works/pi-tui": "^0.85.0",
|
|
57
58
|
"@types/node": "^24.0.0",
|
|
58
59
|
"dependency-cruiser": "^17.4.3",
|
|
59
60
|
"git-cliff": "^2.13.1",
|