pi-pretty-tui 0.1.4 → 0.1.6
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 +8 -6
- package/extensions/index.ts +438 -30
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# pi-pretty-tui
|
|
2
2
|
|
|
3
|
-
A visual refinement extension for the [Pi coding agent](https://pi.dev/). It provides compact tool-call rendering and cleaner list presentation while preserving Pi's built-in
|
|
3
|
+
A visual refinement extension for the [Pi coding agent](https://pi.dev/). It provides framed user messages, compact tool-call rendering, and cleaner list presentation while preserving Pi's built-in behavior.
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
+
- Rounded, titled `User` message frames with theme-aware borders, filled backgrounds, and Markdown support
|
|
10
|
+
- A rounded prompt editor that preserves native cursor, IME, autocomplete, and mouse behavior
|
|
9
11
|
- Compact `Read`, `Bash`, `Edit`, `Write`, `Grep`, `Find`, and `List` calls
|
|
10
12
|
- Concise per-tool call and result summaries with expandable details
|
|
11
13
|
- Native-style `Write` previews in `full` mode: first 10 lines when collapsed, full content when expanded
|
|
@@ -37,9 +39,9 @@ Run `/pretty-tui` to choose a mode interactively, or set one directly:
|
|
|
37
39
|
/pretty-tui status
|
|
38
40
|
```
|
|
39
41
|
|
|
40
|
-
- `full
|
|
42
|
+
- `full`: preserve each tool's detailed call renderer and normal result preview. Press `Ctrl+O` to expand available output, diffs, and content.
|
|
41
43
|
- `compact`: when collapsed, all supported built-in tools use concise one-line call summaries and final result summaries. Long commands, written content, edit diffs, and tool output stay hidden until expanded; press `Ctrl+O` to reveal the full details.
|
|
42
|
-
- `clean
|
|
44
|
+
- `clean` (default): collapse supported tool calls into a single `Running(...)` status; the activity slot shows a tool name as soon as its streamed tool call appears, such as `Running(3 tool calls · Read)`, and remains visible for at least 1 second before changing to `thinking...`. Once settled, the label becomes `Done(...)`. Click a group status to reveal all tools in that group as indented compact children while retaining the parent `Running`/`Done` row, then click an individual tool to toggle its full details. Visible assistant text starts a new tool group, so later calls get a separate status. Collapsed thinking blocks stay hidden without Pi's `Thinking...` placeholder; press `Ctrl+O` to reveal thinking content and all expanded tool calls/output in transcript order.
|
|
43
45
|
|
|
44
46
|
Clean mode covers the built-in tools managed by this package: `read`, `bash`, `edit`, `write`, `grep`, `find`, and `ls`. Third-party tools keep their own rendering.
|
|
45
47
|
|
|
@@ -47,13 +49,13 @@ The selected mode applies immediately and persists in `~/.pi/agent/pretty-tui.js
|
|
|
47
49
|
|
|
48
50
|
## Expand tool output
|
|
49
51
|
|
|
50
|
-
Press `Ctrl+O` (Pi's default `app.tools.expand` keybinding) to show or hide detailed output, edit diffs, complete `Write` content, full tool details, and thinking content in `compact` or `clean` mode.
|
|
52
|
+
Press `Ctrl+O` (Pi's default `app.tools.expand` keybinding) to show or hide detailed output, edit diffs, complete `Write` content, full tool details, and thinking content in `compact` or `clean` mode. In clean mode, mouse interaction also supports progressive disclosure: click a group status to retain it as a parent and reveal compact child tools, click one child for its full view, or click the parent again to collapse the group.
|
|
51
53
|
|
|
52
54
|
## Compatibility notice
|
|
53
55
|
|
|
54
|
-
Tool rendering uses Pi's documented extension APIs. Clean thinking and active-tool state follow Pi's built-in expansion and execution transitions through its exported interactive components.
|
|
56
|
+
Tool rendering uses Pi's documented extension APIs. Clean thinking and active-tool state follow Pi's built-in expansion and execution transitions through its exported interactive components. Framing native user messages, rounding the native prompt editor, and changing unordered-list markers require reload-safe runtime patches because Pi does not currently expose public renderer hooks for those presentation details. User Markdown and terminal semantic zones remain handled by Pi, and code blocks use Pi's original renderer without modification.
|
|
55
57
|
|
|
56
|
-
No Pi source files are modified.
|
|
58
|
+
No Pi source files are modified. Runtime patches are removed during session shutdown, but a future Pi release may require this extension to be updated.
|
|
57
59
|
|
|
58
60
|
## Uninstall
|
|
59
61
|
|
package/extensions/index.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AssistantMessageComponent,
|
|
3
|
+
CustomEditor,
|
|
3
4
|
InteractiveMode,
|
|
4
5
|
ToolExecutionComponent,
|
|
6
|
+
UserMessageComponent,
|
|
5
7
|
createBashTool,
|
|
6
8
|
createEditTool,
|
|
7
9
|
createFindTool,
|
|
@@ -40,16 +42,46 @@ const loadConfig = (path: string): PrettyTuiConfig => {
|
|
|
40
42
|
}
|
|
41
43
|
};
|
|
42
44
|
|
|
43
|
-
/** Polished tool calls and list rendering for Pi's TUI. */
|
|
45
|
+
/** Polished user messages, tool calls, and list rendering for Pi's TUI. */
|
|
44
46
|
export default function prettyTui(pi: ExtensionAPI) {
|
|
45
47
|
const cwd = process.cwd();
|
|
46
48
|
const configPath = join(getAgentDir(), "pretty-tui.json");
|
|
47
49
|
let config = loadConfig(configPath);
|
|
48
50
|
const configuredMode = config.mode ?? config.bash?.mode;
|
|
49
|
-
let renderMode: PrettyTuiMode = configuredMode === "compact" || configuredMode === "clean"
|
|
51
|
+
let renderMode: PrettyTuiMode = configuredMode === "full" || configuredMode === "compact" || configuredMode === "clean"
|
|
50
52
|
? configuredMode
|
|
51
|
-
: "
|
|
53
|
+
: "clean";
|
|
52
54
|
let cleanToolsExpanded = false;
|
|
55
|
+
let changingAllToolsExpansion = false;
|
|
56
|
+
const cleanCompactToolCallIds = new Set<string>();
|
|
57
|
+
const cleanGroupToolCallIds = new Map<string, string[]>();
|
|
58
|
+
const cleanToolCallGroupOwners = new Map<string, string>();
|
|
59
|
+
const cleanToolComponents = new Map<string, any>();
|
|
60
|
+
const cleanToolThemes = new Map<string, any>();
|
|
61
|
+
const cleanToolNames = new Map<string, string>();
|
|
62
|
+
let renderCleanGroupSummary = (_lastToolCallId: string, _width: number): string[] => [];
|
|
63
|
+
|
|
64
|
+
const setCleanGroupMembers = (lastToolCallId: string, toolCallIds: string[]) => {
|
|
65
|
+
cleanGroupToolCallIds.set(lastToolCallId, toolCallIds);
|
|
66
|
+
for (const toolCallId of toolCallIds) cleanToolCallGroupOwners.set(toolCallId, lastToolCallId);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const isCleanGroupRevealed = (toolCallId: string): boolean => {
|
|
70
|
+
if (cleanCompactToolCallIds.has(toolCallId)) return true;
|
|
71
|
+
const groupOwner = cleanToolCallGroupOwners.get(toolCallId);
|
|
72
|
+
const groupToolCallIds = groupOwner ? cleanGroupToolCallIds.get(groupOwner) : undefined;
|
|
73
|
+
return groupToolCallIds?.some((id) => cleanCompactToolCallIds.has(id)) ?? false;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const cleanThemeForToolCall = (toolCallId: string): any => {
|
|
77
|
+
const directTheme = cleanToolThemes.get(toolCallId);
|
|
78
|
+
if (directTheme) return directTheme;
|
|
79
|
+
const groupOwner = cleanToolCallGroupOwners.get(toolCallId);
|
|
80
|
+
const groupToolCallIds = groupOwner ? cleanGroupToolCallIds.get(groupOwner) : undefined;
|
|
81
|
+
return groupToolCallIds
|
|
82
|
+
?.map((id) => cleanToolThemes.get(id))
|
|
83
|
+
.find(Boolean);
|
|
84
|
+
};
|
|
53
85
|
|
|
54
86
|
const saveRenderMode = (mode: PrettyTuiMode) => {
|
|
55
87
|
config = { ...config, mode };
|
|
@@ -94,7 +126,9 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
94
126
|
return;
|
|
95
127
|
}
|
|
96
128
|
|
|
129
|
+
cleanCompactToolCallIds.clear();
|
|
97
130
|
renderMode = requested;
|
|
131
|
+
for (const component of cleanToolComponents.values()) component.updateDisplay?.();
|
|
98
132
|
try {
|
|
99
133
|
saveRenderMode(renderMode);
|
|
100
134
|
ctx.ui.notify(`pi-pretty-tui mode set to: ${renderMode}`, "info");
|
|
@@ -105,6 +139,149 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
105
139
|
},
|
|
106
140
|
});
|
|
107
141
|
|
|
142
|
+
// Give Pi's main prompt editor a complete rounded frame. Render the native
|
|
143
|
+
// editor at a two-column narrower width so cursor layout, wrapping, IME, and
|
|
144
|
+
// autocomplete remain native, then add one themed border column per side.
|
|
145
|
+
const customEditorPrototype = CustomEditor.prototype as any;
|
|
146
|
+
const editorFramePatchKey = Symbol.for("pretty-tui.rounded-editor-frame");
|
|
147
|
+
if (!customEditorPrototype[editorFramePatchKey]) {
|
|
148
|
+
const hadOwnRender = Object.prototype.hasOwnProperty.call(customEditorPrototype, "render");
|
|
149
|
+
const hadOwnHandleMouse = Object.prototype.hasOwnProperty.call(customEditorPrototype, "handleMouse");
|
|
150
|
+
const originalEditorRender = customEditorPrototype.render;
|
|
151
|
+
const originalEditorHandleMouse = customEditorPrototype.handleMouse;
|
|
152
|
+
const patchedEditorRender = function (this: any, width: number): string[] {
|
|
153
|
+
if (width < 3) return originalEditorRender.call(this, width);
|
|
154
|
+
|
|
155
|
+
const innerWidth = width - 2;
|
|
156
|
+
const lines = originalEditorRender.call(this, innerWidth) as string[];
|
|
157
|
+
const visibleLineCount = Math.max(0, Number(this.renderedVisibleLineCount) || 0);
|
|
158
|
+
const bottomBorderIndex = visibleLineCount + 1;
|
|
159
|
+
const fitInnerWidth = (line: string) =>
|
|
160
|
+
line + " ".repeat(Math.max(0, innerWidth - visibleWidth(line)));
|
|
161
|
+
|
|
162
|
+
return lines.map((line, index) => {
|
|
163
|
+
const fitted = fitInnerWidth(line);
|
|
164
|
+
if (index === 0) return this.borderColor("╭") + fitted + this.borderColor("╮");
|
|
165
|
+
if (index === bottomBorderIndex) return this.borderColor("╰") + fitted + this.borderColor("╯");
|
|
166
|
+
if (index < bottomBorderIndex) return this.borderColor("│") + fitted + this.borderColor("│");
|
|
167
|
+
// Autocomplete remains outside the input frame, aligned to its content.
|
|
168
|
+
return " " + fitted + " ";
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
const patchedEditorHandleMouse = function (this: any, event: any) {
|
|
172
|
+
if (event.width < 3) return originalEditorHandleMouse.call(this, event);
|
|
173
|
+
const innerWidth = event.width - 2;
|
|
174
|
+
return originalEditorHandleMouse.call(this, {
|
|
175
|
+
...event,
|
|
176
|
+
x: Math.max(0, Math.min(innerWidth - 1, event.x - 1)),
|
|
177
|
+
width: innerWidth,
|
|
178
|
+
});
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
customEditorPrototype[editorFramePatchKey] = {
|
|
182
|
+
hadOwnRender,
|
|
183
|
+
hadOwnHandleMouse,
|
|
184
|
+
originalRender: originalEditorRender,
|
|
185
|
+
originalHandleMouse: originalEditorHandleMouse,
|
|
186
|
+
patchedRender: patchedEditorRender,
|
|
187
|
+
patchedHandleMouse: patchedEditorHandleMouse,
|
|
188
|
+
};
|
|
189
|
+
customEditorPrototype.render = patchedEditorRender;
|
|
190
|
+
customEditorPrototype.handleMouse = patchedEditorHandleMouse;
|
|
191
|
+
|
|
192
|
+
pi.on("session_shutdown", () => {
|
|
193
|
+
const patch = customEditorPrototype[editorFramePatchKey];
|
|
194
|
+
if (!patch) return;
|
|
195
|
+
if (patch.patchedRender === customEditorPrototype.render) {
|
|
196
|
+
if (patch.hadOwnRender) customEditorPrototype.render = patch.originalRender;
|
|
197
|
+
else delete customEditorPrototype.render;
|
|
198
|
+
}
|
|
199
|
+
if (patch.patchedHandleMouse === customEditorPrototype.handleMouse) {
|
|
200
|
+
if (patch.hadOwnHandleMouse) customEditorPrototype.handleMouse = patch.originalHandleMouse;
|
|
201
|
+
else delete customEditorPrototype.handleMouse;
|
|
202
|
+
}
|
|
203
|
+
if (
|
|
204
|
+
customEditorPrototype.render === patch.originalRender &&
|
|
205
|
+
customEditorPrototype.handleMouse === patch.originalHandleMouse
|
|
206
|
+
) {
|
|
207
|
+
delete customEditorPrototype[editorFramePatchKey];
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Render user prompts as a titled, rounded frame while preserving Pi's
|
|
213
|
+
// original Markdown component, wrapping, output padding, and OSC 133 zones.
|
|
214
|
+
const userMessagePrototype = UserMessageComponent.prototype as any;
|
|
215
|
+
const userMessagePatchKey = Symbol.for("pretty-tui.user-message-frame");
|
|
216
|
+
if (!userMessagePrototype[userMessagePatchKey]) {
|
|
217
|
+
const originalUserMessageRebuild = userMessagePrototype.rebuild;
|
|
218
|
+
const patchedUserMessageRebuild = function (this: any) {
|
|
219
|
+
originalUserMessageRebuild.call(this);
|
|
220
|
+
const content = this.children?.[0] as any;
|
|
221
|
+
if (!content) return;
|
|
222
|
+
|
|
223
|
+
// Reuse Pi's theme-aware user background across the complete frame.
|
|
224
|
+
const userMessageBg = content.bgFn as ((text: string) => string) | undefined;
|
|
225
|
+
content.paddingX = 0;
|
|
226
|
+
content.paddingY = 0;
|
|
227
|
+
content.setBgFn?.(undefined);
|
|
228
|
+
content.invalidate?.();
|
|
229
|
+
|
|
230
|
+
const outputPad = Math.max(0, Number(this.outputPad) || 0);
|
|
231
|
+
const markdownTheme = this.markdownTheme;
|
|
232
|
+
const border = (text: string) => markdownTheme.quoteBorder(text);
|
|
233
|
+
const frame: Component = {
|
|
234
|
+
render(width: number): string[] {
|
|
235
|
+
const sidePad = Math.min(outputPad, Math.max(0, Math.floor((width - 1) / 2)));
|
|
236
|
+
const outer = " ".repeat(sidePad);
|
|
237
|
+
const frameWidth = Math.max(1, width - sidePad * 2);
|
|
238
|
+
const paintBackground = (line: string) => {
|
|
239
|
+
const padding = " ".repeat(Math.max(0, frameWidth - visibleWidth(line)));
|
|
240
|
+
return userMessageBg ? userMessageBg(line + padding) : line + padding;
|
|
241
|
+
};
|
|
242
|
+
if (frameWidth < 4) {
|
|
243
|
+
return content.render(frameWidth).map((line: string) => outer + paintBackground(line));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const title = " User ";
|
|
247
|
+
const topStart = "╭─";
|
|
248
|
+
const topTail = "─".repeat(Math.max(0, frameWidth - visibleWidth(topStart) - visibleWidth(title) - 1));
|
|
249
|
+
const top = border(topStart + markdownTheme.bold(title) + topTail + "╮");
|
|
250
|
+
const bottom = border("╰" + "─".repeat(Math.max(0, frameWidth - 2)) + "╯");
|
|
251
|
+
const contentWidth = Math.max(1, frameWidth - 4);
|
|
252
|
+
const body = content.render(contentWidth).map((line: string) => {
|
|
253
|
+
const padding = " ".repeat(Math.max(0, contentWidth - visibleWidth(line)));
|
|
254
|
+
return border("│ ") + line + padding + border(" │");
|
|
255
|
+
});
|
|
256
|
+
return [top, ...body, bottom].map((line: string) => outer + paintBackground(line));
|
|
257
|
+
},
|
|
258
|
+
invalidate() {
|
|
259
|
+
content.invalidate?.();
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
this.clear();
|
|
264
|
+
this.addChild(frame);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
userMessagePrototype[userMessagePatchKey] = {
|
|
268
|
+
originalRebuild: originalUserMessageRebuild,
|
|
269
|
+
patchedRebuild: patchedUserMessageRebuild,
|
|
270
|
+
};
|
|
271
|
+
userMessagePrototype.rebuild = patchedUserMessageRebuild;
|
|
272
|
+
|
|
273
|
+
pi.on("session_shutdown", () => {
|
|
274
|
+
const patch = userMessagePrototype[userMessagePatchKey];
|
|
275
|
+
if (!patch) return;
|
|
276
|
+
if (patch.patchedRebuild === userMessagePrototype.rebuild) {
|
|
277
|
+
userMessagePrototype.rebuild = patch.originalRebuild;
|
|
278
|
+
}
|
|
279
|
+
if (userMessagePrototype.rebuild === patch.originalRebuild) {
|
|
280
|
+
delete userMessagePrototype[userMessagePatchKey];
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
108
285
|
// Pi's built-in assistant component turns hidden thinking into a static
|
|
109
286
|
// "Thinking..." label. In collapsed clean mode, omit that label entirely;
|
|
110
287
|
// the clean-mode Running(...) row owns activity, while Ctrl+O still reveals
|
|
@@ -200,7 +377,13 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
200
377
|
const originalSetToolsExpanded = interactiveModePrototype.setToolsExpanded;
|
|
201
378
|
const patchedSetToolsExpanded = function (this: any, expanded: boolean) {
|
|
202
379
|
cleanToolsExpanded = expanded;
|
|
203
|
-
|
|
380
|
+
changingAllToolsExpansion = true;
|
|
381
|
+
if (!expanded) cleanCompactToolCallIds.clear();
|
|
382
|
+
try {
|
|
383
|
+
return originalSetToolsExpanded.call(this, expanded);
|
|
384
|
+
} finally {
|
|
385
|
+
changingAllToolsExpansion = false;
|
|
386
|
+
}
|
|
204
387
|
};
|
|
205
388
|
|
|
206
389
|
interactiveModePrototype[toolsExpansionPatchKey] = {
|
|
@@ -472,6 +655,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
472
655
|
count: number;
|
|
473
656
|
failed: number;
|
|
474
657
|
lastToolCallId: string;
|
|
658
|
+
toolCallIds?: string[];
|
|
475
659
|
activity?: string;
|
|
476
660
|
};
|
|
477
661
|
type ToolSummaryData = {
|
|
@@ -503,6 +687,8 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
503
687
|
requestRender?: () => void;
|
|
504
688
|
count: number;
|
|
505
689
|
failed: number;
|
|
690
|
+
currentToolCallIds: string[];
|
|
691
|
+
activeToolCallIds: Set<string>;
|
|
506
692
|
groups: ToolSummaryGroup[];
|
|
507
693
|
active: boolean;
|
|
508
694
|
settled: boolean;
|
|
@@ -510,6 +696,8 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
510
696
|
const cleanRun: CleanRunState = {
|
|
511
697
|
count: 0,
|
|
512
698
|
failed: 0,
|
|
699
|
+
currentToolCallIds: [],
|
|
700
|
+
activeToolCallIds: new Set(),
|
|
513
701
|
groups: [],
|
|
514
702
|
active: false,
|
|
515
703
|
settled: false,
|
|
@@ -596,6 +784,9 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
596
784
|
return () => heldActivity(hold);
|
|
597
785
|
};
|
|
598
786
|
|
|
787
|
+
const liveCleanToolCount = (): number =>
|
|
788
|
+
Math.max(cleanRun.count, cleanRun.currentToolCallIds.length);
|
|
789
|
+
|
|
599
790
|
const currentCleanActivity = (): string => {
|
|
600
791
|
if (cleanRun.activeToolCallId) {
|
|
601
792
|
return cleanRun.activeToolName ?? cleanRun.activity ?? "thinking...";
|
|
@@ -616,10 +807,23 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
616
807
|
const toolExecutionPatchKey = Symbol.for("pretty-tui.clean-tool-execution");
|
|
617
808
|
if (!toolExecutionPrototype[toolExecutionPatchKey]) {
|
|
618
809
|
const originalMarkExecutionStarted = toolExecutionPrototype.markExecutionStarted;
|
|
810
|
+
const originalSetExpanded = toolExecutionPrototype.setExpanded;
|
|
619
811
|
const originalToolRender = toolExecutionPrototype.render;
|
|
812
|
+
const originalToolHandleMouse = toolExecutionPrototype.handleMouse;
|
|
620
813
|
const renderedModeKey = Symbol("pretty-tui.tool-rendered-mode");
|
|
814
|
+
const revealCleanGroup = (groupToolCallIds: string[], ui?: any) => {
|
|
815
|
+
for (const toolCallId of groupToolCallIds) cleanCompactToolCallIds.add(toolCallId);
|
|
816
|
+
for (const toolCallId of groupToolCallIds) cleanToolComponents.get(toolCallId)?.updateDisplay();
|
|
817
|
+
ui?.requestRender?.();
|
|
818
|
+
};
|
|
621
819
|
const patchedMarkExecutionStarted = function (this: any) {
|
|
622
820
|
if (supportedTools.has(this.toolName)) {
|
|
821
|
+
if (!cleanRun.currentToolCallIds.includes(this.toolCallId)) {
|
|
822
|
+
cleanRun.currentToolCallIds.push(this.toolCallId);
|
|
823
|
+
}
|
|
824
|
+
cleanRun.activeToolCallIds.add(this.toolCallId);
|
|
825
|
+
cleanToolNames.set(this.toolCallId, toolDisplayName(this.toolName));
|
|
826
|
+
setCleanGroupMembers(this.toolCallId, cleanRun.currentToolCallIds.slice());
|
|
623
827
|
if (typeof this.ui?.requestRender === "function") {
|
|
624
828
|
cleanRun.requestRender = () => this.ui.requestRender();
|
|
625
829
|
}
|
|
@@ -629,22 +833,127 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
629
833
|
}
|
|
630
834
|
return originalMarkExecutionStarted.call(this);
|
|
631
835
|
};
|
|
836
|
+
const patchedSetExpanded = function (this: any, expanded: boolean) {
|
|
837
|
+
const groupToolCallIds = cleanGroupToolCallIds.get(this.toolCallId);
|
|
838
|
+
if (
|
|
839
|
+
renderMode === "clean" &&
|
|
840
|
+
!changingAllToolsExpansion &&
|
|
841
|
+
expanded &&
|
|
842
|
+
!this.expanded &&
|
|
843
|
+
!isCleanGroupRevealed(this.toolCallId) &&
|
|
844
|
+
groupToolCallIds?.length
|
|
845
|
+
) {
|
|
846
|
+
revealCleanGroup(groupToolCallIds, this.ui);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
return originalSetExpanded.call(this, expanded);
|
|
850
|
+
};
|
|
632
851
|
const patchedToolRender = function (this: any, width: number): string[] {
|
|
852
|
+
if (supportedTools.has(this.toolName)) {
|
|
853
|
+
cleanToolComponents.set(this.toolCallId, this);
|
|
854
|
+
knownToolCallIds.add(this.toolCallId);
|
|
855
|
+
}
|
|
633
856
|
if (this[renderedModeKey] !== renderMode) {
|
|
634
857
|
this[renderedModeKey] = renderMode;
|
|
635
858
|
this.updateDisplay();
|
|
636
859
|
}
|
|
637
|
-
|
|
860
|
+
|
|
861
|
+
const groupOwner = cleanToolCallGroupOwners.get(this.toolCallId);
|
|
862
|
+
const groupToolCallIds = groupOwner ? cleanGroupToolCallIds.get(groupOwner) : undefined;
|
|
863
|
+
const childIndex = groupToolCallIds?.indexOf(this.toolCallId) ?? -1;
|
|
864
|
+
const showAsChild = renderMode === "clean" && childIndex >= 0 && isCleanGroupRevealed(this.toolCallId);
|
|
865
|
+
if (!showAsChild || !groupOwner || !groupToolCallIds) {
|
|
866
|
+
return originalToolRender.call(this, width);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
const rawChildPrefix = childIndex === groupToolCallIds.length - 1 ? " └─ " : " ├─ ";
|
|
870
|
+
const rawContinuation = childIndex === groupToolCallIds.length - 1 ? " " : " │ ";
|
|
871
|
+
const childTheme = cleanThemeForToolCall(this.toolCallId);
|
|
872
|
+
const childPrefix = childTheme ? childTheme.fg("dim", rawChildPrefix) : rawChildPrefix;
|
|
873
|
+
const continuation = childTheme ? childTheme.fg("dim", rawContinuation) : rawContinuation;
|
|
874
|
+
const childWidth = Math.max(1, width - visibleWidth(rawChildPrefix));
|
|
875
|
+
const lines = originalToolRender.call(this, childWidth);
|
|
876
|
+
if (lines.length === 0) return lines;
|
|
877
|
+
|
|
878
|
+
const decoratedContent = lines.slice(1).map((line: string, index: number) =>
|
|
879
|
+
(index === 0 ? childPrefix : continuation) + line
|
|
880
|
+
);
|
|
881
|
+
if (childIndex !== 0) return decoratedContent;
|
|
882
|
+
return [lines[0], ...renderCleanGroupSummary(groupOwner, width), ...decoratedContent];
|
|
883
|
+
};
|
|
884
|
+
const patchedToolHandleMouse = function (this: any, event: any) {
|
|
885
|
+
const groupOwner = cleanToolCallGroupOwners.get(this.toolCallId);
|
|
886
|
+
const groupToolCallIds = groupOwner ? cleanGroupToolCallIds.get(groupOwner) : undefined;
|
|
887
|
+
const childIndex = groupToolCallIds?.indexOf(this.toolCallId) ?? -1;
|
|
888
|
+
const showAsChild = renderMode === "clean" && childIndex >= 0 && isCleanGroupRevealed(this.toolCallId);
|
|
889
|
+
const isLeftClick = event.type === "click" && event.button === "left";
|
|
890
|
+
|
|
891
|
+
// Pi normally ignores tool clicks until a result exists. Clean mode can
|
|
892
|
+
// still reveal the active call safely because this changes presentation
|
|
893
|
+
// only; it does not affect or delay execution.
|
|
894
|
+
if (
|
|
895
|
+
renderMode === "clean" &&
|
|
896
|
+
!showAsChild &&
|
|
897
|
+
isLeftClick &&
|
|
898
|
+
this.toolCallId === groupOwner &&
|
|
899
|
+
groupToolCallIds?.length
|
|
900
|
+
) {
|
|
901
|
+
revealCleanGroup(groupToolCallIds, this.ui);
|
|
902
|
+
return { handled: true };
|
|
903
|
+
}
|
|
904
|
+
if (!showAsChild || !groupToolCallIds) return originalToolHandleMouse.call(this, event);
|
|
905
|
+
|
|
906
|
+
const summaryHeight = childIndex === 0 ? renderCleanGroupSummary(groupOwner!, event.width).length : 0;
|
|
907
|
+
if (
|
|
908
|
+
childIndex === 0 &&
|
|
909
|
+
event.y > 0 &&
|
|
910
|
+
event.y <= summaryHeight &&
|
|
911
|
+
isLeftClick
|
|
912
|
+
) {
|
|
913
|
+
changingAllToolsExpansion = true;
|
|
914
|
+
try {
|
|
915
|
+
for (const toolCallId of groupToolCallIds) {
|
|
916
|
+
const component = cleanToolComponents.get(toolCallId);
|
|
917
|
+
if (component?.expanded) originalSetExpanded.call(component, false);
|
|
918
|
+
cleanCompactToolCallIds.delete(toolCallId);
|
|
919
|
+
}
|
|
920
|
+
for (const toolCallId of groupToolCallIds) cleanToolComponents.get(toolCallId)?.updateDisplay();
|
|
921
|
+
} finally {
|
|
922
|
+
changingAllToolsExpansion = false;
|
|
923
|
+
}
|
|
924
|
+
this.ui?.requestRender?.();
|
|
925
|
+
return { handled: true };
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
if (!this.result && isLeftClick) {
|
|
929
|
+
originalSetExpanded.call(this, !this.expanded);
|
|
930
|
+
this.ui?.requestRender?.();
|
|
931
|
+
return { handled: true };
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
const childPrefixWidth = visibleWidth(" ├─ ");
|
|
935
|
+
return originalToolHandleMouse.call(this, {
|
|
936
|
+
...event,
|
|
937
|
+
x: Math.max(0, event.x - childPrefixWidth),
|
|
938
|
+
y: event.y - summaryHeight + (childIndex === 0 ? 0 : 1),
|
|
939
|
+
width: Math.max(1, event.width - childPrefixWidth),
|
|
940
|
+
});
|
|
638
941
|
};
|
|
639
942
|
|
|
640
943
|
toolExecutionPrototype[toolExecutionPatchKey] = {
|
|
641
944
|
originalMarkExecutionStarted,
|
|
642
945
|
patchedMarkExecutionStarted,
|
|
946
|
+
originalSetExpanded,
|
|
947
|
+
patchedSetExpanded,
|
|
643
948
|
originalRender: originalToolRender,
|
|
644
949
|
patchedRender: patchedToolRender,
|
|
950
|
+
originalHandleMouse: originalToolHandleMouse,
|
|
951
|
+
patchedHandleMouse: patchedToolHandleMouse,
|
|
645
952
|
};
|
|
646
953
|
toolExecutionPrototype.markExecutionStarted = patchedMarkExecutionStarted;
|
|
954
|
+
toolExecutionPrototype.setExpanded = patchedSetExpanded;
|
|
647
955
|
toolExecutionPrototype.render = patchedToolRender;
|
|
956
|
+
toolExecutionPrototype.handleMouse = patchedToolHandleMouse;
|
|
648
957
|
|
|
649
958
|
pi.on("session_shutdown", () => {
|
|
650
959
|
const patch = toolExecutionPrototype[toolExecutionPatchKey];
|
|
@@ -652,12 +961,20 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
652
961
|
if (patch.patchedMarkExecutionStarted === toolExecutionPrototype.markExecutionStarted) {
|
|
653
962
|
toolExecutionPrototype.markExecutionStarted = patch.originalMarkExecutionStarted;
|
|
654
963
|
}
|
|
964
|
+
if (patch.patchedSetExpanded === toolExecutionPrototype.setExpanded) {
|
|
965
|
+
toolExecutionPrototype.setExpanded = patch.originalSetExpanded;
|
|
966
|
+
}
|
|
655
967
|
if (patch.patchedRender === toolExecutionPrototype.render) {
|
|
656
968
|
toolExecutionPrototype.render = patch.originalRender;
|
|
657
969
|
}
|
|
970
|
+
if (patch.patchedHandleMouse === toolExecutionPrototype.handleMouse) {
|
|
971
|
+
toolExecutionPrototype.handleMouse = patch.originalHandleMouse;
|
|
972
|
+
}
|
|
658
973
|
if (
|
|
659
974
|
toolExecutionPrototype.markExecutionStarted === patch.originalMarkExecutionStarted &&
|
|
660
|
-
toolExecutionPrototype.
|
|
975
|
+
toolExecutionPrototype.setExpanded === patch.originalSetExpanded &&
|
|
976
|
+
toolExecutionPrototype.render === patch.originalRender &&
|
|
977
|
+
toolExecutionPrototype.handleMouse === patch.originalHandleMouse
|
|
661
978
|
) {
|
|
662
979
|
delete toolExecutionPrototype[toolExecutionPatchKey];
|
|
663
980
|
}
|
|
@@ -670,6 +987,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
670
987
|
count: cleanRun.count,
|
|
671
988
|
failed: cleanRun.failed,
|
|
672
989
|
lastToolCallId: cleanRun.lastCompletedToolCallId,
|
|
990
|
+
toolCallIds: cleanRun.currentToolCallIds.slice(),
|
|
673
991
|
activity,
|
|
674
992
|
};
|
|
675
993
|
cleanRun.groups.push(group);
|
|
@@ -678,9 +996,12 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
678
996
|
failed: group.failed,
|
|
679
997
|
activity: activityValueForHold(group.lastToolCallId, activity),
|
|
680
998
|
});
|
|
999
|
+
setCleanGroupMembers(group.lastToolCallId, group.toolCallIds ?? []);
|
|
681
1000
|
}
|
|
682
1001
|
cleanRun.count = 0;
|
|
683
1002
|
cleanRun.failed = 0;
|
|
1003
|
+
cleanRun.currentToolCallIds = [];
|
|
1004
|
+
cleanRun.activeToolCallIds.clear();
|
|
684
1005
|
cleanRun.lastCompletedToolCallId = undefined;
|
|
685
1006
|
cleanRun.activeToolCallId = undefined;
|
|
686
1007
|
cleanRun.activeToolName = undefined;
|
|
@@ -728,6 +1049,27 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
728
1049
|
},
|
|
729
1050
|
});
|
|
730
1051
|
|
|
1052
|
+
renderCleanGroupSummary = (lastToolCallId: string, width: number): string[] => {
|
|
1053
|
+
const summaryTheme = cleanThemeForToolCall(lastToolCallId);
|
|
1054
|
+
if (!summaryTheme) return [];
|
|
1055
|
+
const settled = settledSummaries.get(lastToolCallId);
|
|
1056
|
+
if (settled) {
|
|
1057
|
+
return block([summaryRow(summaryTheme, settled.count, settled.failed, settled.activity)]).render(width);
|
|
1058
|
+
}
|
|
1059
|
+
if (cleanRun.activeToolCallId === lastToolCallId) {
|
|
1060
|
+
return block([summaryRow(
|
|
1061
|
+
summaryTheme,
|
|
1062
|
+
liveCleanToolCount(),
|
|
1063
|
+
cleanRun.failed,
|
|
1064
|
+
() => cleanRun.activeToolName ?? currentCleanActivity(),
|
|
1065
|
+
)]).render(width);
|
|
1066
|
+
}
|
|
1067
|
+
if (cleanRun.lastCompletedToolCallId === lastToolCallId && cleanRun.count > 0) {
|
|
1068
|
+
return block([summaryRow(summaryTheme, cleanRun.count, cleanRun.failed, currentCleanActivity)]).render(width);
|
|
1069
|
+
}
|
|
1070
|
+
return [];
|
|
1071
|
+
};
|
|
1072
|
+
|
|
731
1073
|
/**
|
|
732
1074
|
* Clean mode keeps Running/Done rows in the existing tool components. This
|
|
733
1075
|
* avoids waiting for agent_end (which can be followed by retry/compaction),
|
|
@@ -735,6 +1077,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
735
1077
|
*/
|
|
736
1078
|
const cleanToolCall = (theme: any, name: string, toolCallId: string): Component => {
|
|
737
1079
|
knownToolCallIds.add(toolCallId);
|
|
1080
|
+
cleanToolThemes.set(toolCallId, theme);
|
|
738
1081
|
return {
|
|
739
1082
|
render(width: number): string[] {
|
|
740
1083
|
if (renderMode !== "clean") return [];
|
|
@@ -748,7 +1091,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
748
1091
|
if (cleanRun.activeToolCallId === toolCallId) {
|
|
749
1092
|
return block([summaryRow(
|
|
750
1093
|
theme,
|
|
751
|
-
|
|
1094
|
+
liveCleanToolCount(),
|
|
752
1095
|
cleanRun.failed,
|
|
753
1096
|
() => cleanRun.activeToolName ?? name,
|
|
754
1097
|
)]).render(width);
|
|
@@ -783,10 +1126,16 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
783
1126
|
// In collapsed clean mode, every supported tool call is represented by its
|
|
784
1127
|
// Running row. Ctrl+O bypasses this and restores the normal full renderer.
|
|
785
1128
|
const hideCleanTool = (
|
|
786
|
-
|
|
1129
|
+
toolCallId: string,
|
|
787
1130
|
expanded: boolean,
|
|
788
1131
|
_executionStarted = false,
|
|
789
|
-
): boolean => renderMode === "clean" && !expanded;
|
|
1132
|
+
): boolean => renderMode === "clean" && !expanded && !isCleanGroupRevealed(toolCallId);
|
|
1133
|
+
|
|
1134
|
+
const useCompactToolView = (toolCallId: string, expanded: boolean): boolean =>
|
|
1135
|
+
!expanded && (
|
|
1136
|
+
renderMode === "compact" ||
|
|
1137
|
+
(renderMode === "clean" && isCleanGroupRevealed(toolCallId))
|
|
1138
|
+
);
|
|
790
1139
|
|
|
791
1140
|
pi.registerEntryRenderer<ToolSummaryData>("pretty-tui-tool-summary", (entry, { expanded }, theme) => ({
|
|
792
1141
|
render(width: number): string[] {
|
|
@@ -825,14 +1174,33 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
825
1174
|
invalidate() {},
|
|
826
1175
|
}));
|
|
827
1176
|
|
|
828
|
-
|
|
1177
|
+
const messageContentItems = (message: any): any[] =>
|
|
1178
|
+
Array.isArray(message?.content) ? message.content : [];
|
|
1179
|
+
|
|
1180
|
+
const messageHasVisibleText = (message: any): boolean =>
|
|
1181
|
+
typeof message?.content === "string"
|
|
1182
|
+
? message.content.trim().length > 0
|
|
1183
|
+
: messageContentItems(message).some(
|
|
1184
|
+
(item: any) => item.type === "text" && typeof item.text === "string" && item.text.trim().length > 0,
|
|
1185
|
+
);
|
|
1186
|
+
|
|
1187
|
+
const restoreCleanSession = (ctx: any) => {
|
|
829
1188
|
cleanToolsExpanded = ctx.ui.getToolsExpanded();
|
|
830
1189
|
settledSummaries.clear();
|
|
831
1190
|
legacySummaryLastToolCallIds.clear();
|
|
1191
|
+
knownToolCallIds.clear();
|
|
1192
|
+
cleanCompactToolCallIds.clear();
|
|
1193
|
+
cleanGroupToolCallIds.clear();
|
|
1194
|
+
cleanToolCallGroupOwners.clear();
|
|
1195
|
+
cleanToolComponents.clear();
|
|
1196
|
+
cleanToolThemes.clear();
|
|
1197
|
+
cleanToolNames.clear();
|
|
832
1198
|
clearToolActivityHolds();
|
|
833
1199
|
cleanRun.requestRender = undefined;
|
|
834
1200
|
cleanRun.count = 0;
|
|
835
1201
|
cleanRun.failed = 0;
|
|
1202
|
+
cleanRun.currentToolCallIds = [];
|
|
1203
|
+
cleanRun.activeToolCallIds.clear();
|
|
836
1204
|
cleanRun.groups = [];
|
|
837
1205
|
cleanRun.activeToolCallId = undefined;
|
|
838
1206
|
cleanRun.activeToolName = undefined;
|
|
@@ -854,13 +1222,17 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
854
1222
|
failed: group.failed,
|
|
855
1223
|
activity: "done",
|
|
856
1224
|
});
|
|
1225
|
+
if (group.toolCallIds?.length) {
|
|
1226
|
+
setCleanGroupMembers(group.lastToolCallId, group.toolCallIds.slice());
|
|
1227
|
+
}
|
|
857
1228
|
if (entryId) legacySummaryLastToolCallIds.set(entryId, group.lastToolCallId);
|
|
858
1229
|
explicitSummaryIds.add(group.lastToolCallId);
|
|
859
1230
|
};
|
|
860
1231
|
|
|
861
1232
|
const finishGroup = () => {
|
|
862
1233
|
if (lastToolCallId && count > 0) {
|
|
863
|
-
lastFinishedGroup = { count, failed, lastToolCallId };
|
|
1234
|
+
lastFinishedGroup = { count, failed, lastToolCallId, toolCallIds: [...toolCalls] };
|
|
1235
|
+
setCleanGroupMembers(lastToolCallId, [...toolCalls]);
|
|
864
1236
|
if (!explicitSummaryIds.has(lastToolCallId)) {
|
|
865
1237
|
settledSummaries.set(lastToolCallId, { count, failed, activity: "done" });
|
|
866
1238
|
}
|
|
@@ -916,11 +1288,8 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
916
1288
|
}
|
|
917
1289
|
|
|
918
1290
|
if (message.role === "assistant") {
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
);
|
|
922
|
-
if (hasVisibleText) finishGroup();
|
|
923
|
-
for (const item of message.content ?? []) {
|
|
1291
|
+
if (messageHasVisibleText(message)) finishGroup();
|
|
1292
|
+
for (const item of messageContentItems(message)) {
|
|
924
1293
|
if (item.type !== "toolCall" || !supportedTools.has(item.name)) continue;
|
|
925
1294
|
count++;
|
|
926
1295
|
lastToolCallId = item.id;
|
|
@@ -935,21 +1304,37 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
935
1304
|
}
|
|
936
1305
|
|
|
937
1306
|
finishGroup();
|
|
1307
|
+
};
|
|
1308
|
+
|
|
1309
|
+
pi.on("session_start", (_event, ctx) => restoreCleanSession(ctx));
|
|
1310
|
+
pi.on("session_tree", (_event, ctx) => restoreCleanSession(ctx));
|
|
1311
|
+
pi.on("session_compact", () => {
|
|
1312
|
+
// Tool components removed by compaction must no longer suppress the
|
|
1313
|
+
// durable summary fallback. Remaining components register again as Pi
|
|
1314
|
+
// renders the rebuilt transcript.
|
|
1315
|
+
knownToolCallIds.clear();
|
|
938
1316
|
});
|
|
939
1317
|
|
|
940
1318
|
const hasVisibleAssistantText = (message: any): boolean =>
|
|
941
|
-
message?.role === "assistant" && (message
|
|
942
|
-
(item: any) => item.type === "text" && typeof item.text === "string" && item.text.trim().length > 0,
|
|
943
|
-
);
|
|
1319
|
+
message?.role === "assistant" && messageHasVisibleText(message);
|
|
944
1320
|
|
|
945
|
-
const
|
|
946
|
-
|
|
1321
|
+
const pendingToolCalls = (message: any): any[] =>
|
|
1322
|
+
messageContentItems(message).filter(
|
|
947
1323
|
(item: any) => item.type === "toolCall" && supportedTools.has(item.name) && item.id,
|
|
948
1324
|
);
|
|
949
1325
|
|
|
950
1326
|
const trackPendingToolActivity = (message: any): boolean => {
|
|
951
|
-
const
|
|
952
|
-
if (
|
|
1327
|
+
const toolCalls = pendingToolCalls(message);
|
|
1328
|
+
if (toolCalls.length === 0) return false;
|
|
1329
|
+
for (const toolCall of toolCalls) {
|
|
1330
|
+
if (!cleanRun.currentToolCallIds.includes(toolCall.id)) {
|
|
1331
|
+
cleanRun.currentToolCallIds.push(toolCall.id);
|
|
1332
|
+
}
|
|
1333
|
+
cleanToolNames.set(toolCall.id, toolDisplayName(toolCall.name));
|
|
1334
|
+
}
|
|
1335
|
+
const toolCall = toolCalls[toolCalls.length - 1];
|
|
1336
|
+
if (cleanRun.lastCompletedToolCallId === toolCall.id) return false;
|
|
1337
|
+
setCleanGroupMembers(toolCall.id, cleanRun.currentToolCallIds.slice());
|
|
953
1338
|
cleanRun.active = true;
|
|
954
1339
|
cleanRun.activeToolCallId = toolCall.id;
|
|
955
1340
|
beginToolActivity(toolCall.id, toolDisplayName(toolCall.name));
|
|
@@ -996,17 +1381,26 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
996
1381
|
clearToolActivityHolds();
|
|
997
1382
|
cleanRun.count = 0;
|
|
998
1383
|
cleanRun.failed = 0;
|
|
1384
|
+
cleanRun.currentToolCallIds = [];
|
|
1385
|
+
cleanRun.activeToolCallIds.clear();
|
|
999
1386
|
cleanRun.groups = [];
|
|
1000
1387
|
cleanRun.lastCompletedToolCallId = undefined;
|
|
1001
1388
|
cleanRun.settled = false;
|
|
1002
1389
|
}
|
|
1003
1390
|
cleanRun.active = true;
|
|
1391
|
+
cleanRun.activeToolCallIds.clear();
|
|
1004
1392
|
cleanRun.activeToolCallId = undefined;
|
|
1005
1393
|
cleanRun.activeToolName = undefined;
|
|
1006
1394
|
cleanRun.activity = "thinking...";
|
|
1007
1395
|
});
|
|
1008
1396
|
pi.on("tool_execution_start", (event) => {
|
|
1009
1397
|
if (!supportedTools.has(event.toolName)) return;
|
|
1398
|
+
if (!cleanRun.currentToolCallIds.includes(event.toolCallId)) {
|
|
1399
|
+
cleanRun.currentToolCallIds.push(event.toolCallId);
|
|
1400
|
+
}
|
|
1401
|
+
cleanRun.activeToolCallIds.add(event.toolCallId);
|
|
1402
|
+
cleanToolNames.set(event.toolCallId, toolDisplayName(event.toolName));
|
|
1403
|
+
setCleanGroupMembers(event.toolCallId, cleanRun.currentToolCallIds.slice());
|
|
1010
1404
|
cleanRun.active = true;
|
|
1011
1405
|
cleanRun.activeToolCallId = event.toolCallId;
|
|
1012
1406
|
beginToolActivity(event.toolCallId, toolDisplayName(event.toolName), true);
|
|
@@ -1022,16 +1416,24 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1022
1416
|
cleanRun.count++;
|
|
1023
1417
|
if (event.isError) cleanRun.failed++;
|
|
1024
1418
|
cleanRun.lastCompletedToolCallId = event.toolCallId;
|
|
1419
|
+
cleanRun.activeToolCallIds.delete(event.toolCallId);
|
|
1025
1420
|
if (cleanRun.activeToolCallId === event.toolCallId) {
|
|
1026
|
-
cleanRun.activeToolCallId =
|
|
1027
|
-
|
|
1028
|
-
|
|
1421
|
+
cleanRun.activeToolCallId = [...cleanRun.currentToolCallIds]
|
|
1422
|
+
.reverse()
|
|
1423
|
+
.find((toolCallId) => cleanRun.activeToolCallIds.has(toolCallId));
|
|
1424
|
+
cleanRun.activeToolName = cleanRun.activeToolCallId
|
|
1425
|
+
? cleanToolNames.get(cleanRun.activeToolCallId)
|
|
1426
|
+
: undefined;
|
|
1427
|
+
cleanRun.activity = cleanRun.activeToolName ?? "thinking...";
|
|
1029
1428
|
}
|
|
1429
|
+
const visibleSummaryToolCallId = cleanRun.activeToolCallId ?? cleanRun.lastCompletedToolCallId;
|
|
1430
|
+
setCleanGroupMembers(visibleSummaryToolCallId, cleanRun.currentToolCallIds.slice());
|
|
1030
1431
|
});
|
|
1031
1432
|
pi.on("agent_end", () => {
|
|
1032
1433
|
// No summary is appended here: this event may be followed by an automatic
|
|
1033
1434
|
// retry or compaction. The live row remains available until settled.
|
|
1034
1435
|
clearPendingToolActivities();
|
|
1436
|
+
cleanRun.activeToolCallIds.clear();
|
|
1035
1437
|
cleanRun.activeToolCallId = undefined;
|
|
1036
1438
|
cleanRun.activeToolName = undefined;
|
|
1037
1439
|
cleanRun.activity = "thinking...";
|
|
@@ -1051,6 +1453,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1051
1453
|
});
|
|
1052
1454
|
}
|
|
1053
1455
|
cleanRun.active = false;
|
|
1456
|
+
cleanRun.activeToolCallIds.clear();
|
|
1054
1457
|
cleanRun.activeToolCallId = undefined;
|
|
1055
1458
|
cleanRun.activeToolName = undefined;
|
|
1056
1459
|
cleanRun.activity = "done";
|
|
@@ -1066,7 +1469,12 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1066
1469
|
count,
|
|
1067
1470
|
failed,
|
|
1068
1471
|
lastToolCallId,
|
|
1069
|
-
groups: groups.map(({ count, failed, lastToolCallId }) => ({
|
|
1472
|
+
groups: groups.map(({ count, failed, lastToolCallId, toolCallIds }) => ({
|
|
1473
|
+
count,
|
|
1474
|
+
failed,
|
|
1475
|
+
lastToolCallId,
|
|
1476
|
+
toolCallIds,
|
|
1477
|
+
})),
|
|
1070
1478
|
});
|
|
1071
1479
|
});
|
|
1072
1480
|
|
|
@@ -1107,7 +1515,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1107
1515
|
renderCall(args: any, theme: any, context: any) {
|
|
1108
1516
|
if (hideCleanTool(context.toolCallId, context.expanded, context.executionStarted)) return cleanToolCall(theme, "Bash", context.toolCallId);
|
|
1109
1517
|
const command = typeof args.command === "string" ? args.command : "";
|
|
1110
|
-
if (
|
|
1518
|
+
if (useCompactToolView(context.toolCallId, context.expanded)) {
|
|
1111
1519
|
const commandLines = command.split(/\r\n|\r|\n/);
|
|
1112
1520
|
const firstLine = commandLines[0] ?? "";
|
|
1113
1521
|
const omitted = commandLines.length - 1;
|
|
@@ -1121,7 +1529,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1121
1529
|
renderResult(toolResult: any, options: any, theme: any, context: any) {
|
|
1122
1530
|
const output = textContent(toolResult);
|
|
1123
1531
|
if (hideCleanTool(context.toolCallId, options.expanded, context.executionStarted)) return hiddenToolResult(context, options, output);
|
|
1124
|
-
if (
|
|
1532
|
+
if (useCompactToolView(context.toolCallId, options.expanded)) {
|
|
1125
1533
|
if (options.isPartial) return partialResult(context, theme, "Running…");
|
|
1126
1534
|
const failed = completeStatus(context, output);
|
|
1127
1535
|
return result(theme, failed ? "Command failed" : "Done", "", false, failed);
|
|
@@ -1199,7 +1607,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1199
1607
|
if (hideCleanTool(context.toolCallId, context.expanded, context.executionStarted)) return cleanToolCall(theme, "Write", context.toolCallId);
|
|
1200
1608
|
const content = typeof args.content === "string" ? args.content : "";
|
|
1201
1609
|
const path = String(args.path ?? "");
|
|
1202
|
-
if (
|
|
1610
|
+
if (useCompactToolView(context.toolCallId, context.expanded)) {
|
|
1203
1611
|
const lines = content.replace(/\t/g, " ").split("\n");
|
|
1204
1612
|
while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
1205
1613
|
const total = lines.length;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-pretty-tui",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Polished tool calls and list rendering for the Pi coding agent TUI",
|
|
3
|
+
"version": "0.1.6",
|
|
4
|
+
"description": "Polished user messages, tool calls, and list rendering for the Pi coding agent TUI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Kainan Yang <ykn0309@whu.edu.cn>",
|
|
7
7
|
"type": "module",
|
|
@@ -40,6 +40,6 @@
|
|
|
40
40
|
"extensions": [
|
|
41
41
|
"./extensions/index.ts"
|
|
42
42
|
],
|
|
43
|
-
"image": "https://unpkg.com/pi-pretty-tui@0.1.
|
|
43
|
+
"image": "https://unpkg.com/pi-pretty-tui@0.1.6/assets/screenshot.png"
|
|
44
44
|
}
|
|
45
45
|
}
|