pi-pretty-tui 0.2.9 → 0.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/README.md +52 -15
- package/README.zh-CN.md +52 -15
- package/extensions/ansi-style.ts +29 -0
- package/extensions/card-surface.ts +38 -0
- package/extensions/full-frame.ts +145 -0
- package/extensions/index.ts +437 -259
- package/extensions/nested-tools.ts +59 -13
- package/extensions/selection-ranges.ts +245 -0
- package/extensions/shell-highlight.ts +36 -0
- package/extensions/thought-presentation.ts +38 -0
- package/extensions/tool-presentation.ts +292 -0
- package/package.json +10 -4
package/extensions/index.ts
CHANGED
|
@@ -1,18 +1,31 @@
|
|
|
1
|
+
import { installSelectionCopy, selectableRow, selectionMetadataActive } from "./selection-ranges.js";
|
|
2
|
+
import { isNativeImageRow } from "./ansi-style.js";
|
|
3
|
+
import { cardMargin, cardBackground, frameNativeRows, nativeFrameWidth } from "./full-frame.js";
|
|
4
|
+
import { ThoughtPresentation } from "./thought-presentation.js";
|
|
5
|
+
import { isThirdPartyTool, renderToolProjection, THIRD_PARTY_TOOL, toolLabel, type ToolProjection } from "./tool-presentation.js";
|
|
1
6
|
import { NestedTools, NESTED_SNAPSHOT_ENTRY } from "./nested-tools.js";
|
|
2
7
|
import {
|
|
3
8
|
AssistantMessageComponent,
|
|
4
9
|
CustomEditor,
|
|
5
10
|
CustomMessageComponent,
|
|
11
|
+
CompactionSummaryMessageComponent,
|
|
6
12
|
InteractiveMode,
|
|
7
13
|
ToolExecutionComponent,
|
|
8
14
|
UserMessageComponent,
|
|
9
15
|
createBashTool,
|
|
16
|
+
createBashToolDefinition,
|
|
10
17
|
createEditTool,
|
|
18
|
+
createEditToolDefinition,
|
|
11
19
|
createFindTool,
|
|
20
|
+
createFindToolDefinition,
|
|
12
21
|
createGrepTool,
|
|
22
|
+
createGrepToolDefinition,
|
|
13
23
|
createLsTool,
|
|
24
|
+
createLsToolDefinition,
|
|
14
25
|
createReadTool,
|
|
26
|
+
createReadToolDefinition,
|
|
15
27
|
createWriteTool,
|
|
28
|
+
createWriteToolDefinition,
|
|
16
29
|
copyToClipboard,
|
|
17
30
|
getAgentDir,
|
|
18
31
|
getMarkdownTheme,
|
|
@@ -46,29 +59,12 @@ type PrettyTuiMode = "full" | "compact" | "clean";
|
|
|
46
59
|
const CLEAN_TOOL_ACTIVITY_MIN_MS = 1000;
|
|
47
60
|
const TRANSIENT_UI_STATUS_MS = 10_000;
|
|
48
61
|
const RESPONSE_FOOTER_ENTRY_TYPE = "pretty-tui-response-footer";
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const stripAnsiBackgrounds = (value: string): string =>
|
|
52
|
-
value.replace(/\x1b\[([0-9:;]*)m/g, (sequence, raw: string) => {
|
|
53
|
-
const fields = raw === "" ? [0] : raw.split(";").map((field) => Number(field));
|
|
54
|
-
const kept: number[] = [];
|
|
55
|
-
for (let index = 0; index < fields.length; index++) {
|
|
56
|
-
const field = fields[index];
|
|
57
|
-
if ((field >= 40 && field <= 49) || (field >= 100 && field <= 107)) continue;
|
|
58
|
-
if (field === 48) {
|
|
59
|
-
const mode = fields[index + 1];
|
|
60
|
-
if (mode === 5) index += 2;
|
|
61
|
-
else if (mode === 2) index += 4;
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
kept.push(field);
|
|
65
|
-
}
|
|
66
|
-
if (raw.includes(":") && /(?:^|;)48:/u.test(raw)) return "";
|
|
67
|
-
return kept.length > 0 ? `\x1b[${kept.join(";")}m` : "";
|
|
68
|
-
});
|
|
62
|
+
|
|
69
63
|
type PrettyTuiConfig = {
|
|
70
64
|
enabled?: boolean;
|
|
71
65
|
mode?: PrettyTuiMode;
|
|
66
|
+
/** Exclude plugin decoration from fullscreen selections. Opt-in. */
|
|
67
|
+
selectionCopy?: boolean;
|
|
72
68
|
/** Legacy location used by the first mode implementation. */
|
|
73
69
|
bash?: {
|
|
74
70
|
mode?: "full" | "compact";
|
|
@@ -95,6 +91,10 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
95
91
|
? configuredMode
|
|
96
92
|
: "clean";
|
|
97
93
|
let cleanToolsExpanded = false;
|
|
94
|
+
// Full starts with thoughts open; an intentional Ctrl+T overrides that
|
|
95
|
+
// default for this runtime, independently of compact's initial setting.
|
|
96
|
+
let fullThinkingHidden = false;
|
|
97
|
+
let compactThinkingHidden = true;
|
|
98
98
|
let cleanContextCompacted = false;
|
|
99
99
|
let fullscreenTui = false;
|
|
100
100
|
const mouseClickHitTests = new WeakMap<object, (event: any) => boolean>();
|
|
@@ -159,7 +159,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
159
159
|
};
|
|
160
160
|
const refreshLatestActivityWidget = () => {
|
|
161
161
|
if (!currentExtensionUi?.setWidget) return;
|
|
162
|
-
if (
|
|
162
|
+
if (!latestActivityStatus) {
|
|
163
163
|
if (latestActivityWidgetInstalled) {
|
|
164
164
|
currentExtensionUi.setWidget(latestActivityWidgetKey, undefined);
|
|
165
165
|
latestActivityWidgetInstalled = false;
|
|
@@ -261,7 +261,9 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
261
261
|
const activityTimeline = new ActivityTimeline();
|
|
262
262
|
const nestedTools = new NestedTools();
|
|
263
263
|
const refreshNestedTools = (id: string) => {
|
|
264
|
-
|
|
264
|
+
const owner = toolComponents.get(nestedTools.root(id));
|
|
265
|
+
owner?.updateDisplay?.();
|
|
266
|
+
owner?.ui?.requestRender?.();
|
|
265
267
|
currentTui?.requestRender?.();
|
|
266
268
|
};
|
|
267
269
|
const revealedActivityGroups = new Set<string>();
|
|
@@ -283,7 +285,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
283
285
|
const cleanCompactToolCallIds = new Set<string>();
|
|
284
286
|
const cleanGroupToolCallIds = new Map<string, string[]>();
|
|
285
287
|
const cleanToolCallGroupOwners = new Map<string, string>();
|
|
286
|
-
const
|
|
288
|
+
const toolComponents = new Map<string, any>();
|
|
287
289
|
const cleanToolThemes = new Map<string, any>();
|
|
288
290
|
const cleanToolNames = new Map<string, string>();
|
|
289
291
|
let renderCleanGroupSummary = (_lastToolCallId: string, _width: number): string[] => [];
|
|
@@ -355,12 +357,14 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
355
357
|
return group.toolCallIds[group.toolCallIds.length - 1];
|
|
356
358
|
};
|
|
357
359
|
|
|
360
|
+
let fallbackActivityTheme: any;
|
|
358
361
|
const defaultActivityTheme = () => {
|
|
359
362
|
if (currentExtensionUi?.theme?.fg) return currentExtensionUi.theme;
|
|
360
363
|
const markdownTheme = getMarkdownTheme();
|
|
361
|
-
return {
|
|
364
|
+
return fallbackActivityTheme ??= {
|
|
362
365
|
fg: (color: string, text: string) => {
|
|
363
366
|
if (color === "accent") return markdownTheme.listBullet(text);
|
|
367
|
+
if (color === "mdHeading") return markdownTheme.heading(text);
|
|
364
368
|
// Session UI normally supplies Pi's active Theme above. Keep portable
|
|
365
369
|
// semantic fallbacks for early startup and isolated renderer tests.
|
|
366
370
|
if (color === "warning") return `\x1b[33m${text}\x1b[39m`;
|
|
@@ -443,7 +447,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
443
447
|
const refreshActivityGroup = (group: ActivityGroup): void => {
|
|
444
448
|
for (const child of group.members) {
|
|
445
449
|
if (child.kind === "tool" && child.toolCallId) {
|
|
446
|
-
|
|
450
|
+
toolComponents.get(child.toolCallId)?.updateDisplay?.();
|
|
447
451
|
} else if (child.kind === "thinking" && child.messageKey) {
|
|
448
452
|
thinkingComponents.get(child.messageKey)?.invalidate?.();
|
|
449
453
|
} else if (child.kind === "update") {
|
|
@@ -468,7 +472,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
468
472
|
else collapsedActivityGroups.delete(group.id);
|
|
469
473
|
for (const toolCallId of group.toolCallIds) {
|
|
470
474
|
cleanCompactToolCallIds.delete(toolCallId);
|
|
471
|
-
const component =
|
|
475
|
+
const component = toolComponents.get(toolCallId);
|
|
472
476
|
if (component?.expanded) component.setExpanded(false);
|
|
473
477
|
}
|
|
474
478
|
for (const child of group.members) expandedThinkingMembers.delete(child.id);
|
|
@@ -580,7 +584,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
580
584
|
};
|
|
581
585
|
|
|
582
586
|
const renderStandaloneError = (member: ActivityMember, width: number, component?: any): string[] => {
|
|
583
|
-
const run = component ? errorRun(component) : { hidden: false, count: 1, compact: false };
|
|
587
|
+
const run = component && renderMode !== "full" ? errorRun(component) : { hidden: false, count: 1, compact: false };
|
|
584
588
|
if (run.hidden) return [];
|
|
585
589
|
const theme = defaultActivityTheme();
|
|
586
590
|
const style = activityUpdateStyle(member, theme);
|
|
@@ -624,7 +628,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
624
628
|
width: number,
|
|
625
629
|
component?: any,
|
|
626
630
|
): string[] => {
|
|
627
|
-
if (!activityGroupCollapsible(group)) return renderPendingActivityUpdate(member, width, component);
|
|
631
|
+
if (renderMode !== "clean" || !activityGroupCollapsible(group)) return renderPendingActivityUpdate(member, width, component);
|
|
628
632
|
const position = activityMemberPosition(group, member);
|
|
629
633
|
if (!activityGroupRevealed(group)) {
|
|
630
634
|
return position.first
|
|
@@ -640,7 +644,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
640
644
|
event: any,
|
|
641
645
|
) => {
|
|
642
646
|
const isLeftClick = event.type === "click" && event.button === "left";
|
|
643
|
-
if (!isLeftClick || !activityGroupCollapsible(group)) return undefined;
|
|
647
|
+
if (renderMode !== "clean" || !isLeftClick || !activityGroupCollapsible(group)) return undefined;
|
|
644
648
|
const position = activityMemberPosition(group, member);
|
|
645
649
|
if (!activityGroupRevealed(group)) {
|
|
646
650
|
if (!position.first) return undefined;
|
|
@@ -675,7 +679,9 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
675
679
|
{ value: "full", label: "full", description: "Full tool details and output" },
|
|
676
680
|
{ value: "compact", label: "compact", description: "Concise summaries for all built-in tools" },
|
|
677
681
|
{ value: "clean", label: "clean", description: "Group supported tools into Running/Done status" },
|
|
678
|
-
{ value: "
|
|
682
|
+
{ value: "selection-copy on", label: "selection-copy on", description: "Copy selected text without UI borders in fullscreen mode; enable and reload" },
|
|
683
|
+
{ value: "selection-copy off", label: "selection-copy off", description: "Restore native selection copying and reload" },
|
|
684
|
+
{ value: "status", label: "status", description: "Show the current settings" },
|
|
679
685
|
];
|
|
680
686
|
const filtered = items.filter((item) => item.value.startsWith(prefix.trim().toLowerCase()));
|
|
681
687
|
return filtered.length > 0 ? filtered : null;
|
|
@@ -686,18 +692,21 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
686
692
|
if (!requested) {
|
|
687
693
|
if (!ctx.hasUI) {
|
|
688
694
|
ctx.ui.notify(
|
|
689
|
-
`pi-pretty-tui: ${config.enabled !== false ? "enabled" : "disabled"} · mode: ${renderMode}`,
|
|
695
|
+
`pi-pretty-tui: ${config.enabled !== false ? "enabled" : "disabled"} · mode: ${renderMode} · selection copy: ${config.selectionCopy === true ? "on" : "off"}`,
|
|
690
696
|
"info",
|
|
691
697
|
);
|
|
692
698
|
return;
|
|
693
699
|
}
|
|
694
700
|
const enabled = `Enabled — ${config.enabled !== false ? "on" : "off"}`;
|
|
695
701
|
const full = `Full — full tool details and output${renderMode === "full" ? " (current)" : ""}`;
|
|
696
|
-
const compact = `Compact — concise summaries
|
|
702
|
+
const compact = `Compact — concise tool summaries and nested calls${renderMode === "compact" ? " (current)" : ""}`;
|
|
697
703
|
const clean = `Clean — group supported tools into Running/Done status${renderMode === "clean" ? " (current)" : ""}`;
|
|
698
|
-
const
|
|
704
|
+
const selectionCopy = `Selection copy — ${config.selectionCopy === true ? "on" : "off"} · copy text without UI borders (fullscreen, experimental)`;
|
|
705
|
+
const selected = await ctx.ui.select("pi-pretty-tui settings", [enabled, full, compact, clean, selectionCopy]);
|
|
699
706
|
if (!selected) return;
|
|
700
|
-
requested = selected ===
|
|
707
|
+
requested = selected === selectionCopy
|
|
708
|
+
? config.selectionCopy === true ? "selection-copy off" : "selection-copy on"
|
|
709
|
+
: selected === enabled
|
|
701
710
|
? config.enabled !== false ? "disable" : "enable"
|
|
702
711
|
: selected === full
|
|
703
712
|
? "full"
|
|
@@ -708,11 +717,28 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
708
717
|
|
|
709
718
|
if (requested === "status") {
|
|
710
719
|
ctx.ui.notify(
|
|
711
|
-
`pi-pretty-tui: ${config.enabled !== false ? "enabled" : "disabled"} · mode: ${renderMode}`,
|
|
720
|
+
`pi-pretty-tui: ${config.enabled !== false ? "enabled" : "disabled"} · mode: ${renderMode} · selection copy: ${config.selectionCopy === true ? "on" : "off"}`,
|
|
712
721
|
"info",
|
|
713
722
|
);
|
|
714
723
|
return;
|
|
715
724
|
}
|
|
725
|
+
if (requested === "selection-copy on" || requested === "selection-copy off") {
|
|
726
|
+
const selectionCopy = requested.endsWith(" on");
|
|
727
|
+
try {
|
|
728
|
+
saveConfig({ ...config, selectionCopy });
|
|
729
|
+
} catch (error) {
|
|
730
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
731
|
+
ctx.ui.notify(`Could not save selection-copy setting: ${message}`, "error");
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
ctx.ui.notify(`Border-free selection copying ${selectionCopy ? "enabled" : "disabled"}; reloading…`, "info");
|
|
735
|
+
try { await ctx.reload(); }
|
|
736
|
+
catch (error) {
|
|
737
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
738
|
+
ctx.ui.notify(`Setting saved, but automatic reload failed: ${message}`, "warning");
|
|
739
|
+
}
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
716
742
|
if (requested === "enable" || requested === "disable") {
|
|
717
743
|
const enabled = requested === "enable";
|
|
718
744
|
try {
|
|
@@ -736,7 +762,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
736
762
|
}
|
|
737
763
|
}
|
|
738
764
|
if (requested !== "full" && requested !== "compact" && requested !== "clean") {
|
|
739
|
-
ctx.ui.notify("Usage: /pretty-tui [enable|disable|full|compact|clean|status]", "error");
|
|
765
|
+
ctx.ui.notify("Usage: /pretty-tui [enable|disable|full|compact|clean|status|selection-copy on|selection-copy off]", "error");
|
|
740
766
|
return;
|
|
741
767
|
}
|
|
742
768
|
|
|
@@ -745,14 +771,9 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
745
771
|
collapsedActivityGroups.clear();
|
|
746
772
|
expandedThinkingMembers.clear();
|
|
747
773
|
renderMode = requested;
|
|
748
|
-
if (
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
} else {
|
|
752
|
-
stopWorkingElapsedTimer();
|
|
753
|
-
clearLatestActivityStatus();
|
|
754
|
-
}
|
|
755
|
-
for (const component of cleanToolComponents.values()) component.updateDisplay?.();
|
|
774
|
+
if (cleanRun.active && !cleanRun.settled) startWorkingElapsedTimer();
|
|
775
|
+
refreshLatestActivityWidget();
|
|
776
|
+
for (const component of toolComponents.values()) component.updateDisplay?.();
|
|
756
777
|
for (const component of thinkingComponents.values()) component.invalidate?.();
|
|
757
778
|
try {
|
|
758
779
|
saveRenderMode(renderMode);
|
|
@@ -768,6 +789,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
768
789
|
// render patches or override built-in tools until the next enabled reload.
|
|
769
790
|
if (!activeForSession) return;
|
|
770
791
|
pi.on("session_shutdown", stopWorkingElapsedTimer);
|
|
792
|
+
if (config.selectionCopy === true) pi.on("session_shutdown", installSelectionCopy());
|
|
771
793
|
|
|
772
794
|
// Give Pi's main prompt editor a complete rounded frame. Render the native
|
|
773
795
|
// editor at a two-column narrower width so cursor layout, wrapping, IME, and
|
|
@@ -784,7 +806,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
784
806
|
if (this.tui) currentTui = this.tui;
|
|
785
807
|
currentPromptEditor = this;
|
|
786
808
|
const indicator = this.workingStatusIndicator;
|
|
787
|
-
if (
|
|
809
|
+
if (indicator?.kind !== "working" || responseRunStartedAt === undefined) {
|
|
788
810
|
return originalEditorTopBorder.call(this, width, hiddenLineCount);
|
|
789
811
|
}
|
|
790
812
|
const elapsed = formatResponseDuration(Date.now() - responseRunStartedAt);
|
|
@@ -816,9 +838,9 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
816
838
|
|
|
817
839
|
return lines.map((line, index) => {
|
|
818
840
|
const fitted = fitInnerWidth(line);
|
|
819
|
-
if (index === 0) return this.borderColor("╭") + fitted + this.borderColor("╮");
|
|
820
|
-
if (index === bottomBorderIndex) return this.borderColor("╰") + fitted + this.borderColor("╯");
|
|
821
|
-
if (index < bottomBorderIndex) return this.borderColor("│") + fitted + this.borderColor("│");
|
|
841
|
+
if (index === 0) return selectableRow(this.borderColor("╭") + fitted + this.borderColor("╮"), width, []);
|
|
842
|
+
if (index === bottomBorderIndex) return selectableRow(this.borderColor("╰") + fitted + this.borderColor("╯"), width, []);
|
|
843
|
+
if (index < bottomBorderIndex) return selectableRow(this.borderColor("│") + fitted + this.borderColor("│"), width, [[1, width - 1]]);
|
|
822
844
|
// Autocomplete remains outside the input frame, aligned to its content.
|
|
823
845
|
return " " + fitted + " ";
|
|
824
846
|
});
|
|
@@ -872,10 +894,8 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
872
894
|
});
|
|
873
895
|
}
|
|
874
896
|
|
|
875
|
-
//
|
|
876
|
-
//
|
|
877
|
-
// zones. Long messages stop at 75% of the terminal width; narrow terminals
|
|
878
|
-
// trade away the left gap before sacrificing readability.
|
|
897
|
+
// Clean uses content-sized bubbles; compact/full share full-width panels.
|
|
898
|
+
// Keep Pi's Markdown, mouse handling and OSC 133 zones in every mode.
|
|
879
899
|
const userMessagePrototype = UserMessageComponent.prototype as any;
|
|
880
900
|
const userMessagePatchKey = Symbol.for("pretty-tui.user-message-frame");
|
|
881
901
|
if (!userMessagePrototype[userMessagePatchKey]) {
|
|
@@ -912,9 +932,9 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
912
932
|
.map((line: string) => line.replace(/ +$/u, ""));
|
|
913
933
|
const layout = (width: number) => {
|
|
914
934
|
const terminalWidth = Math.max(1, width);
|
|
915
|
-
const rightPad = Math.min(outputPad, Math.max(0, terminalWidth - 1));
|
|
935
|
+
const rightPad = renderMode !== "clean" ? 0 : Math.min(outputPad, Math.max(0, terminalWidth - 1));
|
|
916
936
|
const availableWidth = Math.max(1, terminalWidth - rightPad);
|
|
917
|
-
if (availableWidth < 4) {
|
|
937
|
+
if (availableWidth < (renderMode !== "clean" ? 8 : 4)) {
|
|
918
938
|
return {
|
|
919
939
|
framed: false,
|
|
920
940
|
rightPad,
|
|
@@ -925,6 +945,16 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
925
945
|
};
|
|
926
946
|
}
|
|
927
947
|
|
|
948
|
+
if (renderMode !== "clean") {
|
|
949
|
+
const margin = cardMargin(terminalWidth);
|
|
950
|
+
const contentWidth = nativeFrameWidth(terminalWidth);
|
|
951
|
+
return {
|
|
952
|
+
framed: true, rightPad: margin, leftPad: margin,
|
|
953
|
+
frameWidth: terminalWidth - margin * 2, contentWidth,
|
|
954
|
+
body: renderMarkdown(contentWidth),
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
|
|
928
958
|
const preferredMaximum = Math.max(4, Math.floor(terminalWidth * 0.75));
|
|
929
959
|
// Keep at least one blank column on the left whenever a framed bubble
|
|
930
960
|
// fits, even after a narrow terminal relaxes the usual 75% maximum.
|
|
@@ -955,8 +985,8 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
955
985
|
};
|
|
956
986
|
};
|
|
957
987
|
const border = (text: string) => currentExtensionUi?.theme?.fg
|
|
958
|
-
? currentExtensionUi.theme.fg("
|
|
959
|
-
: markdownTheme.
|
|
988
|
+
? currentExtensionUi.theme.fg("mdHeading", text)
|
|
989
|
+
: markdownTheme.heading(text);
|
|
960
990
|
const userText = typeof this.text === "string" ? this.text : "";
|
|
961
991
|
const frame: Component = {
|
|
962
992
|
render(width: number): string[] {
|
|
@@ -964,16 +994,22 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
964
994
|
const left = " ".repeat(measured.leftPad);
|
|
965
995
|
const right = " ".repeat(measured.rightPad);
|
|
966
996
|
if (!measured.framed) {
|
|
967
|
-
return measured.body.map((line: string) =>
|
|
997
|
+
return measured.body.map((line: string) => renderMode !== "clean"
|
|
998
|
+
? cardBackground(line, width, defaultActivityTheme(), "toolPendingBg", "mdHeading") : left + line + right);
|
|
999
|
+
}
|
|
1000
|
+
if (renderMode !== "clean") {
|
|
1001
|
+
return frameNativeRows("User", measured.body, width, defaultActivityTheme(), undefined,
|
|
1002
|
+
frame, "mdHeading", "", "mdHeading").lines;
|
|
968
1003
|
}
|
|
969
1004
|
|
|
970
1005
|
const top = border("╭" + "─".repeat(Math.max(0, measured.frameWidth - 2)) + "╮");
|
|
971
1006
|
const bottom = border("╰" + "─".repeat(Math.max(0, measured.frameWidth - 2)) + "╯");
|
|
972
1007
|
const body = measured.body.map((line: string) => {
|
|
973
1008
|
const padding = " ".repeat(Math.max(0, measured.contentWidth - visibleWidth(line)));
|
|
974
|
-
return border("│ ") + line + padding + border(" │");
|
|
1009
|
+
return selectableRow(border("│ ") + line + padding + border(" │"), measured.frameWidth, [[2, 2 + Math.max(1, Math.min(measured.contentWidth, visibleWidth(line)))]]);
|
|
975
1010
|
});
|
|
976
|
-
return [top,
|
|
1011
|
+
return [selectableRow(top, measured.frameWidth, []), ...body, selectableRow(bottom, measured.frameWidth, [])]
|
|
1012
|
+
.map((line: string) => selectableRow(left + line + right, width, [[measured.leftPad, width - measured.rightPad]]));
|
|
977
1013
|
},
|
|
978
1014
|
handleMouse(event: any) {
|
|
979
1015
|
const measured = layout(event.width);
|
|
@@ -1106,6 +1142,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1106
1142
|
return true;
|
|
1107
1143
|
};
|
|
1108
1144
|
const patchedCustomRender = function (this: any, width: number): string[] {
|
|
1145
|
+
if (renderMode === "full") return originalCustomRender.call(this, width);
|
|
1109
1146
|
if (releaseSemanticCustomMessage(this)) {
|
|
1110
1147
|
return originalCustomRender.call(this, width);
|
|
1111
1148
|
}
|
|
@@ -1116,23 +1153,24 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1116
1153
|
// message_start event. Claim them at the component boundary so live
|
|
1117
1154
|
// rendering matches transcript restoration instead of failing open to
|
|
1118
1155
|
// the purple native box until the next reload.
|
|
1119
|
-
if (!member
|
|
1156
|
+
if (!member) {
|
|
1120
1157
|
member = addPersistentActivityUpdate(message);
|
|
1121
1158
|
}
|
|
1122
1159
|
const group = member ? activityTimeline.groupForMember(member.id) : undefined;
|
|
1123
|
-
if (
|
|
1160
|
+
if (!member || !group) {
|
|
1124
1161
|
return originalCustomRender.call(this, width);
|
|
1125
1162
|
}
|
|
1126
1163
|
activityUpdateComponents.set(member.id, this);
|
|
1127
1164
|
return renderActivityUpdateProjection(group, member, width);
|
|
1128
1165
|
};
|
|
1129
1166
|
const patchedCustomHandleMouse = function (this: any, event: any) {
|
|
1167
|
+
if (renderMode === "full") return originalCustomHandleMouse?.call(this, event);
|
|
1130
1168
|
if (releaseSemanticCustomMessage(this)) {
|
|
1131
1169
|
return originalCustomHandleMouse?.call(this, event);
|
|
1132
1170
|
}
|
|
1133
1171
|
const member = activityTimeline.memberForUpdate(customMessageKey(this.message));
|
|
1134
1172
|
const group = member ? activityTimeline.groupForMember(member.id) : undefined;
|
|
1135
|
-
if (
|
|
1173
|
+
if (!member || !group) {
|
|
1136
1174
|
return originalCustomHandleMouse?.call(this, event);
|
|
1137
1175
|
}
|
|
1138
1176
|
return handleActivityUpdateMouse(group, member, event);
|
|
@@ -1163,20 +1201,79 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1163
1201
|
});
|
|
1164
1202
|
}
|
|
1165
1203
|
|
|
1204
|
+
// Keep Pi's compaction content and expansion state inside the shared panel.
|
|
1205
|
+
const compactionPrototype = CompactionSummaryMessageComponent.prototype as any;
|
|
1206
|
+
const compactionPatchKey = Symbol.for("pretty-tui.compaction-panel");
|
|
1207
|
+
if (!compactionPrototype[compactionPatchKey]) {
|
|
1208
|
+
const originalRender = compactionPrototype.render;
|
|
1209
|
+
const originalHandleMouse = compactionPrototype.handleMouse;
|
|
1210
|
+
const views = new WeakMap<object, { width: number; sourceOffset: number; frame: ReturnType<typeof frameNativeRows> }>();
|
|
1211
|
+
const render = function (this: any, width: number): string[] {
|
|
1212
|
+
if (renderMode === "clean") {
|
|
1213
|
+
views.delete(this);
|
|
1214
|
+
mouseClickHitTests.delete(this);
|
|
1215
|
+
return originalRender.call(this, width);
|
|
1216
|
+
}
|
|
1217
|
+
const innerWidth = nativeFrameWidth(width);
|
|
1218
|
+
const region = this.children[0];
|
|
1219
|
+
// Native content begins with its [compaction] label and a spacer. The
|
|
1220
|
+
// panel title replaces those two rows; the summary itself is untouched.
|
|
1221
|
+
const content = region.child.children;
|
|
1222
|
+
const sourceOffset = content.slice(0, 2).reduce((height: number, child: any) => height + child.render(innerWidth).length, 0);
|
|
1223
|
+
const source = content.slice(2).flatMap((child: any) => child.render(innerWidth));
|
|
1224
|
+
const theme = defaultActivityTheme();
|
|
1225
|
+
const frame = frameNativeRows(`compaction ${this.expanded ? "▾" : "▸"}`, source, width, theme,
|
|
1226
|
+
undefined, this, "customMessageLabel", "", "customMessageLabel", "customMessageBg");
|
|
1227
|
+
views.set(this, { width: innerWidth, sourceOffset, frame });
|
|
1228
|
+
mouseClickHitTests.set(this, (event) => event.y === 0 || frame.sourceRows.has(event.y));
|
|
1229
|
+
return frame.lines.map((line) => isNativeImageRow(line) ? line : truncateToWidth(line, Math.max(1, width), ""));
|
|
1230
|
+
};
|
|
1231
|
+
const handleMouse = function (this: any, event: any) {
|
|
1232
|
+
if (renderMode === "clean") return originalHandleMouse?.call(this, event);
|
|
1233
|
+
render.call(this, event.width);
|
|
1234
|
+
const view = views.get(this);
|
|
1235
|
+
if (!view) return undefined;
|
|
1236
|
+
if (event.y === 0 && event.type === "click" && event.button === "left") {
|
|
1237
|
+
this.setExpanded(!this.expanded);
|
|
1238
|
+
currentTui?.requestRender?.();
|
|
1239
|
+
return { handled: true };
|
|
1240
|
+
}
|
|
1241
|
+
const sourceY = view.frame.sourceRows.get(event.y);
|
|
1242
|
+
if (sourceY === undefined) return undefined;
|
|
1243
|
+
const result = this.children[0].handleMouse?.({ ...event,
|
|
1244
|
+
x: Math.max(0, event.x - view.frame.inset), y: sourceY + view.sourceOffset,
|
|
1245
|
+
width: view.width, height: view.frame.sourceRows.size + view.sourceOffset,
|
|
1246
|
+
});
|
|
1247
|
+
if (result?.handled) currentTui?.requestRender?.();
|
|
1248
|
+
return result;
|
|
1249
|
+
};
|
|
1250
|
+
compactionPrototype[compactionPatchKey] = { originalRender, originalHandleMouse };
|
|
1251
|
+
compactionPrototype.render = render;
|
|
1252
|
+
compactionPrototype.handleMouse = handleMouse;
|
|
1253
|
+
pi.on("session_shutdown", () => {
|
|
1254
|
+
if (compactionPrototype.render === render) compactionPrototype.render = originalRender;
|
|
1255
|
+
if (compactionPrototype.handleMouse === handleMouse) compactionPrototype.handleMouse = originalHandleMouse;
|
|
1256
|
+
if (compactionPrototype.render === originalRender && compactionPrototype.handleMouse === originalHandleMouse) {
|
|
1257
|
+
delete compactionPrototype[compactionPatchKey];
|
|
1258
|
+
}
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1166
1262
|
// Thinking and tools are peers in the transcript-first activity timeline.
|
|
1167
|
-
// Assistant components keep Pi's native Markdown renderer
|
|
1168
|
-
//
|
|
1263
|
+
// Assistant components keep Pi's native visible-answer Markdown renderer.
|
|
1264
|
+
// All modes share thought content; only clean adds activity-group framing.
|
|
1169
1265
|
const assistantPrototype = AssistantMessageComponent.prototype as any;
|
|
1170
1266
|
const thinkingPatchKey = Symbol.for("pretty-tui.clean-thinking");
|
|
1171
1267
|
if (!assistantPrototype[thinkingPatchKey]) {
|
|
1172
1268
|
const originalUpdateContent = assistantPrototype.updateContent;
|
|
1173
1269
|
const originalRender = assistantPrototype.render;
|
|
1174
1270
|
const originalHandleMouse = assistantPrototype.handleMouse;
|
|
1271
|
+
const originalSetHideThinkingBlock = assistantPrototype.setHideThinkingBlock;
|
|
1272
|
+
const explicitThinkingVisibility = new WeakMap<object, boolean>();
|
|
1175
1273
|
const originalMessageKey = Symbol("pretty-tui.original-assistant-message");
|
|
1176
1274
|
const renderedModeKey = Symbol("pretty-tui.rendered-assistant-mode");
|
|
1177
1275
|
const renderedExpansionKey = Symbol("pretty-tui.rendered-assistant-expansion");
|
|
1178
1276
|
const cleanThinkingRenderCacheKey = Symbol("pretty-tui.clean-thinking-render-cache");
|
|
1179
|
-
const cleanThinkingDetailKey = Symbol("pretty-tui.clean-thinking-detail");
|
|
1180
1277
|
|
|
1181
1278
|
const assistantTerminalUpdate = (message: any): ActivityMember | undefined => {
|
|
1182
1279
|
if (!assistantTerminalState(message)) return undefined;
|
|
@@ -1216,32 +1313,84 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1216
1313
|
currentTui?.requestRender?.();
|
|
1217
1314
|
};
|
|
1218
1315
|
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1316
|
+
const thoughtPresentation = new ThoughtPresentation();
|
|
1317
|
+
const fullThinkingVisibility = new WeakMap<object, boolean>();
|
|
1318
|
+
const patchedSetHideThinkingBlock = function (this: any, hide: boolean) {
|
|
1319
|
+
if (renderMode !== "clean") explicitThinkingVisibility.set(this, !hide);
|
|
1320
|
+
if (renderMode === "full") fullThinkingVisibility.set(this, !hide);
|
|
1321
|
+
return originalSetHideThinkingBlock.call(this, hide);
|
|
1322
|
+
};
|
|
1323
|
+
const panelAssistantViews = new WeakMap<object, { rows: Map<number, { sourceY: number; inset: number }>; width: number; height: number }>();
|
|
1324
|
+
const renderPanelAssistant = function (this: any, width: number): string[] {
|
|
1325
|
+
const innerWidth = nativeFrameWidth(width);
|
|
1326
|
+
const nativeLines = originalRender.call(this, innerWidth);
|
|
1327
|
+
const rows = new Map<number, { sourceY: number; inset: number }>();
|
|
1328
|
+
const thoughtRows = new Set<number>();
|
|
1329
|
+
const output: string[] = [];
|
|
1330
|
+
const children = this.contentContainer.children;
|
|
1331
|
+
let sourceY = 0;
|
|
1332
|
+
let thoughtRun = 0;
|
|
1333
|
+
const previews: string[] = [];
|
|
1334
|
+
if (renderMode === "compact") {
|
|
1335
|
+
const message = this[originalMessageKey] ?? this.lastMessage;
|
|
1336
|
+
let run: string[] = [];
|
|
1337
|
+
const finishRun = () => {
|
|
1338
|
+
if (run.length) previews.push(run.join("\n\n").split(/\r\n|\r|\n/).find((line) => line.trim())?.trim() ?? "");
|
|
1339
|
+
run = [];
|
|
1238
1340
|
};
|
|
1239
|
-
|
|
1341
|
+
for (const item of message?.content ?? []) {
|
|
1342
|
+
if (item.type === "thinking") {
|
|
1343
|
+
if (item.thinking?.trim()) run.push(item.thinking.trim());
|
|
1344
|
+
} else finishRun();
|
|
1345
|
+
}
|
|
1346
|
+
finishRun();
|
|
1240
1347
|
}
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1348
|
+
for (let index = 0; index < children.length; index++) {
|
|
1349
|
+
const child = children[index];
|
|
1350
|
+
const childLines = child.render(innerWidth);
|
|
1351
|
+
const isThought = Boolean(child.onMouse && child.child);
|
|
1352
|
+
if (isThought || child instanceof Markdown && childLines.length) {
|
|
1353
|
+
let height = childLines.length;
|
|
1354
|
+
// Adjacent native text blocks belong to the same answer card. Keep
|
|
1355
|
+
// native paragraph order and source coordinates, not copied text.
|
|
1356
|
+
if (!isThought) {
|
|
1357
|
+
while (children[index + 1] instanceof Markdown) {
|
|
1358
|
+
height += children[++index].render(innerWidth).length;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
const runIndex = thoughtRun;
|
|
1362
|
+
const hidden = isThought && (this.thinkingVisibilityOverrides.get(thoughtRun++) ?? this.hideThinkingBlock);
|
|
1363
|
+
const source = renderMode === "compact" && hidden
|
|
1364
|
+
? [defaultActivityTheme().fg("thinkingText", truncateToWidth(previews[runIndex] ?? "", innerWidth, "…"))]
|
|
1365
|
+
: nativeLines.slice(sourceY, sourceY + height);
|
|
1366
|
+
const role = isThought ? "mdLink" : "text";
|
|
1367
|
+
const baseLabel = isThought ? this.isStreaming ? "thinking" : "thought" : "Assistant";
|
|
1368
|
+
const label = isThought && renderMode === "compact" ? `${baseLabel} ${hidden ? "▸" : "▾"}` : baseLabel;
|
|
1369
|
+
const frame = frameNativeRows(label, source, width, defaultActivityTheme(), undefined, child, role, "", role);
|
|
1370
|
+
for (const [y, nativeY] of frame.sourceRows) {
|
|
1371
|
+
rows.set(output.length + y, { sourceY: sourceY + nativeY, inset: frame.inset });
|
|
1372
|
+
if (isThought) thoughtRows.add(output.length + y);
|
|
1373
|
+
}
|
|
1374
|
+
if (isThought && height) {
|
|
1375
|
+
rows.set(output.length, { sourceY, inset: frame.inset });
|
|
1376
|
+
thoughtRows.add(output.length);
|
|
1377
|
+
}
|
|
1378
|
+
output.push(...frame.lines);
|
|
1379
|
+
sourceY += height;
|
|
1380
|
+
} else {
|
|
1381
|
+
for (let y = 0; y < childLines.length; y++) rows.set(output.length + y, { sourceY: sourceY + y, inset: 0 });
|
|
1382
|
+
output.push(...nativeLines.slice(sourceY, sourceY + childLines.length));
|
|
1383
|
+
sourceY += childLines.length;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
if (renderMode === "compact") {
|
|
1387
|
+
const terminal = assistantTerminalUpdate(this[originalMessageKey] ?? this.lastMessage);
|
|
1388
|
+
if (terminal) output.push(...renderPendingActivityUpdate(terminal, width, this));
|
|
1389
|
+
}
|
|
1390
|
+
panelAssistantViews.set(this, { rows, width: innerWidth, height: nativeLines.length });
|
|
1391
|
+
mouseClickHitTests.set(this, (event) => thoughtRows.has(event.y));
|
|
1392
|
+
return renderMode === "compact" && width < 8
|
|
1393
|
+
? output.map((line) => isNativeImageRow(line) ? line : truncateToWidth(line, Math.max(1, width), "")) : output;
|
|
1245
1394
|
};
|
|
1246
1395
|
|
|
1247
1396
|
const patchedUpdateContent = function (this: any, message: any, isStreaming = this.isStreaming) {
|
|
@@ -1250,8 +1399,13 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1250
1399
|
this[renderedExpansionKey] = cleanToolsExpanded;
|
|
1251
1400
|
this.lastMessage = message;
|
|
1252
1401
|
if (renderMode !== "clean") {
|
|
1253
|
-
|
|
1402
|
+
this.hideThinkingBlock = renderMode === "full"
|
|
1403
|
+
? !(fullThinkingVisibility.get(this) ?? !fullThinkingHidden)
|
|
1404
|
+
: !(explicitThinkingVisibility.get(this) ?? !compactThinkingHidden);
|
|
1405
|
+
originalUpdateContent.call(this, renderMode === "compact" && assistantTerminalState(message)
|
|
1406
|
+
? { ...message, errorMessage: undefined, stopReason: "stop" } : message, isStreaming);
|
|
1254
1407
|
this.lastMessage = message;
|
|
1408
|
+
if (eligibleThinking(message)) thinkingComponents.set(assistantMessageKey(message), this);
|
|
1255
1409
|
return;
|
|
1256
1410
|
}
|
|
1257
1411
|
const groupedThinking = eligibleThinking(message);
|
|
@@ -1263,15 +1417,17 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1263
1417
|
|
|
1264
1418
|
const previousHideThinkingBlock = this.hideThinkingBlock;
|
|
1265
1419
|
this.hideThinkingBlock = false;
|
|
1266
|
-
//
|
|
1267
|
-
// native content tree also removes the native MouseRegion that would
|
|
1420
|
+
// Shared thought presentation owns thinking completely. Removing it from
|
|
1421
|
+
// Pi's native content tree also removes the native MouseRegion that would
|
|
1268
1422
|
// otherwise toggle a second, unrelated `Thinking...` block.
|
|
1269
1423
|
const displayMessage =
|
|
1270
1424
|
groupedThinking && Array.isArray(message?.content)
|
|
1271
1425
|
? { ...message, content: message.content.filter((item: any) => item.type !== "thinking") }
|
|
1272
1426
|
: message;
|
|
1273
1427
|
try {
|
|
1274
|
-
originalUpdateContent.call(this,
|
|
1428
|
+
originalUpdateContent.call(this, assistantTerminalState(message)
|
|
1429
|
+
? { ...displayMessage, errorMessage: undefined, stopReason: "stop" }
|
|
1430
|
+
: displayMessage, isStreaming);
|
|
1275
1431
|
} finally {
|
|
1276
1432
|
this.hideThinkingBlock = previousHideThinkingBlock;
|
|
1277
1433
|
}
|
|
@@ -1286,14 +1442,17 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1286
1442
|
) {
|
|
1287
1443
|
patchedUpdateContent.call(this, message, this.isStreaming);
|
|
1288
1444
|
}
|
|
1289
|
-
if (renderMode !== "clean") return
|
|
1445
|
+
if (renderMode !== "clean") return renderPanelAssistant.call(this, width);
|
|
1290
1446
|
const terminalMember = assistantTerminalUpdate(message);
|
|
1291
1447
|
if (!eligibleThinking(message)) {
|
|
1292
1448
|
if (!terminalMember) return originalRender.call(this, width);
|
|
1293
1449
|
const terminalGroup = activityTimeline.groupForMember(terminalMember.id);
|
|
1294
1450
|
if (!terminalGroup) return originalRender.call(this, width);
|
|
1295
1451
|
activityUpdateComponents.set(terminalMember.id, this);
|
|
1296
|
-
return
|
|
1452
|
+
return [
|
|
1453
|
+
...renderActivityUpdateProjection(terminalGroup, terminalMember, width, this),
|
|
1454
|
+
...(visibleAssistantText(message) ? originalRender.call(this, width) : []),
|
|
1455
|
+
];
|
|
1297
1456
|
}
|
|
1298
1457
|
|
|
1299
1458
|
const key = assistantMessageKey(message);
|
|
@@ -1311,10 +1470,9 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1311
1470
|
}
|
|
1312
1471
|
const position = activityMemberPosition(group, member);
|
|
1313
1472
|
const visibleLines = visibleAssistantText(message) ? originalRender.call(this, width) : [];
|
|
1314
|
-
//
|
|
1315
|
-
//
|
|
1316
|
-
|
|
1317
|
-
const terminalLines = terminalMember && visibleLines.length === 0
|
|
1473
|
+
// Native errors are filtered above; append the shared alert exactly once
|
|
1474
|
+
// while preserving any partial visible answer outside the thought block.
|
|
1475
|
+
const terminalLines = terminalMember
|
|
1318
1476
|
? renderPendingActivityUpdate(terminalMember, width)
|
|
1319
1477
|
: [];
|
|
1320
1478
|
const appendTail = (activityLines: string[]) => {
|
|
@@ -1343,7 +1501,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1343
1501
|
);
|
|
1344
1502
|
// Clean mode owns thought disclosure: revealing a group shows the thought
|
|
1345
1503
|
// member, while only a member click (or Ctrl+O) reveals its full text.
|
|
1346
|
-
const expanded =
|
|
1504
|
+
const expanded = expandedThinkingMembers.has(member.id) || (explicitThinkingVisibility.get(this) ?? cleanToolsExpanded);
|
|
1347
1505
|
const settledOwner = position.first ? activityGroupOwner(group) : undefined;
|
|
1348
1506
|
// A group without tools has no durable summary, so its own boundary is
|
|
1349
1507
|
// the signal that it finished.
|
|
@@ -1367,12 +1525,10 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1367
1525
|
return appendTail(cached.lines);
|
|
1368
1526
|
}
|
|
1369
1527
|
const stateLabel = this.isStreaming ? "thinking" : "thought";
|
|
1370
|
-
const
|
|
1371
|
-
const header = groupTheme.fg("mdLink", "● ") +
|
|
1372
|
-
groupTheme.fg("text", groupTheme.bold(label));
|
|
1528
|
+
const header = thoughtPresentation.header(stateLabel, childWidth, groupTheme);
|
|
1373
1529
|
let contentLines: string[] = [header];
|
|
1374
1530
|
if (expanded) {
|
|
1375
|
-
contentLines.push(...
|
|
1531
|
+
contentLines.push(...thoughtPresentation.details(
|
|
1376
1532
|
this,
|
|
1377
1533
|
member.thinking ?? thinkingText(message),
|
|
1378
1534
|
groupTheme,
|
|
@@ -1405,7 +1561,15 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1405
1561
|
|
|
1406
1562
|
const handleAssistantTranscriptMouse = function (this: any, event: any) {
|
|
1407
1563
|
const message = this[originalMessageKey] ?? this.lastMessage;
|
|
1408
|
-
if (renderMode !== "clean")
|
|
1564
|
+
if (renderMode !== "clean") {
|
|
1565
|
+
patchedRender.call(this, event.width);
|
|
1566
|
+
const view = panelAssistantViews.get(this);
|
|
1567
|
+
const row = view?.rows.get(event.y);
|
|
1568
|
+
if (!view || !row) return undefined;
|
|
1569
|
+
return originalHandleMouse.call(this, { ...event,
|
|
1570
|
+
x: Math.max(0, event.x - row.inset), y: row.sourceY, width: view.width, height: view.height,
|
|
1571
|
+
});
|
|
1572
|
+
}
|
|
1409
1573
|
if (!eligibleThinking(message)) {
|
|
1410
1574
|
const terminalMember = assistantTerminalUpdate(message);
|
|
1411
1575
|
const terminalGroup = terminalMember
|
|
@@ -1478,6 +1642,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1478
1642
|
originalHandleMouse,
|
|
1479
1643
|
patchedHandleMouse,
|
|
1480
1644
|
};
|
|
1645
|
+
assistantPrototype.setHideThinkingBlock = patchedSetHideThinkingBlock;
|
|
1481
1646
|
assistantPrototype.updateContent = patchedUpdateContent;
|
|
1482
1647
|
assistantPrototype.render = patchedRender;
|
|
1483
1648
|
assistantPrototype.handleMouse = patchedHandleMouse;
|
|
@@ -1485,6 +1650,9 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1485
1650
|
pi.on("session_shutdown", () => {
|
|
1486
1651
|
const patch = assistantPrototype[thinkingPatchKey];
|
|
1487
1652
|
if (!patch) return;
|
|
1653
|
+
if (assistantPrototype.setHideThinkingBlock === patchedSetHideThinkingBlock) {
|
|
1654
|
+
assistantPrototype.setHideThinkingBlock = originalSetHideThinkingBlock;
|
|
1655
|
+
}
|
|
1488
1656
|
if (patch.patchedUpdateContent === assistantPrototype.updateContent) {
|
|
1489
1657
|
assistantPrototype.updateContent = patch.originalUpdateContent;
|
|
1490
1658
|
}
|
|
@@ -1682,10 +1850,8 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1682
1850
|
message: string,
|
|
1683
1851
|
type?: "info" | "warning" | "error",
|
|
1684
1852
|
) {
|
|
1685
|
-
if (renderMode !== "clean") {
|
|
1686
|
-
return originalShowExtensionNotify.call(this, message, type);
|
|
1687
|
-
}
|
|
1688
1853
|
const severity: ActivitySeverity = type ?? "info";
|
|
1854
|
+
if (renderMode === "full") return nativeExtensionNotify.call(this, message, severity);
|
|
1689
1855
|
if (!appendRuntimeActivityUpdate(this, message, severity)) {
|
|
1690
1856
|
nativeExtensionNotify.call(this, message, severity);
|
|
1691
1857
|
}
|
|
@@ -1693,6 +1859,13 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1693
1859
|
|
|
1694
1860
|
const patchedToggleThinkingBlockVisibility = function (this: any) {
|
|
1695
1861
|
if (renderMode !== "clean") {
|
|
1862
|
+
if (renderMode === "full") {
|
|
1863
|
+
this.hideThinkingBlock = fullThinkingHidden;
|
|
1864
|
+
fullThinkingHidden = !fullThinkingHidden;
|
|
1865
|
+
} else {
|
|
1866
|
+
this.hideThinkingBlock = compactThinkingHidden;
|
|
1867
|
+
compactThinkingHidden = !compactThinkingHidden;
|
|
1868
|
+
}
|
|
1696
1869
|
return originalToggleThinkingBlockVisibility.call(this);
|
|
1697
1870
|
}
|
|
1698
1871
|
// Do not change Pi's persisted hideThinkingBlock setting in clean mode.
|
|
@@ -1704,7 +1877,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1704
1877
|
};
|
|
1705
1878
|
|
|
1706
1879
|
const patchedShowStatus = function (this: any, message: string) {
|
|
1707
|
-
if (renderMode
|
|
1880
|
+
if (renderMode === "full") return originalShowStatus.call(this, message);
|
|
1708
1881
|
const [firstLine, ...remainingLines] = String(message).split("\n");
|
|
1709
1882
|
setLatestActivityStatus({
|
|
1710
1883
|
title: firstLine || "Status",
|
|
@@ -1715,13 +1888,15 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1715
1888
|
};
|
|
1716
1889
|
|
|
1717
1890
|
const patchedShowWarning = function (this: any, message: string) {
|
|
1718
|
-
if (renderMode
|
|
1891
|
+
if (renderMode === "full") return originalShowWarning.call(this, message);
|
|
1892
|
+
if (!appendRuntimeActivityUpdate(this, message, "warning")) {
|
|
1719
1893
|
return originalShowWarning.call(this, message);
|
|
1720
1894
|
}
|
|
1721
1895
|
};
|
|
1722
1896
|
|
|
1723
1897
|
const patchedShowError = function (this: any, message: string) {
|
|
1724
|
-
if (renderMode
|
|
1898
|
+
if (renderMode === "full") return originalShowError.call(this, message);
|
|
1899
|
+
if (!appendRuntimeActivityUpdate(this, message, "error")) {
|
|
1725
1900
|
return originalShowError.call(this, message);
|
|
1726
1901
|
}
|
|
1727
1902
|
};
|
|
@@ -1772,9 +1947,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1772
1947
|
}
|
|
1773
1948
|
// A mode switch can keep existing components rather than rebuild them.
|
|
1774
1949
|
const component: Component = {
|
|
1775
|
-
render: (width: number) =>
|
|
1776
|
-
? renderActivityUpdateProjection(group, member, width)
|
|
1777
|
-
: nativeNodes.flatMap((node) => node.render(width)),
|
|
1950
|
+
render: (width: number) => renderActivityUpdateProjection(group, member, width),
|
|
1778
1951
|
invalidate() {},
|
|
1779
1952
|
handleMouse: (event: any) => renderMode === "clean"
|
|
1780
1953
|
? handleActivityUpdateMouse(group, member, event)
|
|
@@ -1808,7 +1981,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1808
1981
|
if (!group) continue;
|
|
1809
1982
|
mountActivityUpdate(
|
|
1810
1983
|
cacheNoticeHost.current, group, member, [],
|
|
1811
|
-
|
|
1984
|
+
toolComponents.get(toolCallId),
|
|
1812
1985
|
);
|
|
1813
1986
|
refreshActivityGroup(group);
|
|
1814
1987
|
cacheNoticeHost.current?.ui?.requestRender?.();
|
|
@@ -1817,7 +1990,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1817
1990
|
flushDeferredCacheNotices = flushPendingCacheNotices;
|
|
1818
1991
|
|
|
1819
1992
|
const patchedAddCacheMissNotice = function (this: any, miss: any) {
|
|
1820
|
-
if (
|
|
1993
|
+
if (!this.chatContainer?.addChild) {
|
|
1821
1994
|
return originalAddCacheMissNotice.call(this, miss);
|
|
1822
1995
|
}
|
|
1823
1996
|
// Let Pi decide thresholds, formatting and cause labels. Capture its
|
|
@@ -1876,7 +2049,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1876
2049
|
if (!group) return;
|
|
1877
2050
|
mountActivityUpdate(
|
|
1878
2051
|
this, group, member, nativeNodes,
|
|
1879
|
-
anchor?.toolCallId ?
|
|
2052
|
+
anchor?.toolCallId ? toolComponents.get(anchor.toolCallId) : undefined,
|
|
1880
2053
|
);
|
|
1881
2054
|
const component = activityUpdateComponents.get(member.id);
|
|
1882
2055
|
if (component && message) cacheNoticeOwners.set(component, message);
|
|
@@ -2128,6 +2301,21 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2128
2301
|
return originalInvalidate.call(this);
|
|
2129
2302
|
};
|
|
2130
2303
|
|
|
2304
|
+
const copyRows = new WeakMap<object, { source: string[]; padding: number; lines: string[] }>();
|
|
2305
|
+
const selectableMarkdown = (owner: any, source: string[]) => {
|
|
2306
|
+
if (!selectionMetadataActive()) return source;
|
|
2307
|
+
const padding = Math.max(0, Number(owner.paddingX) || 0);
|
|
2308
|
+
if (!padding) return source;
|
|
2309
|
+
const cached = copyRows.get(owner);
|
|
2310
|
+
if (cached?.source === source && cached.padding === padding) return cached.lines;
|
|
2311
|
+
const lines = source.map(line => {
|
|
2312
|
+
const width = visibleWidth(line);
|
|
2313
|
+
return selectableRow(line, width, [[Math.min(padding, width), Math.max(Math.min(padding, width), width - padding)]]);
|
|
2314
|
+
});
|
|
2315
|
+
copyRows.set(owner, { source, padding, lines });
|
|
2316
|
+
return lines;
|
|
2317
|
+
};
|
|
2318
|
+
|
|
2131
2319
|
const patchedRender = function (this: any, width: number): string[] {
|
|
2132
2320
|
const scoped = transcriptMarkdownActive();
|
|
2133
2321
|
mouseClickHitTests.set(this, (event) => fullscreenTui &&
|
|
@@ -2147,7 +2335,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2147
2335
|
if (needsRender) this[codeBlockCollectionKey] = [];
|
|
2148
2336
|
|
|
2149
2337
|
const lines = originalRender.call(this, width);
|
|
2150
|
-
if (!needsRender) return lines;
|
|
2338
|
+
if (!needsRender) return selectableMarkdown(this, lines);
|
|
2151
2339
|
|
|
2152
2340
|
const regions: Array<{ xStart: number; xEnd: number; y: number; code: string }> = [];
|
|
2153
2341
|
let searchFrom = 0;
|
|
@@ -2175,7 +2363,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2175
2363
|
}
|
|
2176
2364
|
this[codeBlockRegionsKey] = regions;
|
|
2177
2365
|
delete this[codeBlockCollectionKey];
|
|
2178
|
-
return lines;
|
|
2366
|
+
return selectableMarkdown(this, lines);
|
|
2179
2367
|
};
|
|
2180
2368
|
|
|
2181
2369
|
const patchedRenderToken = function (
|
|
@@ -2333,14 +2521,12 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2333
2521
|
(showCopyButton ? this.theme.codeBlockBorder(` ${copyLabel} `) : "") + frameBorder("╮");
|
|
2334
2522
|
const framedCodeWidth = Math.max(1, frameWidth - 4);
|
|
2335
2523
|
const lines = [
|
|
2336
|
-
styledTopRule,
|
|
2337
|
-
...codeLines.map((line) =>
|
|
2338
|
-
frameBorder("│ ") +
|
|
2339
|
-
line +
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
),
|
|
2343
|
-
frameBorder(`╰${"─".repeat(Math.max(0, frameWidth - 2))}╯`),
|
|
2524
|
+
selectableRow(styledTopRule, frameWidth, []),
|
|
2525
|
+
...codeLines.map((line) => selectableRow(
|
|
2526
|
+
frameBorder("│ ") + line +
|
|
2527
|
+
" ".repeat(Math.max(0, framedCodeWidth - visibleWidth(line))) + frameBorder(" │"),
|
|
2528
|
+
frameWidth, [[2, frameWidth - 2]])),
|
|
2529
|
+
selectableRow(frameBorder(`╰${"─".repeat(Math.max(0, frameWidth - 2))}╯`), frameWidth, []),
|
|
2344
2530
|
];
|
|
2345
2531
|
|
|
2346
2532
|
if (showCopyButton && Array.isArray(this[codeBlockCollectionKey])) {
|
|
@@ -2520,10 +2706,11 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2520
2706
|
prefix: () => {
|
|
2521
2707
|
const status = (state.compactToolStatus ?? "running") as ToolStatus;
|
|
2522
2708
|
const color = status === "success" ? "success" : status === "error" ? "error" : "dim";
|
|
2709
|
+
if (renderMode === "compact") return name === "bash" ? theme.fg("toolTitle", "$ ") : "";
|
|
2523
2710
|
return theme.fg(color, "● ");
|
|
2524
2711
|
},
|
|
2525
|
-
continuation: " ",
|
|
2526
|
-
content:
|
|
2712
|
+
continuation: renderMode === "compact" && name !== "bash" ? "" : " ",
|
|
2713
|
+
content: renderMode === "compact" ? theme.fg("muted", detail) :
|
|
2527
2714
|
theme.fg("text", theme.bold(name)) +
|
|
2528
2715
|
theme.fg("dim", "(") +
|
|
2529
2716
|
theme.fg("muted", detail) +
|
|
@@ -2769,65 +2956,6 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2769
2956
|
settled: false,
|
|
2770
2957
|
};
|
|
2771
2958
|
|
|
2772
|
-
const toolLabel = (toolName: string, label?: string): string => label || toolName;
|
|
2773
|
-
|
|
2774
|
-
const conciseThirdPartyArgs = (args: any): string => {
|
|
2775
|
-
if (!args || typeof args !== "object" || Array.isArray(args)) return "";
|
|
2776
|
-
const sensitive = /(?:token|secret|password|authorization|credential|api[_-]?key)/iu;
|
|
2777
|
-
const preferred = ["path", "query", "url", "offset", "id"];
|
|
2778
|
-
const entries = Object.entries(args)
|
|
2779
|
-
.filter(([key, value]) => !sensitive.test(key) && ["string", "number", "boolean"].includes(typeof value))
|
|
2780
|
-
.sort(([left], [right]) => {
|
|
2781
|
-
const leftRank = preferred.indexOf(left);
|
|
2782
|
-
const rightRank = preferred.indexOf(right);
|
|
2783
|
-
return (leftRank < 0 ? preferred.length : leftRank) -
|
|
2784
|
-
(rightRank < 0 ? preferred.length : rightRank);
|
|
2785
|
-
})
|
|
2786
|
-
.slice(0, 2)
|
|
2787
|
-
.map(([key, value]) => {
|
|
2788
|
-
const safeValue = String(value)
|
|
2789
|
-
.replace(/(https?:\/\/)[^\s/@:]+:[^\s/@]+@/giu, "$1…:…@")
|
|
2790
|
-
.replace(/([?&](?:token|secret|password|api[_-]?key)=)[^&\s]+/giu, "$1…");
|
|
2791
|
-
const rendered = truncateToWidth(safeValue, 36, "…");
|
|
2792
|
-
return `${key}=${rendered}`;
|
|
2793
|
-
});
|
|
2794
|
-
return entries.join(" · ");
|
|
2795
|
-
};
|
|
2796
|
-
|
|
2797
|
-
const thirdPartyResultSummary = (component: any): string => {
|
|
2798
|
-
const detailsError = component.result?.details?.error;
|
|
2799
|
-
const firstText = component.result?.content?.find(
|
|
2800
|
-
(item: any) => item?.type === "text" && typeof item.text === "string" && item.text.length > 0,
|
|
2801
|
-
)?.text;
|
|
2802
|
-
const text = String(typeof detailsError === "string" ? detailsError : firstText ?? "");
|
|
2803
|
-
const boundedText = text.slice(0, 512);
|
|
2804
|
-
const newline = boundedText.indexOf("\n");
|
|
2805
|
-
const boundedFirstLine = boundedText.slice(0, newline < 0 ? boundedText.length : newline);
|
|
2806
|
-
const firstLine = stripTerminalSequences(boundedFirstLine).trim();
|
|
2807
|
-
if (firstLine) return truncateToWidth(firstLine, 72, "…");
|
|
2808
|
-
if (component.result) return component.result.isError ? "Failed" : "Done";
|
|
2809
|
-
return component.executionStarted ? "Running…" : "Pending";
|
|
2810
|
-
};
|
|
2811
|
-
|
|
2812
|
-
const renderThirdPartyCompact = (component: any, width: number, theme: any): string[] => {
|
|
2813
|
-
const label = toolLabel(component.toolName, component.toolDefinition?.label);
|
|
2814
|
-
const args = conciseThirdPartyArgs(component.args);
|
|
2815
|
-
const dotColor = component.result?.isError
|
|
2816
|
-
? "error"
|
|
2817
|
-
: component.result && !component.isPartial
|
|
2818
|
-
? "success"
|
|
2819
|
-
: "dim";
|
|
2820
|
-
const title = theme.fg(dotColor, "● ") +
|
|
2821
|
-
theme.fg("text", theme.bold(label)) +
|
|
2822
|
-
(args ? theme.fg("muted", `(${args})`) : "");
|
|
2823
|
-
const result = thirdPartyResultSummary(component);
|
|
2824
|
-
const resultColor = component.result?.isError ? "error" : "muted";
|
|
2825
|
-
return [
|
|
2826
|
-
truncateToWidth(title, Math.max(1, width), "…"),
|
|
2827
|
-
theme.fg(resultColor, truncateToWidth(` └ ${result}`, Math.max(1, width), "…")),
|
|
2828
|
-
];
|
|
2829
|
-
};
|
|
2830
|
-
|
|
2831
2959
|
const clearToolActivityHolds = () => {
|
|
2832
2960
|
for (const hold of toolActivityHolds.values()) {
|
|
2833
2961
|
if (hold.timer) clearTimeout(hold.timer);
|
|
@@ -2937,6 +3065,38 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2937
3065
|
const originalToolHandleMouse = toolExecutionPrototype.handleMouse;
|
|
2938
3066
|
const renderedModeKey = Symbol("pretty-tui.tool-rendered-mode");
|
|
2939
3067
|
const cleanChildRenderCacheKey = Symbol("pretty-tui.clean-child-render-cache");
|
|
3068
|
+
const standaloneViews = new WeakMap<object, { projection: ToolProjection; offset: number; width: number }>();
|
|
3069
|
+
const projectTool = (owner: any, width: number, theme: any, mode: "compact" | "full") =>
|
|
3070
|
+
renderToolProjection(owner, width, theme, {
|
|
3071
|
+
mode, nested: nestedTools, nestedExpanded: Boolean(owner.expanded), panels: renderMode !== "clean",
|
|
3072
|
+
renderNative: (nativeWidth) => originalToolRender.call(owner, nativeWidth),
|
|
3073
|
+
});
|
|
3074
|
+
const handleProjectionMouse = (owner: any, projection: ToolProjection, event: any) => {
|
|
3075
|
+
const isClick = event.type === "click" && event.button === "left";
|
|
3076
|
+
const action = projection.actions.get(event.y);
|
|
3077
|
+
if (isClick && action) {
|
|
3078
|
+
nestedTools.toggle(action, Boolean(owner.expanded));
|
|
3079
|
+
owner.updateDisplay?.();
|
|
3080
|
+
owner.ui?.requestRender?.();
|
|
3081
|
+
return { handled: true };
|
|
3082
|
+
}
|
|
3083
|
+
if (isClick && projection.toggleRows.has(event.y)) {
|
|
3084
|
+
originalSetExpanded.call(owner, !owner.expanded);
|
|
3085
|
+
owner.ui?.requestRender?.();
|
|
3086
|
+
return { handled: true };
|
|
3087
|
+
}
|
|
3088
|
+
const native = projection.native;
|
|
3089
|
+
const sourceY = projection.sourceRows?.get(event.y);
|
|
3090
|
+
if (native && (projection.sourceRows ? sourceY !== undefined : event.y >= native.y && event.y < native.y + native.height)) {
|
|
3091
|
+
return originalToolHandleMouse.call(owner, {
|
|
3092
|
+
...event, x: Math.max(0, event.x - native.x),
|
|
3093
|
+
y: sourceY ?? event.y - native.y + native.sourceY,
|
|
3094
|
+
width: native.width, height: native.height + native.sourceY,
|
|
3095
|
+
});
|
|
3096
|
+
}
|
|
3097
|
+
// Projected rows must never be interpreted as native renderer rows.
|
|
3098
|
+
return isClick ? { handled: true } : undefined;
|
|
3099
|
+
};
|
|
2940
3100
|
const revealCleanGroup = revealActivityGroup;
|
|
2941
3101
|
const collapseCleanGroup = collapseActivityGroup;
|
|
2942
3102
|
const patchedMarkExecutionStarted = function (this: any) {
|
|
@@ -2972,7 +3132,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2972
3132
|
return originalSetExpanded.call(this, expanded);
|
|
2973
3133
|
};
|
|
2974
3134
|
const patchedToolRender = function (this: any, width: number): string[] {
|
|
2975
|
-
|
|
3135
|
+
toolComponents.set(this.toolCallId, this);
|
|
2976
3136
|
knownToolCallIds.add(this.toolCallId);
|
|
2977
3137
|
const member = activityTimeline.addTool(this.toolCallId, this.toolName);
|
|
2978
3138
|
cleanToolNames.set(
|
|
@@ -2985,7 +3145,24 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2985
3145
|
}
|
|
2986
3146
|
|
|
2987
3147
|
const group = activityTimeline.groupForMember(member.id);
|
|
2988
|
-
if (renderMode !== "clean" || !group)
|
|
3148
|
+
if (renderMode !== "clean" || !group) {
|
|
3149
|
+
const projection = renderMode !== "clean"
|
|
3150
|
+
? projectTool(this, width, defaultActivityTheme(), renderMode)
|
|
3151
|
+
: undefined;
|
|
3152
|
+
if (!projection) {
|
|
3153
|
+
standaloneViews.delete(this);
|
|
3154
|
+
mouseClickHitTests.delete(this);
|
|
3155
|
+
return originalToolRender.call(this, width);
|
|
3156
|
+
}
|
|
3157
|
+
const offset = renderMode === "full" ? 2 : 1;
|
|
3158
|
+
standaloneViews.set(this, { projection, offset, width });
|
|
3159
|
+
mouseClickHitTests.set(this, (event) => event.width === width && (
|
|
3160
|
+
projection.actions.has(event.y - offset) || projection.toggleRows.has(event.y - offset) ||
|
|
3161
|
+
Boolean(projection.sourceRows?.has(event.y - offset))
|
|
3162
|
+
));
|
|
3163
|
+
return [...Array<string>(offset).fill(""), ...projection.lines];
|
|
3164
|
+
}
|
|
3165
|
+
standaloneViews.delete(this);
|
|
2989
3166
|
const position = activityMemberPosition(group, member);
|
|
2990
3167
|
let renderedHeight = 0;
|
|
2991
3168
|
let nestedClickActions: Map<number, string> | undefined;
|
|
@@ -3019,8 +3196,6 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3019
3196
|
// Other extensions (notably FFF in override mode) can register their own
|
|
3020
3197
|
// grep/find renderers under built-in names. Their default boxed shell is
|
|
3021
3198
|
// not one of our self-rendering specialized tools.
|
|
3022
|
-
const thirdParty = !SPECIALIZED_TOOL_NAMES.has(this.toolName) ||
|
|
3023
|
-
this.toolDefinition?.renderShell !== "self";
|
|
3024
3199
|
const cacheable = nestedTools.count(this.toolCallId) === 0 && !this.expanded && Boolean(this.result) && !this.isPartial;
|
|
3025
3200
|
const cached = cacheable ? this[cleanChildRenderCacheKey] : undefined;
|
|
3026
3201
|
let decoratedContent: string[];
|
|
@@ -3038,43 +3213,21 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3038
3213
|
decoratedContent = cached.lines;
|
|
3039
3214
|
} else {
|
|
3040
3215
|
let contentLines: string[];
|
|
3041
|
-
|
|
3042
|
-
|
|
3216
|
+
const projection = projectTool(this, childWidth, childTheme, "compact");
|
|
3217
|
+
if (projection) {
|
|
3043
3218
|
contentLines = projection.lines;
|
|
3044
|
-
nestedClickActions = projection.actions;
|
|
3045
|
-
} else if (thirdParty) {
|
|
3046
|
-
const summaryLines = renderThirdPartyCompact(this, childWidth, childTheme);
|
|
3047
|
-
if (this.expanded) {
|
|
3048
|
-
const detailPrefix = " │ ";
|
|
3049
|
-
const nativeLines = originalToolRender.call(
|
|
3050
|
-
this,
|
|
3051
|
-
Math.max(1, childWidth - visibleWidth(detailPrefix)),
|
|
3052
|
-
);
|
|
3053
|
-
const nativeContentLines = nativeLines[0] === "" ? nativeLines.slice(1) : nativeLines;
|
|
3054
|
-
contentLines = [
|
|
3055
|
-
summaryLines[0],
|
|
3056
|
-
...nativeContentLines.map((line: string, index: number) =>
|
|
3057
|
-
childTheme.fg(
|
|
3058
|
-
"dim",
|
|
3059
|
-
index === nativeContentLines.length - 1 ? " └ " : detailPrefix,
|
|
3060
|
-
) + stripAnsiBackgrounds(line)
|
|
3061
|
-
),
|
|
3062
|
-
];
|
|
3063
|
-
} else {
|
|
3064
|
-
contentLines = summaryLines;
|
|
3065
|
-
}
|
|
3219
|
+
if (nestedTools.count(this.toolCallId) > 0) nestedClickActions = projection.actions;
|
|
3066
3220
|
} else {
|
|
3067
3221
|
const lines = originalToolRender.call(this, childWidth);
|
|
3068
3222
|
if (lines.length === 0) return lines;
|
|
3069
3223
|
contentLines = lines[0] === "" ? lines.slice(1) : lines;
|
|
3070
3224
|
}
|
|
3071
|
-
decoratedContent = contentLines.map((line: string, index: number) =>
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
);
|
|
3225
|
+
decoratedContent = contentLines.map((line: string, index: number) => {
|
|
3226
|
+
const prefix = index === 0 ? childPrefix : continuation;
|
|
3227
|
+
const rendered = truncateToWidth(prefix + line, Math.max(1, width), "");
|
|
3228
|
+
return selectionMetadataActive()
|
|
3229
|
+
? selectableRow(rendered, visibleWidth(rendered), [[visibleWidth(prefix), visibleWidth(rendered)]]) : rendered;
|
|
3230
|
+
});
|
|
3078
3231
|
if (cacheable) {
|
|
3079
3232
|
this[cleanChildRenderCacheKey] = {
|
|
3080
3233
|
width,
|
|
@@ -3102,6 +3255,11 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3102
3255
|
const group = member ? activityTimeline.groupForMember(member.id) : undefined;
|
|
3103
3256
|
const isLeftClick = event.type === "click" && event.button === "left";
|
|
3104
3257
|
if (renderMode !== "clean" || !member || !group) {
|
|
3258
|
+
patchedToolRender.call(this, event.width);
|
|
3259
|
+
const view = standaloneViews.get(this);
|
|
3260
|
+
if (view) return handleProjectionMouse(this, view.projection, {
|
|
3261
|
+
...event, y: event.y - view.offset, height: view.projection.lines.length,
|
|
3262
|
+
});
|
|
3105
3263
|
return originalToolHandleMouse.call(this, event);
|
|
3106
3264
|
}
|
|
3107
3265
|
const position = activityMemberPosition(group, member);
|
|
@@ -3128,33 +3286,15 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3128
3286
|
return { handled: true };
|
|
3129
3287
|
}
|
|
3130
3288
|
|
|
3131
|
-
if (nestedTools.count(this.toolCallId) > 0
|
|
3289
|
+
if (nestedTools.count(this.toolCallId) > 0 || isThirdPartyTool(this)) {
|
|
3132
3290
|
const theme = cleanThemeForToolCall(this.toolCallId) ?? activityGroupTheme(group);
|
|
3133
|
-
const { childWidth } = activityTreeStyle(group, member, event.width, theme);
|
|
3134
|
-
const projection =
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
this.ui?.requestRender?.();
|
|
3141
|
-
}
|
|
3142
|
-
// The parent title is informational; only disclosure rows act on clicks.
|
|
3143
|
-
// Nested results are UI projections, not native renderer rows.
|
|
3144
|
-
return { handled: true };
|
|
3145
|
-
}
|
|
3146
|
-
|
|
3147
|
-
// Self-rendering tools reserve y=0 for Pi's leading spacer. Clean mode
|
|
3148
|
-
// replaces that spacer with the compact header for non-first members.
|
|
3149
|
-
const thirdPartyHeaderY = position.first ? summaryHeight + 1 : 0;
|
|
3150
|
-
if (
|
|
3151
|
-
(!SPECIALIZED_TOOL_NAMES.has(this.toolName) || this.toolDefinition?.renderShell !== "self") &&
|
|
3152
|
-
isLeftClick &&
|
|
3153
|
-
event.y === thirdPartyHeaderY
|
|
3154
|
-
) {
|
|
3155
|
-
originalSetExpanded.call(this, !this.expanded);
|
|
3156
|
-
this.ui?.requestRender?.();
|
|
3157
|
-
return { handled: true };
|
|
3291
|
+
const { childWidth, prefixWidth } = activityTreeStyle(group, member, event.width, theme);
|
|
3292
|
+
const projection = projectTool(this, childWidth, theme, "compact");
|
|
3293
|
+
if (projection) return handleProjectionMouse(this, projection, {
|
|
3294
|
+
...event, x: Math.max(0, event.x - prefixWidth),
|
|
3295
|
+
y: event.y - (position.first ? summaryHeight + 1 : 0),
|
|
3296
|
+
width: childWidth, height: projection.lines.length,
|
|
3297
|
+
});
|
|
3158
3298
|
}
|
|
3159
3299
|
|
|
3160
3300
|
if (!this.result && isLeftClick) {
|
|
@@ -3653,7 +3793,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3653
3793
|
cleanCompactToolCallIds.clear();
|
|
3654
3794
|
cleanGroupToolCallIds.clear();
|
|
3655
3795
|
cleanToolCallGroupOwners.clear();
|
|
3656
|
-
|
|
3796
|
+
toolComponents.clear();
|
|
3657
3797
|
cleanToolThemes.clear();
|
|
3658
3798
|
cleanToolNames.clear();
|
|
3659
3799
|
clearToolActivityHolds();
|
|
@@ -3902,7 +4042,11 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3902
4042
|
finishGroup();
|
|
3903
4043
|
};
|
|
3904
4044
|
|
|
3905
|
-
pi.on("session_start", (_event, ctx) =>
|
|
4045
|
+
pi.on("session_start", (_event, ctx) => {
|
|
4046
|
+
fullThinkingHidden = false;
|
|
4047
|
+
compactThinkingHidden = true;
|
|
4048
|
+
restoreCleanSession(ctx);
|
|
4049
|
+
});
|
|
3906
4050
|
pi.on("session_tree", (_event, ctx) => restoreCleanSession(ctx));
|
|
3907
4051
|
pi.on("session_before_compact", () => {
|
|
3908
4052
|
// Pi emits this for manual, threshold, and overflow-recovery compaction.
|
|
@@ -4071,7 +4215,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
4071
4215
|
cleanRun.activeToolCallId = undefined;
|
|
4072
4216
|
cleanRun.activeToolName = undefined;
|
|
4073
4217
|
cleanRun.activity = "thinking";
|
|
4074
|
-
|
|
4218
|
+
startWorkingElapsedTimer();
|
|
4075
4219
|
});
|
|
4076
4220
|
pi.on("tool_execution_start", (event) => {
|
|
4077
4221
|
if ((event as any).parentToolCallId) {
|
|
@@ -4222,9 +4366,43 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
4222
4366
|
});
|
|
4223
4367
|
});
|
|
4224
4368
|
|
|
4369
|
+
const nativeDefinitions: Record<string, any> = {
|
|
4370
|
+
read: createReadToolDefinition(cwd), bash: createBashToolDefinition(cwd),
|
|
4371
|
+
edit: createEditToolDefinition(cwd), write: createWriteToolDefinition(cwd),
|
|
4372
|
+
grep: createGrepToolDefinition(cwd), find: createFindToolDefinition(cwd), ls: createLsToolDefinition(cwd),
|
|
4373
|
+
};
|
|
4374
|
+
const registerStyledTool = (definition: any) => {
|
|
4375
|
+
pi.registerToolRenderer((name, next) => {
|
|
4376
|
+
const downstream = next();
|
|
4377
|
+
if (name !== definition.name) return downstream;
|
|
4378
|
+
// A same-named extension/SDK tool owns its own rendering and parameters.
|
|
4379
|
+
// In particular, do not apply built-in summaries to an unrelated schema.
|
|
4380
|
+
const source = pi.getAllTools().find((tool) => tool.name === name)?.sourceInfo;
|
|
4381
|
+
if (source && source.path !== `builtin:${name}`) {
|
|
4382
|
+
return downstream ? { ...downstream, [THIRD_PARTY_TOOL]: true } : downstream;
|
|
4383
|
+
}
|
|
4384
|
+
const native = nativeDefinitions[name];
|
|
4385
|
+
const select = (kind: "renderCall" | "renderResult", args: any[]) => {
|
|
4386
|
+
const context = args.at(-1);
|
|
4387
|
+
const useNative = renderMode === "full" || renderMode === "compact" && context.expanded;
|
|
4388
|
+
const namespace = useNative ? "nativeFull" : "pretty";
|
|
4389
|
+
const state = context.state[namespace] ??= { state: {} };
|
|
4390
|
+
const scoped = { ...context, state: state.state, lastComponent: state[kind] };
|
|
4391
|
+
const renderer = useNative ? downstream?.[kind] ?? native[kind] : definition[kind];
|
|
4392
|
+
const component = renderer?.(...args.slice(0, -1), scoped);
|
|
4393
|
+
state[kind] = component;
|
|
4394
|
+
return component;
|
|
4395
|
+
};
|
|
4396
|
+
return { ...downstream, renderShell: "self",
|
|
4397
|
+
renderCall: (...args: any[]) => select("renderCall", args),
|
|
4398
|
+
renderResult: (...args: any[]) => select("renderResult", args),
|
|
4399
|
+
};
|
|
4400
|
+
});
|
|
4401
|
+
};
|
|
4402
|
+
|
|
4225
4403
|
const read = createReadTool(cwd);
|
|
4226
4404
|
const readLabel = read.label || read.name;
|
|
4227
|
-
|
|
4405
|
+
registerStyledTool({
|
|
4228
4406
|
...read,
|
|
4229
4407
|
renderShell: "self",
|
|
4230
4408
|
renderCall(args: any, theme: any, context: any) {
|
|
@@ -4253,7 +4431,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
4253
4431
|
|
|
4254
4432
|
const bash = createBashTool(cwd);
|
|
4255
4433
|
const bashLabel = bash.label || bash.name;
|
|
4256
|
-
|
|
4434
|
+
registerStyledTool({
|
|
4257
4435
|
...bash,
|
|
4258
4436
|
renderShell: "self",
|
|
4259
4437
|
renderCall(args: any, theme: any, context: any) {
|
|
@@ -4304,7 +4482,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
4304
4482
|
|
|
4305
4483
|
const edit = createEditTool(cwd);
|
|
4306
4484
|
const editLabel = edit.label || edit.name;
|
|
4307
|
-
|
|
4485
|
+
registerStyledTool({
|
|
4308
4486
|
...edit,
|
|
4309
4487
|
renderShell: "self",
|
|
4310
4488
|
renderCall(args: any, theme: any, context: any) {
|
|
@@ -4347,7 +4525,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
4347
4525
|
|
|
4348
4526
|
const write = createWriteTool(cwd);
|
|
4349
4527
|
const writeLabel = write.label || write.name;
|
|
4350
|
-
|
|
4528
|
+
registerStyledTool({
|
|
4351
4529
|
...write,
|
|
4352
4530
|
renderShell: "self",
|
|
4353
4531
|
renderCall(args: any, theme: any, context: any) {
|
|
@@ -4379,7 +4557,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
4379
4557
|
|
|
4380
4558
|
const grep = createGrepTool(cwd);
|
|
4381
4559
|
const grepLabel = grep.label || grep.name;
|
|
4382
|
-
|
|
4560
|
+
registerStyledTool({
|
|
4383
4561
|
...grep,
|
|
4384
4562
|
renderShell: "self",
|
|
4385
4563
|
renderCall(args: any, theme: any, context: any) {
|
|
@@ -4400,7 +4578,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
4400
4578
|
|
|
4401
4579
|
const find = createFindTool(cwd);
|
|
4402
4580
|
const findLabel = find.label || find.name;
|
|
4403
|
-
|
|
4581
|
+
registerStyledTool({
|
|
4404
4582
|
...find,
|
|
4405
4583
|
renderShell: "self",
|
|
4406
4584
|
renderCall(args: any, theme: any, context: any) {
|
|
@@ -4419,7 +4597,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
4419
4597
|
|
|
4420
4598
|
const ls = createLsTool(cwd);
|
|
4421
4599
|
const lsLabel = ls.label || ls.name;
|
|
4422
|
-
|
|
4600
|
+
registerStyledTool({
|
|
4423
4601
|
...ls,
|
|
4424
4602
|
renderShell: "self",
|
|
4425
4603
|
renderCall(args: any, theme: any, context: any) {
|