pi-pretty-tui 0.1.8 → 0.2.0
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 +12 -8
- package/README.zh-CN.md +12 -8
- package/extensions/activity-timeline.ts +286 -0
- package/extensions/index.ts +1318 -210
- package/package.json +2 -2
package/extensions/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AssistantMessageComponent,
|
|
3
3
|
CustomEditor,
|
|
4
|
+
CustomMessageComponent,
|
|
4
5
|
InteractiveMode,
|
|
5
6
|
ToolExecutionComponent,
|
|
6
7
|
UserMessageComponent,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
createWriteTool,
|
|
14
15
|
copyToClipboard,
|
|
15
16
|
getAgentDir,
|
|
17
|
+
getMarkdownTheme,
|
|
16
18
|
type ExtensionAPI,
|
|
17
19
|
} from "@earendil-works/pi-coding-agent";
|
|
18
20
|
import {
|
|
@@ -27,10 +29,40 @@ import {
|
|
|
27
29
|
} from "@earendil-works/pi-tui";
|
|
28
30
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
29
31
|
import { join } from "node:path";
|
|
32
|
+
import {
|
|
33
|
+
ActivityTimeline,
|
|
34
|
+
assistantMessageKey,
|
|
35
|
+
assistantSystemBoundary,
|
|
36
|
+
thinkingText,
|
|
37
|
+
visibleAssistantText,
|
|
38
|
+
type ActivityGroup,
|
|
39
|
+
type ActivityMember,
|
|
40
|
+
} from "./activity-timeline.js";
|
|
30
41
|
|
|
31
42
|
type PrettyTuiMode = "full" | "compact" | "clean";
|
|
32
43
|
const CLEAN_TOOL_ACTIVITY_MIN_MS = 1000;
|
|
44
|
+
const SPECIALIZED_TOOL_NAMES = new Set(["read", "bash", "edit", "write", "grep", "find", "ls"]);
|
|
45
|
+
|
|
46
|
+
const stripAnsiBackgrounds = (value: string): string =>
|
|
47
|
+
value.replace(/\x1b\[([0-9:;]*)m/g, (sequence, raw: string) => {
|
|
48
|
+
const fields = raw === "" ? [0] : raw.split(";").map((field) => Number(field));
|
|
49
|
+
const kept: number[] = [];
|
|
50
|
+
for (let index = 0; index < fields.length; index++) {
|
|
51
|
+
const field = fields[index];
|
|
52
|
+
if ((field >= 40 && field <= 49) || (field >= 100 && field <= 107)) continue;
|
|
53
|
+
if (field === 48) {
|
|
54
|
+
const mode = fields[index + 1];
|
|
55
|
+
if (mode === 5) index += 2;
|
|
56
|
+
else if (mode === 2) index += 4;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
kept.push(field);
|
|
60
|
+
}
|
|
61
|
+
if (raw.includes(":") && /(?:^|;)48:/u.test(raw)) return "";
|
|
62
|
+
return kept.length > 0 ? `\x1b[${kept.join(";")}m` : "";
|
|
63
|
+
});
|
|
33
64
|
type PrettyTuiConfig = {
|
|
65
|
+
enabled?: boolean;
|
|
34
66
|
mode?: PrettyTuiMode;
|
|
35
67
|
/** Legacy location used by the first mode implementation. */
|
|
36
68
|
bash?: {
|
|
@@ -52,6 +84,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
52
84
|
const cwd = process.cwd();
|
|
53
85
|
const configPath = join(getAgentDir(), "pretty-tui.json");
|
|
54
86
|
let config = loadConfig(configPath);
|
|
87
|
+
const activeForSession = config.enabled !== false;
|
|
55
88
|
const configuredMode = config.mode ?? config.bash?.mode;
|
|
56
89
|
let renderMode: PrettyTuiMode = configuredMode === "full" || configuredMode === "compact" || configuredMode === "clean"
|
|
57
90
|
? configuredMode
|
|
@@ -62,6 +95,16 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
62
95
|
let currentTui: any;
|
|
63
96
|
let currentExtensionUi: any;
|
|
64
97
|
let changingAllToolsExpansion = false;
|
|
98
|
+
const activityTimeline = new ActivityTimeline();
|
|
99
|
+
const revealedActivityGroups = new Set<string>();
|
|
100
|
+
const expandedThinkingMembers = new Set<string>();
|
|
101
|
+
const thinkingComponents = new Map<string, any>();
|
|
102
|
+
const activityFallbackThemes = new Map<string, any>();
|
|
103
|
+
const activityUpdateComponents = new Map<string, Component>();
|
|
104
|
+
const customUpdateToolHints = new Map<string, string>();
|
|
105
|
+
const customUpdateBeforeToolHints = new Set<string>();
|
|
106
|
+
let activityUpdateSequence = 0;
|
|
107
|
+
const assistantBoundaryKeys = new Set<string>();
|
|
65
108
|
const cleanCompactToolCallIds = new Set<string>();
|
|
66
109
|
const cleanGroupToolCallIds = new Map<string, string[]>();
|
|
67
110
|
const cleanToolCallGroupOwners = new Map<string, string>();
|
|
@@ -77,31 +120,213 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
77
120
|
|
|
78
121
|
const isCleanGroupRevealed = (toolCallId: string): boolean => {
|
|
79
122
|
if (cleanCompactToolCallIds.has(toolCallId)) return true;
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
123
|
+
const activityGroup = activityTimeline.groupForTool(toolCallId);
|
|
124
|
+
if (activityGroup && activityGroupRevealed(activityGroup)) return true;
|
|
125
|
+
const legacyOwner = cleanToolCallGroupOwners.get(toolCallId);
|
|
126
|
+
const legacyIds = legacyOwner ? cleanGroupToolCallIds.get(legacyOwner) : undefined;
|
|
127
|
+
return legacyIds?.some((id) => cleanCompactToolCallIds.has(id)) ?? false;
|
|
83
128
|
};
|
|
84
129
|
|
|
85
130
|
const cleanThemeForToolCall = (toolCallId: string): any => {
|
|
86
131
|
const directTheme = cleanToolThemes.get(toolCallId);
|
|
87
132
|
if (directTheme) return directTheme;
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
133
|
+
const activityGroup = activityTimeline.groupForTool(toolCallId);
|
|
134
|
+
// Prefer a real tool renderer theme over the Markdown-derived fallback.
|
|
135
|
+
// A group that starts with Thought installs the fallback first; returning
|
|
136
|
+
// it here made later third-party success dots use the heading color.
|
|
137
|
+
if (activityGroup) {
|
|
138
|
+
for (const id of activityGroup.toolCallIds) {
|
|
139
|
+
const activityTheme = cleanToolThemes.get(id);
|
|
140
|
+
if (activityTheme) {
|
|
141
|
+
activityFallbackThemes.set(activityGroup.id, activityTheme);
|
|
142
|
+
return activityTheme;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const cachedActivityTheme = activityGroup
|
|
147
|
+
? activityFallbackThemes.get(activityGroup.id)
|
|
148
|
+
: undefined;
|
|
149
|
+
if (cachedActivityTheme) return cachedActivityTheme;
|
|
150
|
+
const legacyOwner = cleanToolCallGroupOwners.get(toolCallId);
|
|
151
|
+
const legacyIds = legacyOwner ? cleanGroupToolCallIds.get(legacyOwner) : undefined;
|
|
152
|
+
return legacyIds?.map((id) => cleanToolThemes.get(id)).find(Boolean);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const activityGroupOwner = (group: ActivityGroup): string | undefined => {
|
|
156
|
+
for (let index = group.toolCallIds.length - 1; index >= 0; index--) {
|
|
157
|
+
const toolCallId = group.toolCallIds[index];
|
|
158
|
+
if (settledSummaries.has(toolCallId)) return toolCallId;
|
|
159
|
+
}
|
|
160
|
+
if (cleanRun.activeToolCallId && group.toolCallIds.includes(cleanRun.activeToolCallId)) {
|
|
161
|
+
return cleanRun.activeToolCallId;
|
|
162
|
+
}
|
|
163
|
+
if (cleanRun.lastCompletedToolCallId && group.toolCallIds.includes(cleanRun.lastCompletedToolCallId)) {
|
|
164
|
+
return cleanRun.lastCompletedToolCallId;
|
|
165
|
+
}
|
|
166
|
+
return group.toolCallIds[group.toolCallIds.length - 1];
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const defaultActivityTheme = () => {
|
|
170
|
+
const markdownTheme = getMarkdownTheme();
|
|
171
|
+
return {
|
|
172
|
+
fg: (color: string, text: string) => {
|
|
173
|
+
if (color === "accent") return markdownTheme.listBullet(text);
|
|
174
|
+
if (color === "success" || color === "syntaxComment") return markdownTheme.codeBlock(text);
|
|
175
|
+
if (color === "thinkingLow") return markdownTheme.link(text);
|
|
176
|
+
if (color === "dim" || color === "muted" || color === "thinkingText") {
|
|
177
|
+
return markdownTheme.quote(text);
|
|
178
|
+
}
|
|
179
|
+
return text;
|
|
180
|
+
},
|
|
181
|
+
bold: markdownTheme.bold,
|
|
182
|
+
italic: markdownTheme.italic,
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const activityGroupTheme = (group: ActivityGroup): any => {
|
|
187
|
+
// A Thought can render before its tools are constructed and install a
|
|
188
|
+
// Markdown fallback. Always promote a real tool theme once available so
|
|
189
|
+
// light mode receives explicit, high-contrast text colors.
|
|
190
|
+
for (const toolCallId of group.toolCallIds) {
|
|
191
|
+
const toolTheme = cleanToolThemes.get(toolCallId);
|
|
192
|
+
if (toolTheme) {
|
|
193
|
+
activityFallbackThemes.set(group.id, toolTheme);
|
|
194
|
+
return toolTheme;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const cached = activityFallbackThemes.get(group.id);
|
|
198
|
+
if (cached) return cached;
|
|
199
|
+
const fallback = defaultActivityTheme();
|
|
200
|
+
activityFallbackThemes.set(group.id, fallback);
|
|
201
|
+
return fallback;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const activityGroupRevealed = (group: ActivityGroup): boolean =>
|
|
205
|
+
cleanToolsExpanded || revealedActivityGroups.has(group.id);
|
|
206
|
+
|
|
207
|
+
const activityMemberPosition = (group: ActivityGroup, member: ActivityMember) => ({
|
|
208
|
+
first: group.members[0]?.id === member.id,
|
|
209
|
+
last: group.members[group.members.length - 1]?.id === member.id,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const activityTreeStyle = (
|
|
213
|
+
group: ActivityGroup,
|
|
214
|
+
member: ActivityMember,
|
|
215
|
+
width: number,
|
|
216
|
+
memberTheme?: any,
|
|
217
|
+
) => {
|
|
218
|
+
const { last } = activityMemberPosition(group, member);
|
|
219
|
+
const rawPrefix = last ? " └─ " : " ├─ ";
|
|
220
|
+
const rawContinuation = last ? " " : " │ ";
|
|
221
|
+
const prefixWidth = visibleWidth(rawPrefix);
|
|
222
|
+
if (width <= prefixWidth) {
|
|
223
|
+
return { prefix: "", continuation: "", childWidth: Math.max(1, width), prefixWidth: 0 };
|
|
224
|
+
}
|
|
225
|
+
const resolvedTheme = memberTheme ?? activityGroupTheme(group);
|
|
226
|
+
return {
|
|
227
|
+
prefix: resolvedTheme ? resolvedTheme.fg("dim", rawPrefix) : rawPrefix,
|
|
228
|
+
continuation: resolvedTheme ? resolvedTheme.fg("dim", rawContinuation) : rawContinuation,
|
|
229
|
+
childWidth: Math.max(1, width - prefixWidth),
|
|
230
|
+
prefixWidth,
|
|
231
|
+
};
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const humanizeCustomType = (value: string): string =>
|
|
235
|
+
value
|
|
236
|
+
.split(/[-_\s]+/u)
|
|
237
|
+
.filter(Boolean)
|
|
238
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
239
|
+
.join(" ") || "Update";
|
|
240
|
+
|
|
241
|
+
const renderActivityUpdate = (
|
|
242
|
+
group: ActivityGroup,
|
|
243
|
+
member: ActivityMember,
|
|
244
|
+
width: number,
|
|
245
|
+
theme = activityGroupTheme(group),
|
|
246
|
+
): string[] => {
|
|
247
|
+
if (renderMode !== "clean" || !activityGroupRevealed(group)) return [];
|
|
248
|
+
const { prefix, continuation, childWidth } = activityTreeStyle(group, member, width, theme);
|
|
249
|
+
const title = truncateToWidth(member.updateTitle ?? "Update", Math.max(1, childWidth - 2), "…");
|
|
250
|
+
const lines = [theme.fg("accent", "◇ ") + theme.fg("toolTitle", theme.bold(title))];
|
|
251
|
+
if (member.updateContent) {
|
|
252
|
+
const detailWidth = Math.max(1, childWidth - visibleWidth(" │ "));
|
|
253
|
+
const detailLines = wrapTextWithAnsi(theme.fg("muted", member.updateContent), detailWidth);
|
|
254
|
+
lines.push(...detailLines.map((line, index) =>
|
|
255
|
+
theme.fg("dim", index === detailLines.length - 1 ? " └ " : " │ ") + line
|
|
256
|
+
));
|
|
257
|
+
}
|
|
258
|
+
const decorated = lines.map((line, index) =>
|
|
259
|
+
truncateToWidth((index === 0 ? prefix : continuation) + line, Math.max(1, width), "")
|
|
260
|
+
);
|
|
261
|
+
const position = activityMemberPosition(group, member);
|
|
262
|
+
return position.first
|
|
263
|
+
? ["", ...renderActivityGroupSummary(group, width), ...decorated]
|
|
264
|
+
: decorated;
|
|
93
265
|
};
|
|
94
266
|
|
|
95
|
-
const
|
|
96
|
-
|
|
267
|
+
const renderPendingActivityUpdate = (member: ActivityMember, width: number): string[] => {
|
|
268
|
+
const theme = defaultActivityTheme();
|
|
269
|
+
const title = truncateToWidth(member.updateTitle ?? "Update", Math.max(1, width - 2), "…");
|
|
270
|
+
const lines = [theme.fg("accent", "◇ ") + theme.fg("toolTitle", theme.bold(title))];
|
|
271
|
+
if (member.updateContent) {
|
|
272
|
+
const detailWidth = Math.max(1, width - visibleWidth(" │ "));
|
|
273
|
+
const details = wrapTextWithAnsi(theme.fg("muted", member.updateContent), detailWidth);
|
|
274
|
+
lines.push(...details.map((line, index) =>
|
|
275
|
+
theme.fg("dim", index === details.length - 1 ? " └ " : " │ ") + line
|
|
276
|
+
));
|
|
277
|
+
}
|
|
278
|
+
return ["", ...lines.map((line) => truncateToWidth(line, Math.max(1, width), ""))];
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const handleActivityUpdateMouse = (
|
|
282
|
+
group: ActivityGroup,
|
|
283
|
+
member: ActivityMember,
|
|
284
|
+
event: any,
|
|
285
|
+
) => {
|
|
286
|
+
const isLeftClick = event.type === "click" && event.button === "left";
|
|
287
|
+
if (!isLeftClick || group.toolCallIds.length === 0) return undefined;
|
|
288
|
+
const position = activityMemberPosition(group, member);
|
|
289
|
+
if (!activityGroupRevealed(group)) {
|
|
290
|
+
if (!position.first) return undefined;
|
|
291
|
+
revealedActivityGroups.add(group.id);
|
|
292
|
+
for (const toolCallId of group.toolCallIds) cleanCompactToolCallIds.add(toolCallId);
|
|
293
|
+
} else {
|
|
294
|
+
const summaryHeight = position.first ? renderActivityGroupSummary(group, event.width).length : 0;
|
|
295
|
+
if (!position.first || event.y <= 0 || event.y > summaryHeight) return undefined;
|
|
296
|
+
revealedActivityGroups.delete(group.id);
|
|
297
|
+
for (const toolCallId of group.toolCallIds) {
|
|
298
|
+
cleanCompactToolCallIds.delete(toolCallId);
|
|
299
|
+
const component = cleanToolComponents.get(toolCallId);
|
|
300
|
+
if (component?.expanded) component.setExpanded(false);
|
|
301
|
+
}
|
|
302
|
+
for (const child of group.members) expandedThinkingMembers.delete(child.id);
|
|
303
|
+
}
|
|
304
|
+
for (const child of group.members) {
|
|
305
|
+
if (child.kind === "tool" && child.toolCallId) {
|
|
306
|
+
cleanToolComponents.get(child.toolCallId)?.updateDisplay?.();
|
|
307
|
+
} else if (child.kind === "thinking" && child.messageKey) {
|
|
308
|
+
thinkingComponents.get(child.messageKey)?.invalidate?.();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
currentTui?.requestRender?.();
|
|
312
|
+
return { handled: true };
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const saveConfig = (nextConfig: Record<string, any>) => {
|
|
316
|
+
config = nextConfig;
|
|
97
317
|
mkdirSync(getAgentDir(), { recursive: true });
|
|
98
318
|
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
99
319
|
};
|
|
100
320
|
|
|
321
|
+
const saveRenderMode = (mode: PrettyTuiMode) => saveConfig({ ...config, mode });
|
|
322
|
+
const saveEnabled = (enabled: boolean) => saveConfig({ ...config, enabled });
|
|
323
|
+
|
|
101
324
|
pi.registerCommand("pretty-tui", {
|
|
102
|
-
description: "Configure pi-pretty-tui
|
|
325
|
+
description: "Configure pi-pretty-tui",
|
|
103
326
|
getArgumentCompletions: (prefix: string) => {
|
|
104
327
|
const items = [
|
|
328
|
+
{ value: "enable", label: "enable", description: "Enable the extension and reload automatically" },
|
|
329
|
+
{ value: "disable", label: "disable", description: "Disable the extension and reload automatically" },
|
|
105
330
|
{ value: "full", label: "full", description: "Full tool details and output" },
|
|
106
331
|
{ value: "compact", label: "compact", description: "Concise summaries for all built-in tools" },
|
|
107
332
|
{ value: "clean", label: "clean", description: "Group supported tools into Running/Done status" },
|
|
@@ -115,29 +340,67 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
115
340
|
|
|
116
341
|
if (!requested) {
|
|
117
342
|
if (!ctx.hasUI) {
|
|
118
|
-
ctx.ui.notify(
|
|
343
|
+
ctx.ui.notify(
|
|
344
|
+
`pi-pretty-tui: ${config.enabled !== false ? "enabled" : "disabled"} · mode: ${renderMode}`,
|
|
345
|
+
"info",
|
|
346
|
+
);
|
|
119
347
|
return;
|
|
120
348
|
}
|
|
349
|
+
const enabled = `Enabled — ${config.enabled !== false ? "on" : "off"}`;
|
|
121
350
|
const full = `Full — full tool details and output${renderMode === "full" ? " (current)" : ""}`;
|
|
122
351
|
const compact = `Compact — concise summaries for all built-in tools${renderMode === "compact" ? " (current)" : ""}`;
|
|
123
352
|
const clean = `Clean — group supported tools into Running/Done status${renderMode === "clean" ? " (current)" : ""}`;
|
|
124
|
-
const selected = await ctx.ui.select("pi-pretty-tui
|
|
353
|
+
const selected = await ctx.ui.select("pi-pretty-tui settings", [enabled, full, compact, clean]);
|
|
125
354
|
if (!selected) return;
|
|
126
|
-
requested = selected ===
|
|
355
|
+
requested = selected === enabled
|
|
356
|
+
? config.enabled !== false ? "disable" : "enable"
|
|
357
|
+
: selected === full
|
|
358
|
+
? "full"
|
|
359
|
+
: selected === compact
|
|
360
|
+
? "compact"
|
|
361
|
+
: "clean";
|
|
127
362
|
}
|
|
128
363
|
|
|
129
364
|
if (requested === "status") {
|
|
130
|
-
ctx.ui.notify(
|
|
365
|
+
ctx.ui.notify(
|
|
366
|
+
`pi-pretty-tui: ${config.enabled !== false ? "enabled" : "disabled"} · mode: ${renderMode}`,
|
|
367
|
+
"info",
|
|
368
|
+
);
|
|
131
369
|
return;
|
|
132
370
|
}
|
|
371
|
+
if (requested === "enable" || requested === "disable") {
|
|
372
|
+
const enabled = requested === "enable";
|
|
373
|
+
try {
|
|
374
|
+
saveEnabled(enabled);
|
|
375
|
+
} catch (error) {
|
|
376
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
377
|
+
ctx.ui.notify(`Could not save pi-pretty-tui setting: ${message}`, "error");
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
ctx.ui.notify(
|
|
381
|
+
`pi-pretty-tui ${enabled ? "enabled" : "disabled"}; reloading…`,
|
|
382
|
+
"info",
|
|
383
|
+
);
|
|
384
|
+
try {
|
|
385
|
+
await ctx.reload();
|
|
386
|
+
return;
|
|
387
|
+
} catch (error) {
|
|
388
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
389
|
+
ctx.ui.notify(`Setting saved, but automatic reload failed: ${message}`, "warning");
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
133
393
|
if (requested !== "full" && requested !== "compact" && requested !== "clean") {
|
|
134
|
-
ctx.ui.notify("Usage: /pretty-tui [full|compact|clean|status]", "error");
|
|
394
|
+
ctx.ui.notify("Usage: /pretty-tui [enable|disable|full|compact|clean|status]", "error");
|
|
135
395
|
return;
|
|
136
396
|
}
|
|
137
397
|
|
|
138
398
|
cleanCompactToolCallIds.clear();
|
|
399
|
+
revealedActivityGroups.clear();
|
|
400
|
+
expandedThinkingMembers.clear();
|
|
139
401
|
renderMode = requested;
|
|
140
402
|
for (const component of cleanToolComponents.values()) component.updateDisplay?.();
|
|
403
|
+
for (const component of thinkingComponents.values()) component.invalidate?.();
|
|
141
404
|
try {
|
|
142
405
|
saveRenderMode(renderMode);
|
|
143
406
|
ctx.ui.notify(`pi-pretty-tui mode set to: ${renderMode}`, "info");
|
|
@@ -148,6 +411,24 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
148
411
|
},
|
|
149
412
|
});
|
|
150
413
|
|
|
414
|
+
// Keep the settings command available while disabled, but do not install
|
|
415
|
+
// render patches or override built-in tools until the next enabled reload.
|
|
416
|
+
if (!activeForSession) return;
|
|
417
|
+
|
|
418
|
+
// Markdown enhancements are active only while pi-pretty-tui renders the main
|
|
419
|
+
// conversation transcript. Plugin overlays also use Pi's Markdown class, so
|
|
420
|
+
// a global always-on prototype patch would leak transcript styling into them.
|
|
421
|
+
let transcriptMarkdownDepth = 0;
|
|
422
|
+
const withTranscriptMarkdown = <T>(callback: () => T): T => {
|
|
423
|
+
transcriptMarkdownDepth++;
|
|
424
|
+
try {
|
|
425
|
+
return callback();
|
|
426
|
+
} finally {
|
|
427
|
+
transcriptMarkdownDepth--;
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
const transcriptMarkdownActive = () => transcriptMarkdownDepth > 0;
|
|
431
|
+
|
|
151
432
|
// Give Pi's main prompt editor a complete rounded frame. Render the native
|
|
152
433
|
// editor at a two-column narrower width so cursor layout, wrapping, IME, and
|
|
153
434
|
// autocomplete remain native, then add one themed border column per side.
|
|
@@ -258,12 +539,35 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
258
539
|
const top = border(topStart + markdownTheme.bold(title) + topTail + "╮");
|
|
259
540
|
const bottom = border("╰" + "─".repeat(Math.max(0, frameWidth - 2)) + "╯");
|
|
260
541
|
const contentWidth = Math.max(1, frameWidth - 4);
|
|
261
|
-
const body = content.render(contentWidth).map((line: string) => {
|
|
542
|
+
const body = withTranscriptMarkdown(() => content.render(contentWidth)).map((line: string) => {
|
|
262
543
|
const padding = " ".repeat(Math.max(0, contentWidth - visibleWidth(line)));
|
|
263
544
|
return border("│ ") + line + padding + border(" │");
|
|
264
545
|
});
|
|
265
546
|
return [top, ...body, bottom].map((line: string) => outer + paintBackground(line));
|
|
266
547
|
},
|
|
548
|
+
handleMouse(event: any) {
|
|
549
|
+
if (!content.handleMouse) return undefined;
|
|
550
|
+
const sidePad = Math.min(outputPad, Math.max(0, Math.floor((event.width - 1) / 2)));
|
|
551
|
+
const frameWidth = Math.max(1, event.width - sidePad * 2);
|
|
552
|
+
if (frameWidth < 4) {
|
|
553
|
+
return withTranscriptMarkdown(() => content.handleMouse({
|
|
554
|
+
...event,
|
|
555
|
+
x: event.x - sidePad,
|
|
556
|
+
width: frameWidth,
|
|
557
|
+
}));
|
|
558
|
+
}
|
|
559
|
+
const contentWidth = Math.max(1, frameWidth - 4);
|
|
560
|
+
if (event.y <= 0 || event.x < sidePad + 2 || event.x >= sidePad + 2 + contentWidth) {
|
|
561
|
+
return undefined;
|
|
562
|
+
}
|
|
563
|
+
return withTranscriptMarkdown(() => content.handleMouse({
|
|
564
|
+
...event,
|
|
565
|
+
x: event.x - sidePad - 2,
|
|
566
|
+
y: event.y - 1,
|
|
567
|
+
width: contentWidth,
|
|
568
|
+
height: Math.max(0, event.height - 2),
|
|
569
|
+
}));
|
|
570
|
+
},
|
|
267
571
|
invalidate() {
|
|
268
572
|
content.invalidate?.();
|
|
269
573
|
},
|
|
@@ -291,63 +595,376 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
291
595
|
});
|
|
292
596
|
}
|
|
293
597
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
598
|
+
const customMessageKey = (message: any): string => {
|
|
599
|
+
if (message?.timestamp !== undefined) {
|
|
600
|
+
const parsed = typeof message.timestamp === "string"
|
|
601
|
+
? Date.parse(message.timestamp)
|
|
602
|
+
: Number(message.timestamp);
|
|
603
|
+
return Number.isFinite(parsed) ? String(parsed) : String(message.timestamp);
|
|
604
|
+
}
|
|
605
|
+
return `${message?.customType ?? "custom"}:${JSON.stringify(message?.content ?? "")}`;
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
const customMessageText = (message: any): string =>
|
|
609
|
+
typeof message?.content === "string"
|
|
610
|
+
? message.content
|
|
611
|
+
: Array.isArray(message?.content)
|
|
612
|
+
? message.content
|
|
613
|
+
.filter((item: any) => item?.type === "text" && typeof item.text === "string")
|
|
614
|
+
.map((item: any) => item.text)
|
|
615
|
+
.join("\n")
|
|
616
|
+
: "";
|
|
617
|
+
|
|
618
|
+
const addPersistentActivityUpdate = (message: any): ActivityMember => {
|
|
619
|
+
const key = customMessageKey(message);
|
|
620
|
+
const title = humanizeCustomType(message?.customType ?? "update");
|
|
621
|
+
const content = customMessageText(message);
|
|
622
|
+
return activityTimeline.addUpdate(key, title, content, true) ??
|
|
623
|
+
activityTimeline.addPendingUpdate(key, title, content, true);
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
const customMessagePrototype = CustomMessageComponent.prototype as any;
|
|
627
|
+
const customMessagePatchKey = Symbol.for("pretty-tui.clean-custom-message");
|
|
628
|
+
if (!customMessagePrototype[customMessagePatchKey]) {
|
|
629
|
+
const originalCustomRender = customMessagePrototype.render;
|
|
630
|
+
const originalCustomHandleMouse = customMessagePrototype.handleMouse;
|
|
631
|
+
const releaseSemanticCustomMessage = (component: any): boolean => {
|
|
632
|
+
if (!component.customRenderer) return false;
|
|
633
|
+
const key = customMessageKey(component.message);
|
|
634
|
+
const member = activityTimeline.memberForUpdate(key);
|
|
635
|
+
if (member) activityUpdateComponents.delete(member.id);
|
|
636
|
+
activityTimeline.removeUpdate(key);
|
|
637
|
+
return true;
|
|
638
|
+
};
|
|
639
|
+
const patchedCustomRender = function (this: any, width: number): string[] {
|
|
640
|
+
if (releaseSemanticCustomMessage(this)) {
|
|
641
|
+
return originalCustomRender.call(this, width);
|
|
642
|
+
}
|
|
643
|
+
const message = this.message;
|
|
644
|
+
const key = customMessageKey(message);
|
|
645
|
+
let member = activityTimeline.memberForUpdate(key);
|
|
646
|
+
if (!member) {
|
|
647
|
+
const hintedToolCallId = customUpdateToolHints.get(key);
|
|
648
|
+
const hintedGroup = hintedToolCallId
|
|
649
|
+
? activityTimeline.groupForTool(hintedToolCallId)
|
|
650
|
+
: undefined;
|
|
651
|
+
if (hintedGroup) {
|
|
652
|
+
const attach = customUpdateBeforeToolHints.has(key)
|
|
653
|
+
? activityTimeline.addUpdateToGroupStart.bind(activityTimeline)
|
|
654
|
+
: activityTimeline.addUpdateToGroup.bind(activityTimeline);
|
|
655
|
+
member = attach(
|
|
656
|
+
hintedGroup.id,
|
|
657
|
+
key,
|
|
658
|
+
humanizeCustomType(message?.customType ?? "update"),
|
|
659
|
+
customMessageText(message),
|
|
660
|
+
true,
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
const group = member ? activityTimeline.groupForMember(member.id) : undefined;
|
|
665
|
+
if (renderMode !== "clean" || !member || !group) {
|
|
666
|
+
return originalCustomRender.call(this, width);
|
|
667
|
+
}
|
|
668
|
+
activityUpdateComponents.set(member.id, this);
|
|
669
|
+
if (group.toolCallIds.length === 0) return renderPendingActivityUpdate(member, width);
|
|
670
|
+
const position = activityMemberPosition(group, member);
|
|
671
|
+
if (!activityGroupRevealed(group)) {
|
|
672
|
+
return position.first
|
|
673
|
+
? ["", ...renderActivityGroupSummary(group, width, true)]
|
|
674
|
+
: [];
|
|
675
|
+
}
|
|
676
|
+
return renderActivityUpdate(group, member, width);
|
|
677
|
+
};
|
|
678
|
+
const patchedCustomHandleMouse = function (this: any, event: any) {
|
|
679
|
+
if (releaseSemanticCustomMessage(this)) {
|
|
680
|
+
return originalCustomHandleMouse?.call(this, event);
|
|
681
|
+
}
|
|
682
|
+
const member = activityTimeline.memberForUpdate(customMessageKey(this.message));
|
|
683
|
+
const group = member ? activityTimeline.groupForMember(member.id) : undefined;
|
|
684
|
+
if (renderMode !== "clean" || !member || !group) {
|
|
685
|
+
return originalCustomHandleMouse?.call(this, event);
|
|
686
|
+
}
|
|
687
|
+
if (group.toolCallIds.length === 0) return undefined;
|
|
688
|
+
return handleActivityUpdateMouse(group, member, event);
|
|
689
|
+
};
|
|
690
|
+
customMessagePrototype[customMessagePatchKey] = {
|
|
691
|
+
originalRender: originalCustomRender,
|
|
692
|
+
patchedRender: patchedCustomRender,
|
|
693
|
+
originalHandleMouse: originalCustomHandleMouse,
|
|
694
|
+
patchedHandleMouse: patchedCustomHandleMouse,
|
|
695
|
+
};
|
|
696
|
+
customMessagePrototype.render = patchedCustomRender;
|
|
697
|
+
customMessagePrototype.handleMouse = patchedCustomHandleMouse;
|
|
698
|
+
pi.on("session_shutdown", () => {
|
|
699
|
+
const patch = customMessagePrototype[customMessagePatchKey];
|
|
700
|
+
if (!patch) return;
|
|
701
|
+
if (patch.patchedRender === customMessagePrototype.render) {
|
|
702
|
+
customMessagePrototype.render = patch.originalRender;
|
|
703
|
+
}
|
|
704
|
+
if (patch.patchedHandleMouse === customMessagePrototype.handleMouse) {
|
|
705
|
+
customMessagePrototype.handleMouse = patch.originalHandleMouse;
|
|
706
|
+
}
|
|
707
|
+
if (
|
|
708
|
+
customMessagePrototype.render === patch.originalRender &&
|
|
709
|
+
customMessagePrototype.handleMouse === patch.originalHandleMouse
|
|
710
|
+
) {
|
|
711
|
+
delete customMessagePrototype[customMessagePatchKey];
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Thinking and tools are peers in the transcript-first activity timeline.
|
|
717
|
+
// Assistant components keep Pi's native Markdown renderer, while clean mode
|
|
718
|
+
// projects each thinking run as a compact, independently expandable child.
|
|
300
719
|
const assistantPrototype = AssistantMessageComponent.prototype as any;
|
|
301
720
|
const thinkingPatchKey = Symbol.for("pretty-tui.clean-thinking");
|
|
302
721
|
if (!assistantPrototype[thinkingPatchKey]) {
|
|
303
722
|
const originalUpdateContent = assistantPrototype.updateContent;
|
|
304
723
|
const originalRender = assistantPrototype.render;
|
|
724
|
+
const originalHandleMouse = assistantPrototype.handleMouse;
|
|
305
725
|
const originalMessageKey = Symbol("pretty-tui.original-assistant-message");
|
|
306
726
|
const renderedModeKey = Symbol("pretty-tui.rendered-assistant-mode");
|
|
307
|
-
const renderedExpansionKey = Symbol("pretty-tui.rendered-
|
|
727
|
+
const renderedExpansionKey = Symbol("pretty-tui.rendered-assistant-expansion");
|
|
728
|
+
const cleanThinkingRenderCacheKey = Symbol("pretty-tui.clean-thinking-render-cache");
|
|
729
|
+
const cleanThinkingDetailKey = Symbol("pretty-tui.clean-thinking-detail");
|
|
730
|
+
|
|
731
|
+
const eligibleThinking = (message: any) =>
|
|
732
|
+
Array.isArray(message?.content) &&
|
|
733
|
+
message.content.some(
|
|
734
|
+
(item: any) => item?.type === "thinking" &&
|
|
735
|
+
typeof item.thinking === "string" &&
|
|
736
|
+
item.thinking.length > 0,
|
|
737
|
+
) &&
|
|
738
|
+
!assistantSystemBoundary(message);
|
|
739
|
+
|
|
740
|
+
const refreshGroup = (group: ActivityGroup) => {
|
|
741
|
+
for (const member of group.members) {
|
|
742
|
+
if (member.kind === "tool" && member.toolCallId) {
|
|
743
|
+
cleanToolComponents.get(member.toolCallId)?.updateDisplay?.();
|
|
744
|
+
} else if (member.kind === "thinking" && member.messageKey) {
|
|
745
|
+
thinkingComponents.get(member.messageKey)?.invalidate?.();
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
currentTui?.requestRender?.();
|
|
749
|
+
};
|
|
308
750
|
|
|
309
751
|
const patchedUpdateContent = function (this: any, message: any, isStreaming = this.isStreaming) {
|
|
752
|
+
this[originalMessageKey] = message;
|
|
753
|
+
this[renderedModeKey] = renderMode;
|
|
754
|
+
this[renderedExpansionKey] = cleanToolsExpanded;
|
|
755
|
+
this.lastMessage = message;
|
|
310
756
|
if (renderMode !== "clean") {
|
|
311
757
|
originalUpdateContent.call(this, message, isStreaming);
|
|
312
|
-
this
|
|
313
|
-
this[renderedModeKey] = renderMode;
|
|
314
|
-
this[renderedExpansionKey] = undefined;
|
|
758
|
+
this.lastMessage = message;
|
|
315
759
|
return;
|
|
316
760
|
}
|
|
761
|
+
const groupedThinking = eligibleThinking(message);
|
|
762
|
+
if (groupedThinking) {
|
|
763
|
+
const key = assistantMessageKey(message);
|
|
764
|
+
activityTimeline.addThinking(key, thinkingText(message));
|
|
765
|
+
thinkingComponents.set(key, this);
|
|
766
|
+
}
|
|
317
767
|
|
|
318
|
-
const showThinking = cleanToolsExpanded;
|
|
319
|
-
const content = Array.isArray(message?.content) ? message.content : [];
|
|
320
|
-
// In collapsed clean mode, omit Pi's built-in Thinking... placeholder
|
|
321
|
-
// entirely. The clean-mode Running(...) row owns the activity label;
|
|
322
|
-
// expanded mode still reveals the actual thinking content.
|
|
323
|
-
const displayMessage = showThinking
|
|
324
|
-
? message
|
|
325
|
-
: { ...message, content: content.filter((item: any) => item.type !== "thinking") };
|
|
326
768
|
const previousHideThinkingBlock = this.hideThinkingBlock;
|
|
327
769
|
this.hideThinkingBlock = false;
|
|
770
|
+
// Clean mode owns grouped thinking completely. Removing it from Pi's
|
|
771
|
+
// native content tree also removes the native MouseRegion that would
|
|
772
|
+
// otherwise toggle a second, unrelated `Thinking...` block.
|
|
773
|
+
const displayMessage =
|
|
774
|
+
groupedThinking && Array.isArray(message?.content)
|
|
775
|
+
? { ...message, content: message.content.filter((item: any) => item.type !== "thinking") }
|
|
776
|
+
: message;
|
|
328
777
|
try {
|
|
329
778
|
originalUpdateContent.call(this, displayMessage, isStreaming);
|
|
330
779
|
} finally {
|
|
331
780
|
this.hideThinkingBlock = previousHideThinkingBlock;
|
|
332
781
|
}
|
|
333
|
-
|
|
334
|
-
// Pi's invalidate()/setHideThinkingBlock() call updateContent with
|
|
335
|
-
// lastMessage, so retain the unfiltered message for later mode changes.
|
|
336
|
-
this[originalMessageKey] = message;
|
|
337
|
-
this[renderedModeKey] = renderMode;
|
|
338
|
-
this[renderedExpansionKey] = showThinking;
|
|
339
782
|
this.lastMessage = message;
|
|
340
783
|
};
|
|
341
784
|
|
|
342
|
-
const
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
785
|
+
const renderAssistantTranscript = function (this: any, width: number): string[] {
|
|
786
|
+
const message = this[originalMessageKey] ?? this.lastMessage;
|
|
787
|
+
if (
|
|
788
|
+
message &&
|
|
789
|
+
(this[renderedModeKey] !== renderMode || this[renderedExpansionKey] !== cleanToolsExpanded)
|
|
790
|
+
) {
|
|
791
|
+
patchedUpdateContent.call(this, message, this.isStreaming);
|
|
792
|
+
}
|
|
793
|
+
if (renderMode !== "clean" || !eligibleThinking(message)) {
|
|
794
|
+
return originalRender.call(this, width);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const key = assistantMessageKey(message);
|
|
798
|
+
const member = activityTimeline.memberForThinking(key);
|
|
799
|
+
const group = member ? activityTimeline.groupForMember(member.id) : undefined;
|
|
800
|
+
if (!member || !group) {
|
|
801
|
+
// Timeline ownership can be briefly unavailable during transcript
|
|
802
|
+
// rebuilds. Fail open to Pi's filtered native renderer so visible
|
|
803
|
+
// assistant text is never lost.
|
|
804
|
+
return originalRender.call(this, width);
|
|
805
|
+
}
|
|
806
|
+
if (group.toolCallIds.length === 0) {
|
|
807
|
+
// Activity groups are tool-oriented. A model may finish with Thinking
|
|
808
|
+
// and visible text without calling a tool; only the Thinking portion
|
|
809
|
+
// is filtered from Pi's native component, so always preserve the
|
|
810
|
+
// visible response here instead of returning an empty projection.
|
|
811
|
+
return visibleAssistantText(message) ? originalRender.call(this, width) : [];
|
|
812
|
+
}
|
|
813
|
+
thinkingComponents.set(key, this);
|
|
814
|
+
if (!activityFallbackThemes.has(group.id)) {
|
|
815
|
+
activityFallbackThemes.set(group.id, defaultActivityTheme());
|
|
816
|
+
}
|
|
817
|
+
const position = activityMemberPosition(group, member);
|
|
818
|
+
const visibleLines = visibleAssistantText(message) ? originalRender.call(this, width) : [];
|
|
819
|
+
const revealed = activityGroupRevealed(group);
|
|
820
|
+
if (!revealed) {
|
|
821
|
+
const activityLines = position.first
|
|
822
|
+
? ["", ...renderActivityGroupSummary(group, width, true)]
|
|
823
|
+
: [];
|
|
824
|
+
return [...activityLines, ...visibleLines];
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
const groupTheme = activityGroupTheme(group);
|
|
828
|
+
const { prefix, continuation, childWidth } = activityTreeStyle(
|
|
829
|
+
group,
|
|
830
|
+
member,
|
|
831
|
+
width,
|
|
832
|
+
groupTheme,
|
|
833
|
+
);
|
|
834
|
+
const expanded = cleanToolsExpanded || expandedThinkingMembers.has(member.id);
|
|
835
|
+
const settledOwner = position.first ? activityGroupOwner(group) : undefined;
|
|
836
|
+
const stableSummary = !position.first || Boolean(
|
|
837
|
+
settledOwner && settledSummaries.has(settledOwner) && !toolActivityHolds.has(settledOwner),
|
|
838
|
+
);
|
|
839
|
+
const cacheable = !expanded && !this.isStreaming && stableSummary;
|
|
840
|
+
const cached = cacheable ? this[cleanThinkingRenderCacheKey] : undefined;
|
|
841
|
+
if (
|
|
842
|
+
cached?.width === width &&
|
|
843
|
+
cached?.message === message &&
|
|
844
|
+
cached?.theme === groupTheme &&
|
|
845
|
+
cached?.memberCount === group.members.length &&
|
|
846
|
+
cached?.last === position.last
|
|
847
|
+
) {
|
|
848
|
+
// Only the stable activity projection is cached. Visible assistant
|
|
849
|
+
// text belongs outside the collapsible group and must be appended on
|
|
850
|
+
// every render, including cache hits.
|
|
851
|
+
return [...cached.lines, ...visibleLines];
|
|
852
|
+
}
|
|
853
|
+
const stateLabel = this.isStreaming ? "thinking" : "thought";
|
|
854
|
+
const label = truncateToWidth(stateLabel, Math.max(1, childWidth - 2), "…");
|
|
855
|
+
const header = groupTheme.fg("thinkingLow", "● ") +
|
|
856
|
+
groupTheme.fg("toolTitle", groupTheme.bold(label));
|
|
857
|
+
let contentLines: string[] = [header];
|
|
858
|
+
if (expanded) {
|
|
859
|
+
const detailWidth = Math.max(1, childWidth - visibleWidth(" │ "));
|
|
860
|
+
const detailText = member.thinking ?? thinkingText(message);
|
|
861
|
+
let detail = this[cleanThinkingDetailKey];
|
|
862
|
+
if (!detail || detail.text !== detailText) {
|
|
863
|
+
detail = {
|
|
864
|
+
text: detailText,
|
|
865
|
+
component: new Markdown(
|
|
866
|
+
detailText,
|
|
867
|
+
0,
|
|
868
|
+
0,
|
|
869
|
+
this.markdownTheme,
|
|
870
|
+
{
|
|
871
|
+
color: (text: string) => groupTheme.fg("thinkingText", text),
|
|
872
|
+
italic: true,
|
|
873
|
+
},
|
|
874
|
+
),
|
|
875
|
+
};
|
|
876
|
+
this[cleanThinkingDetailKey] = detail;
|
|
877
|
+
}
|
|
878
|
+
const detailLines = detail.component.render(detailWidth);
|
|
879
|
+
contentLines.push(...detailLines.map((line: string, index: number) =>
|
|
880
|
+
groupTheme.fg("dim", index === detailLines.length - 1 ? " └ " : " │ ") + line
|
|
881
|
+
));
|
|
882
|
+
}
|
|
883
|
+
const decorated = contentLines.map((line, index) =>
|
|
884
|
+
truncateToWidth((index === 0 ? prefix : continuation) + line, Math.max(1, width), "")
|
|
346
885
|
);
|
|
347
|
-
|
|
348
|
-
|
|
886
|
+
const output = position.first
|
|
887
|
+
? ["", ...renderActivityGroupSummary(group, width), ...decorated]
|
|
888
|
+
: decorated;
|
|
889
|
+
if (cacheable) {
|
|
890
|
+
this[cleanThinkingRenderCacheKey] = {
|
|
891
|
+
width,
|
|
892
|
+
message,
|
|
893
|
+
theme: groupTheme,
|
|
894
|
+
memberCount: group.members.length,
|
|
895
|
+
last: position.last,
|
|
896
|
+
lines: output,
|
|
897
|
+
};
|
|
349
898
|
}
|
|
350
|
-
return
|
|
899
|
+
return [...output, ...visibleLines];
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
const patchedRender = function (this: any, width: number): string[] {
|
|
903
|
+
return withTranscriptMarkdown(() => renderAssistantTranscript.call(this, width));
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
const handleAssistantTranscriptMouse = function (this: any, event: any) {
|
|
907
|
+
const message = this[originalMessageKey] ?? this.lastMessage;
|
|
908
|
+
if (renderMode !== "clean" || !eligibleThinking(message)) {
|
|
909
|
+
return originalHandleMouse.call(this, event);
|
|
910
|
+
}
|
|
911
|
+
const key = assistantMessageKey(message);
|
|
912
|
+
const member = activityTimeline.memberForThinking(key);
|
|
913
|
+
const group = member ? activityTimeline.groupForMember(member.id) : undefined;
|
|
914
|
+
if (!member || !group) return originalHandleMouse.call(this, event);
|
|
915
|
+
if (group.toolCallIds.length === 0) return originalHandleMouse.call(this, event);
|
|
916
|
+
const position = activityMemberPosition(group, member);
|
|
917
|
+
const isLeftClick = event.type === "click" && event.button === "left";
|
|
918
|
+
|
|
919
|
+
// A completed assistant message can contain both the final Thought and
|
|
920
|
+
// visible Markdown. The Markdown is rendered after the activity rows;
|
|
921
|
+
// route clicks in that suffix back to Pi's native component so links,
|
|
922
|
+
// text selection, and code-block Copy controls keep working.
|
|
923
|
+
if (visibleAssistantText(message)) {
|
|
924
|
+
const visibleLines = originalRender.call(this, event.width);
|
|
925
|
+
const renderedLines = patchedRender.call(this, event.width);
|
|
926
|
+
const activityHeight = Math.max(0, renderedLines.length - visibleLines.length);
|
|
927
|
+
if (event.y >= activityHeight) {
|
|
928
|
+
return originalHandleMouse.call(this, {
|
|
929
|
+
...event,
|
|
930
|
+
y: event.y - activityHeight,
|
|
931
|
+
height: visibleLines.length,
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
if (!activityGroupRevealed(group)) {
|
|
937
|
+
if (!isLeftClick) return undefined;
|
|
938
|
+
if (position.first) {
|
|
939
|
+
revealedActivityGroups.add(group.id);
|
|
940
|
+
for (const toolCallId of group.toolCallIds) cleanCompactToolCallIds.add(toolCallId);
|
|
941
|
+
refreshGroup(group);
|
|
942
|
+
}
|
|
943
|
+
return { handled: true };
|
|
944
|
+
}
|
|
945
|
+
const summaryHeight = position.first ? renderActivityGroupSummary(group, event.width).length : 0;
|
|
946
|
+
if (position.first && event.y > 0 && event.y <= summaryHeight && isLeftClick) {
|
|
947
|
+
revealedActivityGroups.delete(group.id);
|
|
948
|
+
for (const toolCallId of group.toolCallIds) {
|
|
949
|
+
cleanCompactToolCallIds.delete(toolCallId);
|
|
950
|
+
const component = cleanToolComponents.get(toolCallId);
|
|
951
|
+
if (component?.expanded) component.setExpanded(false);
|
|
952
|
+
}
|
|
953
|
+
for (const child of group.members) expandedThinkingMembers.delete(child.id);
|
|
954
|
+
refreshGroup(group);
|
|
955
|
+
return { handled: true };
|
|
956
|
+
}
|
|
957
|
+
if (isLeftClick) {
|
|
958
|
+
if (expandedThinkingMembers.has(member.id)) expandedThinkingMembers.delete(member.id);
|
|
959
|
+
else expandedThinkingMembers.add(member.id);
|
|
960
|
+
refreshGroup(group);
|
|
961
|
+
return { handled: true };
|
|
962
|
+
}
|
|
963
|
+
return undefined;
|
|
964
|
+
};
|
|
965
|
+
|
|
966
|
+
const patchedHandleMouse = function (this: any, event: any) {
|
|
967
|
+
return withTranscriptMarkdown(() => handleAssistantTranscriptMouse.call(this, event));
|
|
351
968
|
};
|
|
352
969
|
|
|
353
970
|
assistantPrototype[thinkingPatchKey] = {
|
|
@@ -355,9 +972,12 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
355
972
|
patchedUpdateContent,
|
|
356
973
|
originalRender,
|
|
357
974
|
patchedRender,
|
|
975
|
+
originalHandleMouse,
|
|
976
|
+
patchedHandleMouse,
|
|
358
977
|
};
|
|
359
978
|
assistantPrototype.updateContent = patchedUpdateContent;
|
|
360
979
|
assistantPrototype.render = patchedRender;
|
|
980
|
+
assistantPrototype.handleMouse = patchedHandleMouse;
|
|
361
981
|
|
|
362
982
|
pi.on("session_shutdown", () => {
|
|
363
983
|
const patch = assistantPrototype[thinkingPatchKey];
|
|
@@ -368,9 +988,13 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
368
988
|
if (patch.patchedRender === assistantPrototype.render) {
|
|
369
989
|
assistantPrototype.render = patch.originalRender;
|
|
370
990
|
}
|
|
991
|
+
if (patch.patchedHandleMouse === assistantPrototype.handleMouse) {
|
|
992
|
+
assistantPrototype.handleMouse = patch.originalHandleMouse;
|
|
993
|
+
}
|
|
371
994
|
if (
|
|
372
995
|
assistantPrototype.updateContent === patch.originalUpdateContent &&
|
|
373
|
-
assistantPrototype.render === patch.originalRender
|
|
996
|
+
assistantPrototype.render === patch.originalRender &&
|
|
997
|
+
assistantPrototype.handleMouse === patch.originalHandleMouse
|
|
374
998
|
) {
|
|
375
999
|
delete assistantPrototype[thinkingPatchKey];
|
|
376
1000
|
}
|
|
@@ -403,20 +1027,76 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
403
1027
|
];
|
|
404
1028
|
};
|
|
405
1029
|
|
|
1030
|
+
const indexDisplayedCustomUpdateHints = (entries: any[]) => {
|
|
1031
|
+
customUpdateToolHints.clear();
|
|
1032
|
+
customUpdateBeforeToolHints.clear();
|
|
1033
|
+
let lastToolCallId: string | undefined;
|
|
1034
|
+
let pendingCustomKeys: string[] = [];
|
|
1035
|
+
for (const entry of entries) {
|
|
1036
|
+
if (entry?.type === "compaction") {
|
|
1037
|
+
lastToolCallId = undefined;
|
|
1038
|
+
pendingCustomKeys = [];
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
if (entry?.type === "custom_message") {
|
|
1042
|
+
if (entry.display !== false) {
|
|
1043
|
+
const key = customMessageKey(entry);
|
|
1044
|
+
if (lastToolCallId) customUpdateToolHints.set(key, lastToolCallId);
|
|
1045
|
+
else pendingCustomKeys.push(key);
|
|
1046
|
+
}
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
if (entry?.type !== "message") continue;
|
|
1050
|
+
const message = entry.message;
|
|
1051
|
+
if (message?.role === "user") {
|
|
1052
|
+
lastToolCallId = undefined;
|
|
1053
|
+
pendingCustomKeys = [];
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
1056
|
+
if (message?.role === "assistant") {
|
|
1057
|
+
const toolCalls = (Array.isArray(message.content) ? message.content : [])
|
|
1058
|
+
.filter((item: any) => item?.type === "toolCall" && item.id);
|
|
1059
|
+
if (toolCalls.length > 0 && pendingCustomKeys.length > 0) {
|
|
1060
|
+
for (const key of pendingCustomKeys) {
|
|
1061
|
+
customUpdateToolHints.set(key, toolCalls[0].id);
|
|
1062
|
+
customUpdateBeforeToolHints.add(key);
|
|
1063
|
+
}
|
|
1064
|
+
pendingCustomKeys = [];
|
|
1065
|
+
}
|
|
1066
|
+
if (visibleAssistantText(message) || assistantSystemBoundary(message)) {
|
|
1067
|
+
lastToolCallId = undefined;
|
|
1068
|
+
pendingCustomKeys = [];
|
|
1069
|
+
}
|
|
1070
|
+
for (const item of toolCalls) lastToolCallId = item.id;
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
if (message?.role === "toolResult" && message.toolCallId) {
|
|
1074
|
+
lastToolCallId = message.toolCallId;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
|
|
406
1079
|
const interactiveModePrototype = InteractiveMode.prototype as any;
|
|
407
1080
|
const toolsExpansionPatchKey = Symbol.for("pretty-tui.clean-tool-expansion");
|
|
408
1081
|
if (!interactiveModePrototype[toolsExpansionPatchKey]) {
|
|
409
1082
|
const originalSetToolsExpanded = interactiveModePrototype.setToolsExpanded;
|
|
410
1083
|
const originalRenderSessionEntries = interactiveModePrototype.renderSessionEntries;
|
|
411
1084
|
const originalSwitchTuiMode = interactiveModePrototype.switchTuiMode;
|
|
1085
|
+
const originalShowExtensionNotify = interactiveModePrototype.showExtensionNotify;
|
|
412
1086
|
const patchedSetToolsExpanded = function (this: any, expanded: boolean) {
|
|
413
1087
|
cleanToolsExpanded = expanded;
|
|
414
1088
|
changingAllToolsExpansion = true;
|
|
415
|
-
if (!expanded)
|
|
1089
|
+
if (!expanded) {
|
|
1090
|
+
cleanCompactToolCallIds.clear();
|
|
1091
|
+
revealedActivityGroups.clear();
|
|
1092
|
+
expandedThinkingMembers.clear();
|
|
1093
|
+
}
|
|
416
1094
|
try {
|
|
417
1095
|
return originalSetToolsExpanded.call(this, expanded);
|
|
418
1096
|
} finally {
|
|
419
1097
|
changingAllToolsExpansion = false;
|
|
1098
|
+
for (const component of thinkingComponents.values()) component.invalidate?.();
|
|
1099
|
+
this.ui?.requestRender?.();
|
|
420
1100
|
}
|
|
421
1101
|
};
|
|
422
1102
|
|
|
@@ -426,11 +1106,9 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
426
1106
|
// buildContextEntries() prepends the latest compaction for model context,
|
|
427
1107
|
// while Pi's live compaction UI appends it chronologically. Keep reloads
|
|
428
1108
|
// and transcript rebuilds consistent with that live presentation.
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
options,
|
|
433
|
-
);
|
|
1109
|
+
const orderedEntries = orderContextEntriesForTranscript(entries);
|
|
1110
|
+
indexDisplayedCustomUpdateHints(orderedEntries);
|
|
1111
|
+
return originalRenderSessionEntries.call(this, orderedEntries, options);
|
|
434
1112
|
};
|
|
435
1113
|
|
|
436
1114
|
const patchedSwitchTuiMode = function (this: any, ...args: any[]) {
|
|
@@ -440,6 +1118,43 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
440
1118
|
return result;
|
|
441
1119
|
};
|
|
442
1120
|
|
|
1121
|
+
const patchedShowExtensionNotify = function (
|
|
1122
|
+
this: any,
|
|
1123
|
+
message: string,
|
|
1124
|
+
type?: "info" | "warning" | "error",
|
|
1125
|
+
) {
|
|
1126
|
+
if (renderMode !== "clean" || (type !== undefined && type !== "info")) {
|
|
1127
|
+
return originalShowExtensionNotify.call(this, message, type);
|
|
1128
|
+
}
|
|
1129
|
+
const [firstLine, ...remainingLines] = String(message).split("\n");
|
|
1130
|
+
const updateKey = `info:${++activityUpdateSequence}`;
|
|
1131
|
+
const title = firstLine || "Info";
|
|
1132
|
+
const content = remainingLines.join("\n");
|
|
1133
|
+
const member = activityTimeline.addUpdate(updateKey, title, content, false) ??
|
|
1134
|
+
activityTimeline.addPendingUpdate(updateKey, title, content, false);
|
|
1135
|
+
const group = activityTimeline.groupForMember(member.id);
|
|
1136
|
+
if (!group || !this.chatContainer?.addChild) {
|
|
1137
|
+
return originalShowExtensionNotify.call(this, message, type);
|
|
1138
|
+
}
|
|
1139
|
+
const component: Component = {
|
|
1140
|
+
render: (width: number) => {
|
|
1141
|
+
if (group.toolCallIds.length === 0) return renderPendingActivityUpdate(member, width);
|
|
1142
|
+
const position = activityMemberPosition(group, member);
|
|
1143
|
+
if (!activityGroupRevealed(group)) {
|
|
1144
|
+
return position.first
|
|
1145
|
+
? ["", ...renderActivityGroupSummary(group, width, true)]
|
|
1146
|
+
: [];
|
|
1147
|
+
}
|
|
1148
|
+
return renderActivityUpdate(group, member, width);
|
|
1149
|
+
},
|
|
1150
|
+
invalidate() {},
|
|
1151
|
+
handleMouse: (event: any) => handleActivityUpdateMouse(group, member, event),
|
|
1152
|
+
} as Component;
|
|
1153
|
+
activityUpdateComponents.set(member.id, component);
|
|
1154
|
+
this.chatContainer.addChild(component);
|
|
1155
|
+
this.ui?.requestRender?.();
|
|
1156
|
+
};
|
|
1157
|
+
|
|
443
1158
|
interactiveModePrototype[toolsExpansionPatchKey] = {
|
|
444
1159
|
originalSetToolsExpanded,
|
|
445
1160
|
patchedSetToolsExpanded,
|
|
@@ -447,10 +1162,13 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
447
1162
|
patchedRenderSessionEntries,
|
|
448
1163
|
originalSwitchTuiMode,
|
|
449
1164
|
patchedSwitchTuiMode,
|
|
1165
|
+
originalShowExtensionNotify,
|
|
1166
|
+
patchedShowExtensionNotify,
|
|
450
1167
|
};
|
|
451
1168
|
interactiveModePrototype.setToolsExpanded = patchedSetToolsExpanded;
|
|
452
1169
|
interactiveModePrototype.renderSessionEntries = patchedRenderSessionEntries;
|
|
453
1170
|
interactiveModePrototype.switchTuiMode = patchedSwitchTuiMode;
|
|
1171
|
+
interactiveModePrototype.showExtensionNotify = patchedShowExtensionNotify;
|
|
454
1172
|
|
|
455
1173
|
pi.on("session_shutdown", () => {
|
|
456
1174
|
fullscreenTui = false;
|
|
@@ -467,10 +1185,14 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
467
1185
|
if (patch.patchedSwitchTuiMode === interactiveModePrototype.switchTuiMode) {
|
|
468
1186
|
interactiveModePrototype.switchTuiMode = patch.originalSwitchTuiMode;
|
|
469
1187
|
}
|
|
1188
|
+
if (patch.patchedShowExtensionNotify === interactiveModePrototype.showExtensionNotify) {
|
|
1189
|
+
interactiveModePrototype.showExtensionNotify = patch.originalShowExtensionNotify;
|
|
1190
|
+
}
|
|
470
1191
|
if (
|
|
471
1192
|
interactiveModePrototype.setToolsExpanded === patch.originalSetToolsExpanded &&
|
|
472
1193
|
interactiveModePrototype.renderSessionEntries === patch.originalRenderSessionEntries &&
|
|
473
|
-
interactiveModePrototype.switchTuiMode === patch.originalSwitchTuiMode
|
|
1194
|
+
interactiveModePrototype.switchTuiMode === patch.originalSwitchTuiMode &&
|
|
1195
|
+
interactiveModePrototype.showExtensionNotify === patch.originalShowExtensionNotify
|
|
474
1196
|
) {
|
|
475
1197
|
delete interactiveModePrototype[toolsExpansionPatchKey];
|
|
476
1198
|
}
|
|
@@ -546,6 +1268,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
546
1268
|
const codeBlockPatchKey = Symbol.for("pretty-tui.code-blocks");
|
|
547
1269
|
const codeBlockCollectionKey = Symbol("pretty-tui.code-block-collection");
|
|
548
1270
|
const codeBlockRegionsKey = Symbol("pretty-tui.code-block-regions");
|
|
1271
|
+
const transcriptMarkdownStateKey = Symbol("pretty-tui.transcript-markdown-state");
|
|
549
1272
|
if (!markdownPrototype[codeBlockPatchKey]) {
|
|
550
1273
|
const originalInvalidate = markdownPrototype.invalidate;
|
|
551
1274
|
const originalRender = markdownPrototype.render;
|
|
@@ -561,6 +1284,15 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
561
1284
|
};
|
|
562
1285
|
|
|
563
1286
|
const patchedRender = function (this: any, width: number): string[] {
|
|
1287
|
+
const scoped = transcriptMarkdownActive();
|
|
1288
|
+
if (this[transcriptMarkdownStateKey] !== scoped) {
|
|
1289
|
+
delete this[codeBlockCollectionKey];
|
|
1290
|
+
delete this[codeBlockRegionsKey];
|
|
1291
|
+
originalInvalidate.call(this);
|
|
1292
|
+
this[transcriptMarkdownStateKey] = scoped;
|
|
1293
|
+
}
|
|
1294
|
+
if (!scoped) return originalRender.call(this, width);
|
|
1295
|
+
|
|
564
1296
|
const needsRender =
|
|
565
1297
|
!this.cachedLines || this.cachedText !== this.text || this.cachedWidth !== width;
|
|
566
1298
|
if (needsRender) this[codeBlockCollectionKey] = [];
|
|
@@ -604,6 +1336,10 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
604
1336
|
nextTokenType?: string,
|
|
605
1337
|
styleContext?: any,
|
|
606
1338
|
): string[] {
|
|
1339
|
+
if (!transcriptMarkdownActive()) {
|
|
1340
|
+
return originalRenderToken.call(this, token, width, nextTokenType, styleContext);
|
|
1341
|
+
}
|
|
1342
|
+
|
|
607
1343
|
if (token?.type === "heading") {
|
|
608
1344
|
const level = Math.max(1, Math.min(6, Number(token.depth) || 1));
|
|
609
1345
|
const maxWidth = Math.max(1, width);
|
|
@@ -766,6 +1502,15 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
766
1502
|
};
|
|
767
1503
|
|
|
768
1504
|
const patchedHandleMouse = function (this: any, event: TuiMouseEvent) {
|
|
1505
|
+
// Fullscreen layout hit-testing may dispatch directly to this Markdown
|
|
1506
|
+
// leaf instead of traversing AssistantMessageComponent.handleMouse. The
|
|
1507
|
+
// most recent render therefore records whether this instance belongs to
|
|
1508
|
+
// the main transcript; dynamic render scope alone is not sufficient.
|
|
1509
|
+
const transcriptOwned = transcriptMarkdownActive() || this[transcriptMarkdownStateKey] === true;
|
|
1510
|
+
if (!transcriptOwned) {
|
|
1511
|
+
delete this[codeBlockRegionsKey];
|
|
1512
|
+
return originalHandleMouse?.call(this, event);
|
|
1513
|
+
}
|
|
769
1514
|
if (event.button === "left" && (event.type === "press" || event.type === "click")) {
|
|
770
1515
|
const region = (this[codeBlockRegionsKey] ?? []).find(
|
|
771
1516
|
(candidate: any) =>
|
|
@@ -913,10 +1658,24 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
913
1658
|
context.state.compactToolStatus = status;
|
|
914
1659
|
};
|
|
915
1660
|
|
|
1661
|
+
const foregroundLuminance = (styled: string): number | undefined => {
|
|
1662
|
+
const trueColor = /\x1b\[38;2;(\d+);(\d+);(\d+)m/.exec(styled);
|
|
1663
|
+
if (!trueColor) return undefined;
|
|
1664
|
+
const [, red, green, blue] = trueColor.map(Number);
|
|
1665
|
+
return red * 0.2126 + green * 0.7152 + blue * 0.0722;
|
|
1666
|
+
};
|
|
1667
|
+
|
|
1668
|
+
const successMarkerColor = (theme: any): string => {
|
|
1669
|
+
const textLuminance = foregroundLuminance(theme.fg("text", "M"));
|
|
1670
|
+
return textLuminance !== undefined && textLuminance < 128
|
|
1671
|
+
? "success"
|
|
1672
|
+
: "syntaxComment";
|
|
1673
|
+
};
|
|
1674
|
+
|
|
916
1675
|
const callRow = (theme: any, name: string, detail: string, state: any): DisplayRow => ({
|
|
917
1676
|
prefix: () => {
|
|
918
1677
|
const status = (state.compactToolStatus ?? "running") as ToolStatus;
|
|
919
|
-
const color = status === "success" ?
|
|
1678
|
+
const color = status === "success" ? successMarkerColor(theme) : status === "error" ? "error" : "dim";
|
|
920
1679
|
return theme.fg(color, "● ");
|
|
921
1680
|
},
|
|
922
1681
|
continuation: " ",
|
|
@@ -930,7 +1689,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
930
1689
|
const call = (theme: any, name: string, detail: string, state: any) =>
|
|
931
1690
|
block([callRow(theme, name, detail, state)]);
|
|
932
1691
|
|
|
933
|
-
const writeCall = (theme: any, path: string, content: string, expanded: boolean, state: any) => {
|
|
1692
|
+
const writeCall = (theme: any, name: string, path: string, content: string, expanded: boolean, state: any) => {
|
|
934
1693
|
const lines = content.replace(/\t/g, " ").split("\n");
|
|
935
1694
|
while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
936
1695
|
|
|
@@ -938,14 +1697,14 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
938
1697
|
const shown = lines.slice(0, expanded ? total : 10);
|
|
939
1698
|
const remaining = total - shown.length;
|
|
940
1699
|
const rows: DisplayRow[] = [
|
|
941
|
-
callRow(theme,
|
|
1700
|
+
callRow(theme, name, `${path} · ${total} ${total === 1 ? "line" : "lines"}`, state),
|
|
942
1701
|
];
|
|
943
1702
|
|
|
944
1703
|
for (let index = 0; index < shown.length; index++) {
|
|
945
1704
|
const isLast = index === shown.length - 1 && remaining === 0;
|
|
946
1705
|
rows.push({
|
|
947
1706
|
prefix: theme.fg("dim", isLast ? " └ " : " │ "),
|
|
948
|
-
continuation: theme.fg("dim", " │ "),
|
|
1707
|
+
continuation: isLast ? " " : theme.fg("dim", " │ "),
|
|
949
1708
|
content: theme.fg("toolOutput", shown[index] || " "),
|
|
950
1709
|
});
|
|
951
1710
|
}
|
|
@@ -978,19 +1737,22 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
978
1737
|
|
|
979
1738
|
if (expanded && output) {
|
|
980
1739
|
const lines = output.split("\n");
|
|
1740
|
+
while (lines.length > 1 && lines[lines.length - 1] === "") lines.pop();
|
|
981
1741
|
const shown = lines.slice(0, 40);
|
|
982
|
-
|
|
1742
|
+
const omitted = lines.length - shown.length;
|
|
1743
|
+
for (let index = 0; index < shown.length; index++) {
|
|
1744
|
+
const isLast = index === shown.length - 1 && omitted === 0;
|
|
983
1745
|
rows.push({
|
|
984
|
-
prefix: theme.fg("dim", " │ "),
|
|
985
|
-
continuation: theme.fg("dim", " │ "),
|
|
986
|
-
content: outputStyle(
|
|
1746
|
+
prefix: theme.fg("dim", isLast ? " └ " : " │ "),
|
|
1747
|
+
continuation: isLast ? " " : theme.fg("dim", " │ "),
|
|
1748
|
+
content: outputStyle(shown[index] || " "),
|
|
987
1749
|
});
|
|
988
1750
|
}
|
|
989
|
-
if (
|
|
1751
|
+
if (omitted > 0) {
|
|
990
1752
|
rows.push({
|
|
991
1753
|
prefix: theme.fg("muted", " └ "),
|
|
992
1754
|
continuation: " ",
|
|
993
|
-
content: theme.fg("muted", `… ${
|
|
1755
|
+
content: theme.fg("muted", `… ${omitted} more lines`),
|
|
994
1756
|
});
|
|
995
1757
|
}
|
|
996
1758
|
}
|
|
@@ -1064,7 +1826,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1064
1826
|
const isLast = index === shown.length - 1;
|
|
1065
1827
|
rows.push({
|
|
1066
1828
|
prefix: theme.fg("dim", isLast ? " └ " : " │ "),
|
|
1067
|
-
continuation: theme.fg("dim", " │ "),
|
|
1829
|
+
continuation: isLast ? " " : theme.fg("dim", " │ "),
|
|
1068
1830
|
content: theme.fg("toolOutput", shown[index] || " "),
|
|
1069
1831
|
});
|
|
1070
1832
|
}
|
|
@@ -1081,6 +1843,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1081
1843
|
type ToolSummaryGroup = {
|
|
1082
1844
|
count: number;
|
|
1083
1845
|
failed: number;
|
|
1846
|
+
thoughtCount?: number;
|
|
1084
1847
|
lastToolCallId: string;
|
|
1085
1848
|
toolCallIds?: string[];
|
|
1086
1849
|
activity?: string;
|
|
@@ -1088,12 +1851,12 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1088
1851
|
type ToolSummaryData = {
|
|
1089
1852
|
count?: number;
|
|
1090
1853
|
failed?: number;
|
|
1854
|
+
thoughtCount?: number;
|
|
1091
1855
|
/** Identifies the last tool component for older single-group entries. */
|
|
1092
1856
|
lastToolCallId?: string;
|
|
1093
1857
|
/** All groups from a run, used to restore summaries after reload. */
|
|
1094
1858
|
groups?: ToolSummaryGroup[];
|
|
1095
1859
|
};
|
|
1096
|
-
const supportedTools = new Set(["read", "bash", "edit", "write", "grep", "find", "ls"]);
|
|
1097
1860
|
type ToolActivityHold = {
|
|
1098
1861
|
toolCallId: string;
|
|
1099
1862
|
name: string;
|
|
@@ -1102,7 +1865,12 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1102
1865
|
started: boolean;
|
|
1103
1866
|
timer?: ReturnType<typeof setTimeout>;
|
|
1104
1867
|
};
|
|
1105
|
-
const settledSummaries = new Map<string, {
|
|
1868
|
+
const settledSummaries = new Map<string, {
|
|
1869
|
+
count: number;
|
|
1870
|
+
failed: number;
|
|
1871
|
+
thoughtCount: number;
|
|
1872
|
+
activity: DisplayValue;
|
|
1873
|
+
}>();
|
|
1106
1874
|
const toolActivityHolds = new Map<string, ToolActivityHold>();
|
|
1107
1875
|
const legacySummaryLastToolCallIds = new Map<string, string>();
|
|
1108
1876
|
const knownToolCallIds = new Set<string>();
|
|
@@ -1130,8 +1898,64 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1130
1898
|
settled: false,
|
|
1131
1899
|
};
|
|
1132
1900
|
|
|
1133
|
-
const
|
|
1134
|
-
|
|
1901
|
+
const toolLabel = (toolName: string, label?: string): string => label || toolName;
|
|
1902
|
+
|
|
1903
|
+
const conciseThirdPartyArgs = (args: any): string => {
|
|
1904
|
+
if (!args || typeof args !== "object" || Array.isArray(args)) return "";
|
|
1905
|
+
const sensitive = /(?:token|secret|password|authorization|credential|api[_-]?key)/iu;
|
|
1906
|
+
const preferred = ["path", "query", "url", "offset", "id"];
|
|
1907
|
+
const entries = Object.entries(args)
|
|
1908
|
+
.filter(([key, value]) => !sensitive.test(key) && ["string", "number", "boolean"].includes(typeof value))
|
|
1909
|
+
.sort(([left], [right]) => {
|
|
1910
|
+
const leftRank = preferred.indexOf(left);
|
|
1911
|
+
const rightRank = preferred.indexOf(right);
|
|
1912
|
+
return (leftRank < 0 ? preferred.length : leftRank) -
|
|
1913
|
+
(rightRank < 0 ? preferred.length : rightRank);
|
|
1914
|
+
})
|
|
1915
|
+
.slice(0, 2)
|
|
1916
|
+
.map(([key, value]) => {
|
|
1917
|
+
const safeValue = String(value)
|
|
1918
|
+
.replace(/(https?:\/\/)[^\s/@:]+:[^\s/@]+@/giu, "$1…:…@")
|
|
1919
|
+
.replace(/([?&](?:token|secret|password|api[_-]?key)=)[^&\s]+/giu, "$1…");
|
|
1920
|
+
const rendered = truncateToWidth(safeValue, 36, "…");
|
|
1921
|
+
return `${key}=${rendered}`;
|
|
1922
|
+
});
|
|
1923
|
+
return entries.join(" · ");
|
|
1924
|
+
};
|
|
1925
|
+
|
|
1926
|
+
const thirdPartyResultSummary = (component: any): string => {
|
|
1927
|
+
const detailsError = component.result?.details?.error;
|
|
1928
|
+
const firstText = component.result?.content?.find(
|
|
1929
|
+
(item: any) => item?.type === "text" && typeof item.text === "string" && item.text.length > 0,
|
|
1930
|
+
)?.text;
|
|
1931
|
+
const text = String(typeof detailsError === "string" ? detailsError : firstText ?? "");
|
|
1932
|
+
const boundedText = text.slice(0, 512);
|
|
1933
|
+
const newline = boundedText.indexOf("\n");
|
|
1934
|
+
const boundedFirstLine = boundedText.slice(0, newline < 0 ? boundedText.length : newline);
|
|
1935
|
+
const firstLine = stripTerminalSequences(boundedFirstLine).trim();
|
|
1936
|
+
if (firstLine) return truncateToWidth(firstLine, 72, "…");
|
|
1937
|
+
if (component.result) return component.result.isError ? "Failed" : "Done";
|
|
1938
|
+
return component.executionStarted ? "Running…" : "Pending";
|
|
1939
|
+
};
|
|
1940
|
+
|
|
1941
|
+
const renderThirdPartyCompact = (component: any, width: number, theme: any): string[] => {
|
|
1942
|
+
const label = toolLabel(component.toolName, component.toolDefinition?.label);
|
|
1943
|
+
const args = conciseThirdPartyArgs(component.args);
|
|
1944
|
+
const dotColor = component.result?.isError
|
|
1945
|
+
? "error"
|
|
1946
|
+
: component.result && !component.isPartial
|
|
1947
|
+
? successMarkerColor(theme)
|
|
1948
|
+
: "accent";
|
|
1949
|
+
const title = theme.fg(dotColor, "● ") +
|
|
1950
|
+
theme.fg("accent", theme.bold(label)) +
|
|
1951
|
+
(args ? theme.fg("muted", `(${args})`) : "");
|
|
1952
|
+
const result = thirdPartyResultSummary(component);
|
|
1953
|
+
const resultColor = component.result?.isError ? "error" : "muted";
|
|
1954
|
+
return [
|
|
1955
|
+
truncateToWidth(title, Math.max(1, width), "…"),
|
|
1956
|
+
theme.fg(resultColor, truncateToWidth(` └ ${result}`, Math.max(1, width), "…")),
|
|
1957
|
+
];
|
|
1958
|
+
};
|
|
1135
1959
|
|
|
1136
1960
|
const clearToolActivityHolds = () => {
|
|
1137
1961
|
for (const hold of toolActivityHolds.values()) {
|
|
@@ -1178,7 +2002,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1178
2002
|
toolCallId,
|
|
1179
2003
|
name,
|
|
1180
2004
|
until: startedAt + CLEAN_TOOL_ACTIVITY_MIN_MS,
|
|
1181
|
-
after: "thinking
|
|
2005
|
+
after: "thinking",
|
|
1182
2006
|
started,
|
|
1183
2007
|
});
|
|
1184
2008
|
cleanRun.activeToolName = name;
|
|
@@ -1193,14 +2017,14 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1193
2017
|
toolCallId,
|
|
1194
2018
|
name,
|
|
1195
2019
|
until: now + CLEAN_TOOL_ACTIVITY_MIN_MS,
|
|
1196
|
-
after: "thinking
|
|
2020
|
+
after: "thinking",
|
|
1197
2021
|
started: true,
|
|
1198
2022
|
};
|
|
1199
2023
|
toolActivityHolds.set(toolCallId, hold);
|
|
1200
2024
|
}
|
|
1201
2025
|
hold.name = name;
|
|
1202
2026
|
hold.started = true;
|
|
1203
|
-
hold.after = "thinking
|
|
2027
|
+
hold.after = "thinking";
|
|
1204
2028
|
scheduleToolActivityRelease(hold);
|
|
1205
2029
|
};
|
|
1206
2030
|
|
|
@@ -1214,15 +2038,18 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1214
2038
|
const liveCleanToolCount = (): number =>
|
|
1215
2039
|
Math.max(cleanRun.count, cleanRun.currentToolCallIds.length);
|
|
1216
2040
|
|
|
2041
|
+
const thoughtCountForTool = (toolCallId: string | undefined): number =>
|
|
2042
|
+
toolCallId ? activityTimeline.groupForTool(toolCallId)?.thoughtCount ?? 0 : 0;
|
|
2043
|
+
|
|
1217
2044
|
const currentCleanActivity = (): string => {
|
|
1218
2045
|
if (cleanRun.activeToolCallId) {
|
|
1219
|
-
return cleanRun.activeToolName ?? cleanRun.activity ?? "thinking
|
|
2046
|
+
return cleanRun.activeToolName ?? cleanRun.activity ?? "thinking";
|
|
1220
2047
|
}
|
|
1221
2048
|
if (cleanRun.lastCompletedToolCallId) {
|
|
1222
2049
|
const hold = toolActivityHolds.get(cleanRun.lastCompletedToolCallId);
|
|
1223
2050
|
if (hold) return heldActivity(hold);
|
|
1224
2051
|
}
|
|
1225
|
-
return cleanRun.activeToolName ?? cleanRun.activity ?? (cleanRun.active ? "thinking
|
|
2052
|
+
return cleanRun.activeToolName ?? cleanRun.activity ?? (cleanRun.active ? "thinking" : "done");
|
|
1226
2053
|
};
|
|
1227
2054
|
|
|
1228
2055
|
pi.on("session_shutdown", clearToolActivityHolds);
|
|
@@ -1238,116 +2065,198 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1238
2065
|
const originalToolRender = toolExecutionPrototype.render;
|
|
1239
2066
|
const originalToolHandleMouse = toolExecutionPrototype.handleMouse;
|
|
1240
2067
|
const renderedModeKey = Symbol("pretty-tui.tool-rendered-mode");
|
|
1241
|
-
const
|
|
1242
|
-
|
|
1243
|
-
for (const
|
|
2068
|
+
const cleanChildRenderCacheKey = Symbol("pretty-tui.clean-child-render-cache");
|
|
2069
|
+
const refreshActivityGroup = (group: ActivityGroup) => {
|
|
2070
|
+
for (const member of group.members) {
|
|
2071
|
+
if (member.kind === "tool" && member.toolCallId) {
|
|
2072
|
+
cleanToolComponents.get(member.toolCallId)?.updateDisplay();
|
|
2073
|
+
} else if (member.kind === "thinking" && member.messageKey) {
|
|
2074
|
+
thinkingComponents.get(member.messageKey)?.invalidate?.();
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
};
|
|
2078
|
+
const revealCleanGroup = (group: ActivityGroup, ui?: any) => {
|
|
2079
|
+
revealedActivityGroups.add(group.id);
|
|
2080
|
+
for (const toolCallId of group.toolCallIds) cleanCompactToolCallIds.add(toolCallId);
|
|
2081
|
+
refreshActivityGroup(group);
|
|
2082
|
+
ui?.requestRender?.();
|
|
2083
|
+
};
|
|
2084
|
+
const collapseCleanGroup = (group: ActivityGroup, ui?: any) => {
|
|
2085
|
+
revealedActivityGroups.delete(group.id);
|
|
2086
|
+
for (const toolCallId of group.toolCallIds) {
|
|
2087
|
+
const component = cleanToolComponents.get(toolCallId);
|
|
2088
|
+
if (component?.expanded) originalSetExpanded.call(component, false);
|
|
2089
|
+
cleanCompactToolCallIds.delete(toolCallId);
|
|
2090
|
+
}
|
|
2091
|
+
for (const member of group.members) expandedThinkingMembers.delete(member.id);
|
|
2092
|
+
refreshActivityGroup(group);
|
|
1244
2093
|
ui?.requestRender?.();
|
|
1245
2094
|
};
|
|
1246
2095
|
const patchedMarkExecutionStarted = function (this: any) {
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
2096
|
+
activityTimeline.addTool(this.toolCallId, this.toolName);
|
|
2097
|
+
if (!cleanRun.currentToolCallIds.includes(this.toolCallId)) {
|
|
2098
|
+
cleanRun.currentToolCallIds.push(this.toolCallId);
|
|
2099
|
+
}
|
|
2100
|
+
cleanRun.activeToolCallIds.add(this.toolCallId);
|
|
2101
|
+
const displayName = toolLabel(this.toolName, this.toolDefinition?.label);
|
|
2102
|
+
cleanToolNames.set(this.toolCallId, displayName);
|
|
2103
|
+
setCleanGroupMembers(this.toolCallId, cleanRun.currentToolCallIds.slice());
|
|
2104
|
+
if (typeof this.ui?.requestRender === "function") {
|
|
2105
|
+
cleanRun.requestRender = () => this.ui.requestRender();
|
|
2106
|
+
}
|
|
2107
|
+
cleanRun.active = true;
|
|
2108
|
+
cleanRun.activeToolCallId = this.toolCallId;
|
|
2109
|
+
beginToolActivity(this.toolCallId, displayName, true);
|
|
1261
2110
|
return originalMarkExecutionStarted.call(this);
|
|
1262
2111
|
};
|
|
1263
2112
|
const patchedSetExpanded = function (this: any, expanded: boolean) {
|
|
1264
|
-
const
|
|
2113
|
+
const group = activityTimeline.groupForTool(this.toolCallId);
|
|
1265
2114
|
if (
|
|
1266
2115
|
renderMode === "clean" &&
|
|
1267
2116
|
!changingAllToolsExpansion &&
|
|
1268
2117
|
expanded &&
|
|
1269
2118
|
!this.expanded &&
|
|
1270
|
-
|
|
1271
|
-
|
|
2119
|
+
group &&
|
|
2120
|
+
!activityGroupRevealed(group)
|
|
1272
2121
|
) {
|
|
1273
|
-
revealCleanGroup(
|
|
2122
|
+
revealCleanGroup(group, this.ui);
|
|
1274
2123
|
return;
|
|
1275
2124
|
}
|
|
1276
2125
|
return originalSetExpanded.call(this, expanded);
|
|
1277
2126
|
};
|
|
1278
2127
|
const patchedToolRender = function (this: any, width: number): string[] {
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
2128
|
+
cleanToolComponents.set(this.toolCallId, this);
|
|
2129
|
+
knownToolCallIds.add(this.toolCallId);
|
|
2130
|
+
const member = activityTimeline.addTool(this.toolCallId, this.toolName);
|
|
2131
|
+
cleanToolNames.set(
|
|
2132
|
+
this.toolCallId,
|
|
2133
|
+
toolLabel(this.toolName, this.toolDefinition?.label),
|
|
2134
|
+
);
|
|
1283
2135
|
if (this[renderedModeKey] !== renderMode) {
|
|
1284
2136
|
this[renderedModeKey] = renderMode;
|
|
1285
2137
|
this.updateDisplay();
|
|
1286
2138
|
}
|
|
1287
2139
|
|
|
1288
|
-
const
|
|
1289
|
-
|
|
1290
|
-
const
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
2140
|
+
const group = activityTimeline.groupForMember(member.id);
|
|
2141
|
+
if (renderMode !== "clean" || !group) return originalToolRender.call(this, width);
|
|
2142
|
+
const position = activityMemberPosition(group, member);
|
|
2143
|
+
if (!activityGroupRevealed(group)) {
|
|
2144
|
+
return position.first
|
|
2145
|
+
? ["", ...renderActivityGroupSummary(group, width, true)]
|
|
2146
|
+
: [];
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
const childTheme = cleanThemeForToolCall(this.toolCallId) ?? activityGroupTheme(group);
|
|
2150
|
+
const {
|
|
2151
|
+
prefix: childPrefix,
|
|
2152
|
+
continuation,
|
|
2153
|
+
childWidth,
|
|
2154
|
+
} = activityTreeStyle(group, member, width, childTheme);
|
|
2155
|
+
const thirdParty = !SPECIALIZED_TOOL_NAMES.has(this.toolName);
|
|
2156
|
+
const cacheable = !this.expanded && Boolean(this.result) && !this.isPartial;
|
|
2157
|
+
const cached = cacheable ? this[cleanChildRenderCacheKey] : undefined;
|
|
2158
|
+
let decoratedContent: string[];
|
|
2159
|
+
if (
|
|
2160
|
+
cached?.width === width &&
|
|
2161
|
+
cached?.childWidth === childWidth &&
|
|
2162
|
+
cached?.result === this.result &&
|
|
2163
|
+
cached?.args === this.args &&
|
|
2164
|
+
cached?.callRenderer === this.callRendererComponent &&
|
|
2165
|
+
cached?.resultRenderer === this.resultRendererComponent &&
|
|
2166
|
+
cached?.theme === childTheme &&
|
|
2167
|
+
cached?.last === position.last &&
|
|
2168
|
+
cached?.memberCount === group.members.length
|
|
2169
|
+
) {
|
|
2170
|
+
decoratedContent = cached.lines;
|
|
2171
|
+
} else {
|
|
2172
|
+
let contentLines: string[];
|
|
2173
|
+
if (thirdParty) {
|
|
2174
|
+
const summaryLines = renderThirdPartyCompact(this, childWidth, childTheme);
|
|
2175
|
+
if (this.expanded) {
|
|
2176
|
+
const detailPrefix = " │ ";
|
|
2177
|
+
const nativeLines = originalToolRender.call(
|
|
2178
|
+
this,
|
|
2179
|
+
Math.max(1, childWidth - visibleWidth(detailPrefix)),
|
|
2180
|
+
);
|
|
2181
|
+
const nativeContentLines = nativeLines[0] === "" ? nativeLines.slice(1) : nativeLines;
|
|
2182
|
+
contentLines = [
|
|
2183
|
+
summaryLines[0],
|
|
2184
|
+
...nativeContentLines.map((line: string, index: number) =>
|
|
2185
|
+
childTheme.fg(
|
|
2186
|
+
"dim",
|
|
2187
|
+
index === nativeContentLines.length - 1 ? " └ " : detailPrefix,
|
|
2188
|
+
) + stripAnsiBackgrounds(line)
|
|
2189
|
+
),
|
|
2190
|
+
];
|
|
2191
|
+
} else {
|
|
2192
|
+
contentLines = summaryLines;
|
|
2193
|
+
}
|
|
2194
|
+
} else {
|
|
2195
|
+
const lines = originalToolRender.call(this, childWidth);
|
|
2196
|
+
if (lines.length === 0) return lines;
|
|
2197
|
+
contentLines = lines[0] === "" ? lines.slice(1) : lines;
|
|
2198
|
+
}
|
|
2199
|
+
decoratedContent = contentLines.map((line: string, index: number) =>
|
|
2200
|
+
truncateToWidth(
|
|
2201
|
+
(index === 0 ? childPrefix : continuation) + line,
|
|
2202
|
+
Math.max(1, width),
|
|
2203
|
+
"",
|
|
2204
|
+
)
|
|
2205
|
+
);
|
|
2206
|
+
if (cacheable) {
|
|
2207
|
+
this[cleanChildRenderCacheKey] = {
|
|
2208
|
+
width,
|
|
2209
|
+
childWidth,
|
|
2210
|
+
result: this.result,
|
|
2211
|
+
args: this.args,
|
|
2212
|
+
callRenderer: this.callRendererComponent,
|
|
2213
|
+
resultRenderer: this.resultRendererComponent,
|
|
2214
|
+
theme: childTheme,
|
|
2215
|
+
last: position.last,
|
|
2216
|
+
memberCount: group.members.length,
|
|
2217
|
+
lines: decoratedContent,
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
1294
2220
|
}
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
const rawContinuation = childIndex === groupToolCallIds.length - 1 ? " " : " │ ";
|
|
1298
|
-
const childTheme = cleanThemeForToolCall(this.toolCallId);
|
|
1299
|
-
const childPrefix = childTheme ? childTheme.fg("dim", rawChildPrefix) : rawChildPrefix;
|
|
1300
|
-
const continuation = childTheme ? childTheme.fg("dim", rawContinuation) : rawContinuation;
|
|
1301
|
-
const childWidth = Math.max(1, width - visibleWidth(rawChildPrefix));
|
|
1302
|
-
const lines = originalToolRender.call(this, childWidth);
|
|
1303
|
-
if (lines.length === 0) return lines;
|
|
1304
|
-
|
|
1305
|
-
const decoratedContent = lines.slice(1).map((line: string, index: number) =>
|
|
1306
|
-
(index === 0 ? childPrefix : continuation) + line
|
|
1307
|
-
);
|
|
1308
|
-
if (childIndex !== 0) return decoratedContent;
|
|
1309
|
-
return [lines[0], ...renderCleanGroupSummary(groupOwner, width), ...decoratedContent];
|
|
2221
|
+
if (!position.first) return decoratedContent;
|
|
2222
|
+
return ["", ...renderActivityGroupSummary(group, width), ...decoratedContent];
|
|
1310
2223
|
};
|
|
1311
2224
|
const patchedToolHandleMouse = function (this: any, event: any) {
|
|
1312
|
-
const
|
|
1313
|
-
const
|
|
1314
|
-
const childIndex = groupToolCallIds?.indexOf(this.toolCallId) ?? -1;
|
|
1315
|
-
const showAsChild = renderMode === "clean" && childIndex >= 0 && isCleanGroupRevealed(this.toolCallId);
|
|
2225
|
+
const member = activityTimeline.memberForTool(this.toolCallId);
|
|
2226
|
+
const group = member ? activityTimeline.groupForMember(member.id) : undefined;
|
|
1316
2227
|
const isLeftClick = event.type === "click" && event.button === "left";
|
|
2228
|
+
if (renderMode !== "clean" || !member || !group) {
|
|
2229
|
+
return originalToolHandleMouse.call(this, event);
|
|
2230
|
+
}
|
|
2231
|
+
const position = activityMemberPosition(group, member);
|
|
2232
|
+
const revealed = activityGroupRevealed(group);
|
|
1317
2233
|
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
// only; it does not affect or delay execution.
|
|
1321
|
-
if (
|
|
1322
|
-
renderMode === "clean" &&
|
|
1323
|
-
!showAsChild &&
|
|
1324
|
-
isLeftClick &&
|
|
1325
|
-
this.toolCallId === groupOwner &&
|
|
1326
|
-
groupToolCallIds?.length
|
|
1327
|
-
) {
|
|
1328
|
-
revealCleanGroup(groupToolCallIds, this.ui);
|
|
2234
|
+
if (!revealed && position.first && isLeftClick) {
|
|
2235
|
+
revealCleanGroup(group, this.ui);
|
|
1329
2236
|
return { handled: true };
|
|
1330
2237
|
}
|
|
1331
|
-
if (!
|
|
2238
|
+
if (!revealed) return isLeftClick ? { handled: true } : undefined;
|
|
1332
2239
|
|
|
1333
|
-
const summaryHeight =
|
|
1334
|
-
if (
|
|
1335
|
-
childIndex === 0 &&
|
|
1336
|
-
event.y > 0 &&
|
|
1337
|
-
event.y <= summaryHeight &&
|
|
1338
|
-
isLeftClick
|
|
1339
|
-
) {
|
|
2240
|
+
const summaryHeight = position.first ? renderActivityGroupSummary(group, event.width).length : 0;
|
|
2241
|
+
if (position.first && event.y > 0 && event.y <= summaryHeight && isLeftClick) {
|
|
1340
2242
|
changingAllToolsExpansion = true;
|
|
1341
2243
|
try {
|
|
1342
|
-
|
|
1343
|
-
const component = cleanToolComponents.get(toolCallId);
|
|
1344
|
-
if (component?.expanded) originalSetExpanded.call(component, false);
|
|
1345
|
-
cleanCompactToolCallIds.delete(toolCallId);
|
|
1346
|
-
}
|
|
1347
|
-
for (const toolCallId of groupToolCallIds) cleanToolComponents.get(toolCallId)?.updateDisplay();
|
|
2244
|
+
collapseCleanGroup(group, this.ui);
|
|
1348
2245
|
} finally {
|
|
1349
2246
|
changingAllToolsExpansion = false;
|
|
1350
2247
|
}
|
|
2248
|
+
return { handled: true };
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
// Self-rendering tools reserve y=0 for Pi's leading spacer. Clean mode
|
|
2252
|
+
// replaces that spacer with the compact header for non-first members.
|
|
2253
|
+
const thirdPartyHeaderY = position.first ? summaryHeight + 1 : 0;
|
|
2254
|
+
if (
|
|
2255
|
+
!SPECIALIZED_TOOL_NAMES.has(this.toolName) &&
|
|
2256
|
+
isLeftClick &&
|
|
2257
|
+
event.y === thirdPartyHeaderY
|
|
2258
|
+
) {
|
|
2259
|
+
originalSetExpanded.call(this, !this.expanded);
|
|
1351
2260
|
this.ui?.requestRender?.();
|
|
1352
2261
|
return { handled: true };
|
|
1353
2262
|
}
|
|
@@ -1358,12 +2267,17 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1358
2267
|
return { handled: true };
|
|
1359
2268
|
}
|
|
1360
2269
|
|
|
1361
|
-
const
|
|
2270
|
+
const { prefixWidth } = activityTreeStyle(
|
|
2271
|
+
group,
|
|
2272
|
+
member,
|
|
2273
|
+
event.width,
|
|
2274
|
+
cleanThemeForToolCall(this.toolCallId),
|
|
2275
|
+
);
|
|
1362
2276
|
return originalToolHandleMouse.call(this, {
|
|
1363
2277
|
...event,
|
|
1364
|
-
x: Math.max(0, event.x -
|
|
1365
|
-
y: event.y - summaryHeight + (
|
|
1366
|
-
width: Math.max(1, event.width -
|
|
2278
|
+
x: Math.max(0, event.x - prefixWidth),
|
|
2279
|
+
y: event.y - summaryHeight + (position.first ? 0 : 1),
|
|
2280
|
+
width: Math.max(1, event.width - prefixWidth),
|
|
1367
2281
|
});
|
|
1368
2282
|
};
|
|
1369
2283
|
|
|
@@ -1413,6 +2327,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1413
2327
|
const group: ToolSummaryGroup = {
|
|
1414
2328
|
count: cleanRun.count,
|
|
1415
2329
|
failed: cleanRun.failed,
|
|
2330
|
+
thoughtCount: thoughtCountForTool(cleanRun.lastCompletedToolCallId),
|
|
1416
2331
|
lastToolCallId: cleanRun.lastCompletedToolCallId,
|
|
1417
2332
|
toolCallIds: cleanRun.currentToolCallIds.slice(),
|
|
1418
2333
|
activity,
|
|
@@ -1421,6 +2336,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1421
2336
|
settledSummaries.set(group.lastToolCallId, {
|
|
1422
2337
|
count: group.count,
|
|
1423
2338
|
failed: group.failed,
|
|
2339
|
+
thoughtCount: group.thoughtCount ?? 0,
|
|
1424
2340
|
activity: activityValueForHold(group.lastToolCallId, activity),
|
|
1425
2341
|
});
|
|
1426
2342
|
setCleanGroupMembers(group.lastToolCallId, group.toolCallIds ?? []);
|
|
@@ -1432,6 +2348,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1432
2348
|
cleanRun.lastCompletedToolCallId = undefined;
|
|
1433
2349
|
cleanRun.activeToolCallId = undefined;
|
|
1434
2350
|
cleanRun.activeToolName = undefined;
|
|
2351
|
+
activityTimeline.boundary();
|
|
1435
2352
|
};
|
|
1436
2353
|
|
|
1437
2354
|
const settleLastCleanGroup = () => {
|
|
@@ -1441,30 +2358,44 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1441
2358
|
settledSummaries.set(group.lastToolCallId, {
|
|
1442
2359
|
count: group.count,
|
|
1443
2360
|
failed: group.failed,
|
|
2361
|
+
thoughtCount: group.thoughtCount ?? 0,
|
|
1444
2362
|
activity: activityValueForHold(group.lastToolCallId, "done"),
|
|
1445
2363
|
});
|
|
1446
2364
|
};
|
|
1447
2365
|
|
|
1448
|
-
const summaryText = (
|
|
1449
|
-
|
|
2366
|
+
const summaryText = (
|
|
2367
|
+
count: number,
|
|
2368
|
+
_failed: number,
|
|
2369
|
+
thoughtCount: number,
|
|
2370
|
+
activity = "done",
|
|
2371
|
+
): string => {
|
|
2372
|
+
const rawActivityText = typeof activity === "string" ? activity : "done";
|
|
2373
|
+
const normalizedActivityText = rawActivityText.trim();
|
|
2374
|
+
const activityText = /^thinking(?:\.\.\.)?$/iu.test(normalizedActivityText)
|
|
2375
|
+
? "thinking"
|
|
2376
|
+
: rawActivityText;
|
|
1450
2377
|
const countLabel = `${count} tool ${count === 1 ? "call" : "calls"}`;
|
|
2378
|
+
const thoughtLabel = thoughtCount > 0
|
|
2379
|
+
? ` · ${thoughtCount} ${thoughtCount === 1 ? "thought" : "thoughts"}`
|
|
2380
|
+
: "";
|
|
1451
2381
|
const activityLabel = activityText !== "done" && activityText.trim()
|
|
1452
2382
|
? ` · ${activityText}`
|
|
1453
2383
|
: "";
|
|
1454
|
-
return `${countLabel}${activityLabel}`;
|
|
2384
|
+
return `${countLabel}${thoughtLabel}${activityLabel}`;
|
|
1455
2385
|
};
|
|
1456
2386
|
|
|
1457
2387
|
const summaryRow = (
|
|
1458
2388
|
theme: any,
|
|
1459
2389
|
count: number,
|
|
1460
2390
|
failed: number,
|
|
2391
|
+
thoughtCount: number,
|
|
1461
2392
|
activity: DisplayValue = "done",
|
|
1462
2393
|
collapsedDone = false,
|
|
1463
2394
|
): DisplayRow => ({
|
|
1464
2395
|
prefix: () => {
|
|
1465
2396
|
const currentActivity = typeof activity === "function" ? activity() : activity;
|
|
1466
2397
|
const color = currentActivity === "done"
|
|
1467
|
-
? collapsedDone ? "thinkingText" :
|
|
2398
|
+
? collapsedDone ? "thinkingText" : successMarkerColor(theme)
|
|
1468
2399
|
: "accent";
|
|
1469
2400
|
return theme.fg(color, "● ");
|
|
1470
2401
|
},
|
|
@@ -1473,12 +2404,13 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1473
2404
|
const currentActivity = typeof activity === "function" ? activity() : activity;
|
|
1474
2405
|
const label = currentActivity === "done" ? "Done" : "Running";
|
|
1475
2406
|
const color = label === "Done"
|
|
1476
|
-
? collapsedDone ? "thinkingText" :
|
|
2407
|
+
? collapsedDone ? "thinkingText" : successMarkerColor(theme)
|
|
1477
2408
|
: "accent";
|
|
1478
2409
|
const detailColor = label === "Done" && collapsedDone ? "thinkingText" : "text";
|
|
1479
2410
|
return theme.fg(color, theme.bold(label)) +
|
|
1480
2411
|
theme.fg("dim", "(") +
|
|
1481
|
-
theme.fg(detailColor, summaryText(count, failed, currentActivity)) +
|
|
2412
|
+
theme.fg(detailColor, summaryText(count, failed, thoughtCount, currentActivity)) +
|
|
2413
|
+
theme.fg("dim", ")");
|
|
1482
2414
|
},
|
|
1483
2415
|
});
|
|
1484
2416
|
|
|
@@ -1487,18 +2419,70 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1487
2419
|
if (!summaryTheme) return [];
|
|
1488
2420
|
const settled = settledSummaries.get(lastToolCallId);
|
|
1489
2421
|
if (settled) {
|
|
1490
|
-
return block([summaryRow(
|
|
2422
|
+
return block([summaryRow(
|
|
2423
|
+
summaryTheme,
|
|
2424
|
+
settled.count,
|
|
2425
|
+
settled.failed,
|
|
2426
|
+
settled.thoughtCount,
|
|
2427
|
+
settled.activity,
|
|
2428
|
+
)]).render(width);
|
|
1491
2429
|
}
|
|
1492
2430
|
if (cleanRun.activeToolCallId === lastToolCallId) {
|
|
1493
2431
|
return block([summaryRow(
|
|
1494
2432
|
summaryTheme,
|
|
1495
2433
|
liveCleanToolCount(),
|
|
1496
2434
|
cleanRun.failed,
|
|
2435
|
+
thoughtCountForTool(lastToolCallId),
|
|
1497
2436
|
() => cleanRun.activeToolName ?? currentCleanActivity(),
|
|
1498
2437
|
)]).render(width);
|
|
1499
2438
|
}
|
|
1500
2439
|
if (cleanRun.lastCompletedToolCallId === lastToolCallId && cleanRun.count > 0) {
|
|
1501
|
-
return block([summaryRow(
|
|
2440
|
+
return block([summaryRow(
|
|
2441
|
+
summaryTheme,
|
|
2442
|
+
cleanRun.count,
|
|
2443
|
+
cleanRun.failed,
|
|
2444
|
+
thoughtCountForTool(lastToolCallId),
|
|
2445
|
+
currentCleanActivity,
|
|
2446
|
+
)]).render(width);
|
|
2447
|
+
}
|
|
2448
|
+
return [];
|
|
2449
|
+
};
|
|
2450
|
+
|
|
2451
|
+
const renderActivityGroupSummary = (
|
|
2452
|
+
group: ActivityGroup,
|
|
2453
|
+
width: number,
|
|
2454
|
+
collapsedDone = false,
|
|
2455
|
+
): string[] => {
|
|
2456
|
+
const fit = (lines: string[]) => lines.map((line) => truncateToWidth(line, Math.max(1, width), ""));
|
|
2457
|
+
const summaryTheme = activityGroupTheme(group);
|
|
2458
|
+
const owner = activityGroupOwner(group);
|
|
2459
|
+
if (!summaryTheme || !owner || group.toolCallIds.length === 0) return [];
|
|
2460
|
+
const settled = settledSummaries.get(owner);
|
|
2461
|
+
if (settled) {
|
|
2462
|
+
return fit(block([summaryRow(
|
|
2463
|
+
summaryTheme,
|
|
2464
|
+
Math.max(settled.count, group.toolCallIds.length),
|
|
2465
|
+
settled.failed,
|
|
2466
|
+
Math.max(settled.thoughtCount, group.thoughtCount),
|
|
2467
|
+
settled.activity,
|
|
2468
|
+
collapsedDone,
|
|
2469
|
+
)]).render(width));
|
|
2470
|
+
}
|
|
2471
|
+
const currentBelongs = cleanRun.activeToolCallId
|
|
2472
|
+
? group.toolCallIds.includes(cleanRun.activeToolCallId)
|
|
2473
|
+
: false;
|
|
2474
|
+
const lastBelongs = cleanRun.lastCompletedToolCallId
|
|
2475
|
+
? group.toolCallIds.includes(cleanRun.lastCompletedToolCallId)
|
|
2476
|
+
: false;
|
|
2477
|
+
if (currentBelongs || lastBelongs || !cleanRun.settled) {
|
|
2478
|
+
return fit(block([summaryRow(
|
|
2479
|
+
summaryTheme,
|
|
2480
|
+
Math.max(group.toolCallIds.length, liveCleanToolCount()),
|
|
2481
|
+
cleanRun.failed,
|
|
2482
|
+
group.thoughtCount,
|
|
2483
|
+
() => cleanRun.activeToolName ?? currentCleanActivity(),
|
|
2484
|
+
collapsedDone,
|
|
2485
|
+
)]).render(width));
|
|
1502
2486
|
}
|
|
1503
2487
|
return [];
|
|
1504
2488
|
};
|
|
@@ -1511,6 +2495,8 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1511
2495
|
const cleanToolCall = (theme: any, name: string, toolCallId: string): Component => {
|
|
1512
2496
|
knownToolCallIds.add(toolCallId);
|
|
1513
2497
|
cleanToolThemes.set(toolCallId, theme);
|
|
2498
|
+
const activityGroup = activityTimeline.groupForTool(toolCallId);
|
|
2499
|
+
if (activityGroup) activityFallbackThemes.set(activityGroup.id, theme);
|
|
1514
2500
|
return {
|
|
1515
2501
|
render(width: number): string[] {
|
|
1516
2502
|
if (renderMode !== "clean") return [];
|
|
@@ -1521,6 +2507,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1521
2507
|
theme,
|
|
1522
2508
|
settledSummary.count,
|
|
1523
2509
|
settledSummary.failed,
|
|
2510
|
+
settledSummary.thoughtCount,
|
|
1524
2511
|
settledSummary.activity,
|
|
1525
2512
|
true,
|
|
1526
2513
|
)]).render(width);
|
|
@@ -1532,6 +2519,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1532
2519
|
theme,
|
|
1533
2520
|
liveCleanToolCount(),
|
|
1534
2521
|
cleanRun.failed,
|
|
2522
|
+
thoughtCountForTool(toolCallId),
|
|
1535
2523
|
() => cleanRun.activeToolName ?? name,
|
|
1536
2524
|
)]).render(width);
|
|
1537
2525
|
}
|
|
@@ -1543,7 +2531,13 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1543
2531
|
cleanRun.count > 0 &&
|
|
1544
2532
|
!cleanRun.activeToolCallId
|
|
1545
2533
|
) {
|
|
1546
|
-
return block([summaryRow(
|
|
2534
|
+
return block([summaryRow(
|
|
2535
|
+
theme,
|
|
2536
|
+
cleanRun.count,
|
|
2537
|
+
cleanRun.failed,
|
|
2538
|
+
thoughtCountForTool(toolCallId),
|
|
2539
|
+
currentCleanActivity,
|
|
2540
|
+
)]).render(width);
|
|
1547
2541
|
}
|
|
1548
2542
|
|
|
1549
2543
|
// Do not fall back to the per-component pending state here: Pi may
|
|
@@ -1562,8 +2556,8 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1562
2556
|
return block([]);
|
|
1563
2557
|
};
|
|
1564
2558
|
|
|
1565
|
-
//
|
|
1566
|
-
//
|
|
2559
|
+
// Built-ins use specialized compact renderers, while the activity timeline
|
|
2560
|
+
// controls grouping for every tool, including third-party definitions.
|
|
1567
2561
|
const hideCleanTool = (
|
|
1568
2562
|
toolCallId: string,
|
|
1569
2563
|
expanded: boolean,
|
|
@@ -1586,6 +2580,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1586
2580
|
? [{
|
|
1587
2581
|
count: data.count ?? 0,
|
|
1588
2582
|
failed: data.failed ?? 0,
|
|
2583
|
+
thoughtCount: data.thoughtCount ?? 0,
|
|
1589
2584
|
lastToolCallId: data.lastToolCallId,
|
|
1590
2585
|
}]
|
|
1591
2586
|
: (() => {
|
|
@@ -1594,6 +2589,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1594
2589
|
? [{
|
|
1595
2590
|
count: data?.count ?? 0,
|
|
1596
2591
|
failed: data?.failed ?? 0,
|
|
2592
|
+
thoughtCount: data?.thoughtCount ?? 0,
|
|
1597
2593
|
lastToolCallId: summaryCallId,
|
|
1598
2594
|
}]
|
|
1599
2595
|
: [];
|
|
@@ -1603,11 +2599,10 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1603
2599
|
// absent from an uncompacted branch.
|
|
1604
2600
|
const missingGroups = groups.filter((group) => !knownToolCallIds.has(group.lastToolCallId));
|
|
1605
2601
|
if (missingGroups.length === 0) return [];
|
|
1606
|
-
// Persisted entries represent settled groups
|
|
1607
|
-
//
|
|
1608
|
-
// normalize their display to Done.
|
|
2602
|
+
// Persisted entries represent settled groups, so always normalize their
|
|
2603
|
+
// display to Done even when an older entry stored a transient activity.
|
|
1609
2604
|
return block(missingGroups.map((group) =>
|
|
1610
|
-
summaryRow(theme, group.count, group.failed, "done", true)
|
|
2605
|
+
summaryRow(theme, group.count, group.failed, group.thoughtCount ?? 0, "done", true)
|
|
1611
2606
|
)).render(width);
|
|
1612
2607
|
},
|
|
1613
2608
|
invalidate() {},
|
|
@@ -1628,6 +2623,16 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1628
2623
|
cleanToolsExpanded = ctx.ui.getToolsExpanded();
|
|
1629
2624
|
settledSummaries.clear();
|
|
1630
2625
|
legacySummaryLastToolCallIds.clear();
|
|
2626
|
+
activityTimeline.clear();
|
|
2627
|
+
revealedActivityGroups.clear();
|
|
2628
|
+
expandedThinkingMembers.clear();
|
|
2629
|
+
thinkingComponents.clear();
|
|
2630
|
+
activityFallbackThemes.clear();
|
|
2631
|
+
activityUpdateComponents.clear();
|
|
2632
|
+
customUpdateToolHints.clear();
|
|
2633
|
+
customUpdateBeforeToolHints.clear();
|
|
2634
|
+
activityUpdateSequence = 0;
|
|
2635
|
+
assistantBoundaryKeys.clear();
|
|
1631
2636
|
knownToolCallIds.clear();
|
|
1632
2637
|
cleanCompactToolCallIds.clear();
|
|
1633
2638
|
cleanGroupToolCallIds.clear();
|
|
@@ -1659,9 +2664,15 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1659
2664
|
const explicitSummaryIds = new Set<string>();
|
|
1660
2665
|
|
|
1661
2666
|
const rememberGroup = (group: ToolSummaryGroup, entryId?: string) => {
|
|
2667
|
+
const thoughtCount = Math.max(
|
|
2668
|
+
group.thoughtCount ?? 0,
|
|
2669
|
+
thoughtCountForTool(group.lastToolCallId),
|
|
2670
|
+
);
|
|
2671
|
+
group.thoughtCount = thoughtCount;
|
|
1662
2672
|
settledSummaries.set(group.lastToolCallId, {
|
|
1663
2673
|
count: group.count,
|
|
1664
2674
|
failed: group.failed,
|
|
2675
|
+
thoughtCount,
|
|
1665
2676
|
activity: "done",
|
|
1666
2677
|
});
|
|
1667
2678
|
if (group.toolCallIds?.length) {
|
|
@@ -1673,11 +2684,22 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1673
2684
|
|
|
1674
2685
|
const finishGroup = () => {
|
|
1675
2686
|
if (lastToolCallId && count > 0) {
|
|
1676
|
-
lastFinishedGroup = {
|
|
2687
|
+
lastFinishedGroup = {
|
|
2688
|
+
count,
|
|
2689
|
+
failed,
|
|
2690
|
+
thoughtCount: thoughtCountForTool(lastToolCallId),
|
|
2691
|
+
lastToolCallId,
|
|
2692
|
+
toolCallIds: [...toolCalls],
|
|
2693
|
+
};
|
|
1677
2694
|
inferredGroups.push(lastFinishedGroup);
|
|
1678
2695
|
setCleanGroupMembers(lastToolCallId, [...toolCalls]);
|
|
1679
2696
|
if (!explicitSummaryIds.has(lastToolCallId)) {
|
|
1680
|
-
settledSummaries.set(lastToolCallId, {
|
|
2697
|
+
settledSummaries.set(lastToolCallId, {
|
|
2698
|
+
count,
|
|
2699
|
+
failed,
|
|
2700
|
+
thoughtCount: lastFinishedGroup.thoughtCount ?? 0,
|
|
2701
|
+
activity: "done",
|
|
2702
|
+
});
|
|
1681
2703
|
}
|
|
1682
2704
|
}
|
|
1683
2705
|
count = 0;
|
|
@@ -1685,13 +2707,20 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1685
2707
|
lastToolCallId = undefined;
|
|
1686
2708
|
toolCalls.clear();
|
|
1687
2709
|
completedToolCalls.clear();
|
|
2710
|
+
activityTimeline.boundary();
|
|
1688
2711
|
};
|
|
1689
2712
|
|
|
1690
2713
|
const inferredGroupsOverlapping = (group: ToolSummaryGroup): ToolSummaryGroup[] => {
|
|
1691
2714
|
if (!group.toolCallIds?.length) return [];
|
|
1692
2715
|
const persistedIds = new Set(group.toolCallIds);
|
|
1693
2716
|
const currentGroup = lastToolCallId && count > 0
|
|
1694
|
-
? [{
|
|
2717
|
+
? [{
|
|
2718
|
+
count,
|
|
2719
|
+
failed,
|
|
2720
|
+
thoughtCount: thoughtCountForTool(lastToolCallId),
|
|
2721
|
+
lastToolCallId,
|
|
2722
|
+
toolCallIds: [...toolCalls],
|
|
2723
|
+
}]
|
|
1695
2724
|
: [];
|
|
1696
2725
|
return [...inferredGroups, ...currentGroup].filter((inferred) =>
|
|
1697
2726
|
inferred.toolCallIds?.some((toolCallId) => persistedIds.has(toolCallId)),
|
|
@@ -1770,6 +2799,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1770
2799
|
const group = {
|
|
1771
2800
|
count: data.count ?? 0,
|
|
1772
2801
|
failed: data.failed ?? 0,
|
|
2802
|
+
thoughtCount: data.thoughtCount ?? 0,
|
|
1773
2803
|
lastToolCallId: data.lastToolCallId,
|
|
1774
2804
|
};
|
|
1775
2805
|
rememberGroup(group);
|
|
@@ -1781,6 +2811,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1781
2811
|
rememberGroup({
|
|
1782
2812
|
count: data?.count ?? lastFinishedGroup.count,
|
|
1783
2813
|
failed: data?.failed ?? lastFinishedGroup.failed,
|
|
2814
|
+
thoughtCount: data?.thoughtCount ?? lastFinishedGroup.thoughtCount ?? 0,
|
|
1784
2815
|
lastToolCallId: lastFinishedGroup.lastToolCallId,
|
|
1785
2816
|
}, entry.id);
|
|
1786
2817
|
} else if (lastToolCallId && count > 0) {
|
|
@@ -1788,9 +2819,16 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1788
2819
|
rememberGroup({
|
|
1789
2820
|
count: data?.count ?? count,
|
|
1790
2821
|
failed: data?.failed ?? failed,
|
|
2822
|
+
thoughtCount: data?.thoughtCount ?? thoughtCountForTool(lastToolCallId),
|
|
1791
2823
|
lastToolCallId,
|
|
1792
2824
|
}, entry.id);
|
|
1793
2825
|
}
|
|
2826
|
+
activityTimeline.boundary();
|
|
2827
|
+
continue;
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
if (entry.type === "custom_message") {
|
|
2831
|
+
if (entry.display !== false) addPersistentActivityUpdate(entry);
|
|
1794
2832
|
continue;
|
|
1795
2833
|
}
|
|
1796
2834
|
|
|
@@ -1803,12 +2841,23 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1803
2841
|
continue;
|
|
1804
2842
|
}
|
|
1805
2843
|
|
|
2844
|
+
if (message.role === "custom") {
|
|
2845
|
+
if (message.display !== false) addPersistentActivityUpdate(message);
|
|
2846
|
+
continue;
|
|
2847
|
+
}
|
|
2848
|
+
|
|
1806
2849
|
if (message.role === "assistant") {
|
|
2850
|
+
const thinking = thinkingText(message);
|
|
2851
|
+
if (thinking && !assistantSystemBoundary(message)) {
|
|
2852
|
+
activityTimeline.addThinking(assistantMessageKey(message), thinking);
|
|
2853
|
+
}
|
|
1807
2854
|
if (messageHasVisibleText(message)) finishGroup();
|
|
1808
2855
|
for (const item of messageContentItems(message)) {
|
|
1809
|
-
if (item.type !== "toolCall" || !
|
|
2856
|
+
if (item.type !== "toolCall" || !item.id) continue;
|
|
1810
2857
|
toolCalls.add(item.id);
|
|
2858
|
+
activityTimeline.addTool(item.id, item.name ?? "tool");
|
|
1811
2859
|
}
|
|
2860
|
+
if (assistantSystemBoundary(message)) finishGroup();
|
|
1812
2861
|
continue;
|
|
1813
2862
|
}
|
|
1814
2863
|
|
|
@@ -1829,6 +2878,17 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1829
2878
|
|
|
1830
2879
|
pi.on("session_start", (_event, ctx) => restoreCleanSession(ctx));
|
|
1831
2880
|
pi.on("session_tree", (_event, ctx) => restoreCleanSession(ctx));
|
|
2881
|
+
pi.on("session_before_compact", () => {
|
|
2882
|
+
// Pi emits this for manual, threshold, and overflow-recovery compaction.
|
|
2883
|
+
// Seal the activity group before the compaction indicator is rendered so
|
|
2884
|
+
// completed work never remains labelled Running(... · thinking). A
|
|
2885
|
+
// lifecycle boundary overrides the minimum per-tool activity hold.
|
|
2886
|
+
clearToolActivityHolds();
|
|
2887
|
+
finishCleanGroup("done");
|
|
2888
|
+
settleLastCleanGroup();
|
|
2889
|
+
cleanRun.activity = "done";
|
|
2890
|
+
cleanRun.requestRender?.();
|
|
2891
|
+
});
|
|
1832
2892
|
pi.on("session_compact", () => {
|
|
1833
2893
|
// Compaction is a hard chronological boundary. Keep tools completed before
|
|
1834
2894
|
// it in their own group so expanded hierarchy lines never cross the summary.
|
|
@@ -1837,31 +2897,55 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1837
2897
|
// Pre-compaction tools have been summarized intentionally. Their durable
|
|
1838
2898
|
// fallback rows should not be replayed beside the compacted transcript.
|
|
1839
2899
|
cleanContextCompacted = true;
|
|
2900
|
+
cleanRun.requestRender?.();
|
|
2901
|
+
});
|
|
2902
|
+
pi.on("session_compact_failed", () => {
|
|
2903
|
+
// The pre-compaction work is still complete even when compaction is
|
|
2904
|
+
// cancelled or fails. Keep it settled and let a retry start a new group.
|
|
2905
|
+
settleLastCleanGroup();
|
|
2906
|
+
cleanRun.activity = "done";
|
|
2907
|
+
cleanRun.requestRender?.();
|
|
1840
2908
|
});
|
|
1841
2909
|
|
|
1842
2910
|
const hasVisibleAssistantText = (message: any): boolean =>
|
|
1843
2911
|
message?.role === "assistant" && messageHasVisibleText(message);
|
|
1844
2912
|
|
|
2913
|
+
const closeAtAssistantBoundary = (message: any, activity: string): boolean => {
|
|
2914
|
+
const key = assistantMessageKey(message);
|
|
2915
|
+
if (assistantBoundaryKeys.has(key)) return false;
|
|
2916
|
+
assistantBoundaryKeys.add(key);
|
|
2917
|
+
cleanRun.activity = activity;
|
|
2918
|
+
finishCleanGroup(activity);
|
|
2919
|
+
return true;
|
|
2920
|
+
};
|
|
2921
|
+
|
|
1845
2922
|
const pendingToolCalls = (message: any): any[] =>
|
|
1846
2923
|
messageContentItems(message).filter(
|
|
1847
|
-
(item: any) => item.type === "toolCall" &&
|
|
2924
|
+
(item: any) => item.type === "toolCall" && item.id,
|
|
1848
2925
|
);
|
|
1849
2926
|
|
|
2927
|
+
const trackThinkingActivity = (message: any): void => {
|
|
2928
|
+
if (message?.role !== "assistant" || assistantSystemBoundary(message)) return;
|
|
2929
|
+
const thinking = thinkingText(message);
|
|
2930
|
+
if (thinking) activityTimeline.addThinking(assistantMessageKey(message), thinking);
|
|
2931
|
+
};
|
|
2932
|
+
|
|
1850
2933
|
const trackPendingToolActivity = (message: any): boolean => {
|
|
1851
2934
|
const toolCalls = pendingToolCalls(message);
|
|
1852
2935
|
if (toolCalls.length === 0) return false;
|
|
1853
2936
|
for (const toolCall of toolCalls) {
|
|
2937
|
+
activityTimeline.addTool(toolCall.id, toolCall.name ?? "tool");
|
|
1854
2938
|
if (!cleanRun.currentToolCallIds.includes(toolCall.id)) {
|
|
1855
2939
|
cleanRun.currentToolCallIds.push(toolCall.id);
|
|
1856
2940
|
}
|
|
1857
|
-
cleanToolNames.set(toolCall.id,
|
|
2941
|
+
cleanToolNames.set(toolCall.id, cleanToolNames.get(toolCall.id) ?? toolCall.name);
|
|
1858
2942
|
}
|
|
1859
2943
|
const toolCall = toolCalls[toolCalls.length - 1];
|
|
1860
2944
|
if (cleanRun.lastCompletedToolCallId === toolCall.id) return false;
|
|
1861
2945
|
setCleanGroupMembers(toolCall.id, cleanRun.currentToolCallIds.slice());
|
|
1862
2946
|
cleanRun.active = true;
|
|
1863
2947
|
cleanRun.activeToolCallId = toolCall.id;
|
|
1864
|
-
beginToolActivity(toolCall.id,
|
|
2948
|
+
beginToolActivity(toolCall.id, cleanToolNames.get(toolCall.id) ?? toolCall.name);
|
|
1865
2949
|
return true;
|
|
1866
2950
|
};
|
|
1867
2951
|
|
|
@@ -1881,29 +2965,42 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1881
2965
|
// message_start runs before Pi adds the user component to the transcript,
|
|
1882
2966
|
// so settling here keeps the previous parent row above that message.
|
|
1883
2967
|
pi.on("message_start", (event) => {
|
|
2968
|
+
if (event.message.role === "custom" && event.message.display !== false) {
|
|
2969
|
+
addPersistentActivityUpdate(event.message);
|
|
2970
|
+
return;
|
|
2971
|
+
}
|
|
1884
2972
|
if (event.message.role !== "user") return;
|
|
1885
2973
|
finishCleanGroup("done");
|
|
1886
2974
|
settleLastCleanGroup();
|
|
1887
2975
|
});
|
|
1888
2976
|
|
|
1889
|
-
// A visible assistant response is the boundary between tool groups.
|
|
1890
|
-
//
|
|
1891
|
-
//
|
|
2977
|
+
// A visible assistant response is the boundary between tool groups. Seal
|
|
2978
|
+
// the prior group directly as Done; the response text already communicates
|
|
2979
|
+
// current activity and does not need a duplicate response status.
|
|
1892
2980
|
pi.on("message_update", (event) => {
|
|
1893
2981
|
if (hasVisibleAssistantText(event.message)) {
|
|
1894
|
-
|
|
1895
|
-
|
|
2982
|
+
trackThinkingActivity(event.message);
|
|
2983
|
+
clearToolActivityHolds();
|
|
2984
|
+
closeAtAssistantBoundary(event.message, "done");
|
|
1896
2985
|
return;
|
|
1897
2986
|
}
|
|
2987
|
+
if (assistantSystemBoundary(event.message)) {
|
|
2988
|
+
closeAtAssistantBoundary(event.message, "done");
|
|
2989
|
+
settleLastCleanGroup();
|
|
2990
|
+
return;
|
|
2991
|
+
}
|
|
2992
|
+
trackThinkingActivity(event.message);
|
|
1898
2993
|
trackPendingToolActivity(event.message);
|
|
1899
2994
|
});
|
|
1900
2995
|
pi.on("message_end", (event) => {
|
|
1901
|
-
if (hasVisibleAssistantText(event.message)) {
|
|
2996
|
+
if (hasVisibleAssistantText(event.message) || assistantSystemBoundary(event.message)) {
|
|
2997
|
+
trackThinkingActivity(event.message);
|
|
2998
|
+
closeAtAssistantBoundary(event.message, "done");
|
|
1902
2999
|
cleanRun.activity = "done";
|
|
1903
|
-
finishCleanGroup("done");
|
|
1904
3000
|
settleLastCleanGroup();
|
|
1905
3001
|
return;
|
|
1906
3002
|
}
|
|
3003
|
+
trackThinkingActivity(event.message);
|
|
1907
3004
|
trackPendingToolActivity(event.message);
|
|
1908
3005
|
});
|
|
1909
3006
|
|
|
@@ -1924,25 +3021,25 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1924
3021
|
cleanRun.activeToolCallIds.clear();
|
|
1925
3022
|
cleanRun.activeToolCallId = undefined;
|
|
1926
3023
|
cleanRun.activeToolName = undefined;
|
|
1927
|
-
cleanRun.activity = "thinking
|
|
3024
|
+
cleanRun.activity = "thinking";
|
|
1928
3025
|
});
|
|
1929
3026
|
pi.on("tool_execution_start", (event) => {
|
|
1930
|
-
|
|
3027
|
+
activityTimeline.addTool(event.toolCallId, event.toolName);
|
|
1931
3028
|
if (!cleanRun.currentToolCallIds.includes(event.toolCallId)) {
|
|
1932
3029
|
cleanRun.currentToolCallIds.push(event.toolCallId);
|
|
1933
3030
|
}
|
|
1934
3031
|
cleanRun.activeToolCallIds.add(event.toolCallId);
|
|
1935
|
-
cleanToolNames.
|
|
3032
|
+
const displayName = cleanToolNames.get(event.toolCallId) ?? event.toolName;
|
|
3033
|
+
cleanToolNames.set(event.toolCallId, displayName);
|
|
1936
3034
|
setCleanGroupMembers(event.toolCallId, cleanRun.currentToolCallIds.slice());
|
|
1937
3035
|
cleanRun.active = true;
|
|
1938
3036
|
cleanRun.activeToolCallId = event.toolCallId;
|
|
1939
|
-
beginToolActivity(event.toolCallId,
|
|
3037
|
+
beginToolActivity(event.toolCallId, displayName, true);
|
|
1940
3038
|
});
|
|
1941
3039
|
pi.on("tool_execution_end", (event) => {
|
|
1942
|
-
if (!supportedTools.has(event.toolName)) return;
|
|
1943
3040
|
const activityName = cleanRun.activeToolCallId === event.toolCallId
|
|
1944
|
-
? cleanRun.activeToolName ??
|
|
1945
|
-
:
|
|
3041
|
+
? cleanRun.activeToolName ?? cleanToolNames.get(event.toolCallId) ?? event.toolName
|
|
3042
|
+
: cleanToolNames.get(event.toolCallId) ?? event.toolName;
|
|
1946
3043
|
// Keep the tool activity visible until at least one second after start;
|
|
1947
3044
|
// this only delays the status transition, never the tool result itself.
|
|
1948
3045
|
holdToolActivity(event.toolCallId, activityName);
|
|
@@ -1957,7 +3054,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1957
3054
|
cleanRun.activeToolName = cleanRun.activeToolCallId
|
|
1958
3055
|
? cleanToolNames.get(cleanRun.activeToolCallId)
|
|
1959
3056
|
: undefined;
|
|
1960
|
-
cleanRun.activity = cleanRun.activeToolName ?? "thinking
|
|
3057
|
+
cleanRun.activity = cleanRun.activeToolName ?? "thinking";
|
|
1961
3058
|
}
|
|
1962
3059
|
const visibleSummaryToolCallId = cleanRun.activeToolCallId ?? cleanRun.lastCompletedToolCallId;
|
|
1963
3060
|
setCleanGroupMembers(visibleSummaryToolCallId, cleanRun.currentToolCallIds.slice());
|
|
@@ -1969,7 +3066,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1969
3066
|
cleanRun.activeToolCallIds.clear();
|
|
1970
3067
|
cleanRun.activeToolCallId = undefined;
|
|
1971
3068
|
cleanRun.activeToolName = undefined;
|
|
1972
|
-
cleanRun.activity = "thinking
|
|
3069
|
+
cleanRun.activity = "thinking";
|
|
1973
3070
|
});
|
|
1974
3071
|
pi.on("agent_settled", () => {
|
|
1975
3072
|
if (cleanRun.settled) return;
|
|
@@ -1982,6 +3079,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1982
3079
|
settledSummaries.set(group.lastToolCallId, {
|
|
1983
3080
|
count: group.count,
|
|
1984
3081
|
failed: group.failed,
|
|
3082
|
+
thoughtCount: group.thoughtCount ?? 0,
|
|
1985
3083
|
activity: activityValueForHold(group.lastToolCallId, "done"),
|
|
1986
3084
|
});
|
|
1987
3085
|
}
|
|
@@ -1997,14 +3095,17 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1997
3095
|
|
|
1998
3096
|
const count = groups.reduce((total, group) => total + group.count, 0);
|
|
1999
3097
|
const failed = groups.reduce((total, group) => total + group.failed, 0);
|
|
3098
|
+
const thoughtCount = groups.reduce((total, group) => total + (group.thoughtCount ?? 0), 0);
|
|
2000
3099
|
const lastToolCallId = groups[groups.length - 1]?.lastToolCallId;
|
|
2001
3100
|
pi.appendEntry<ToolSummaryData>("pretty-tui-tool-summary", {
|
|
2002
3101
|
count,
|
|
2003
3102
|
failed,
|
|
3103
|
+
thoughtCount,
|
|
2004
3104
|
lastToolCallId,
|
|
2005
|
-
groups: groups.map(({ count, failed, lastToolCallId, toolCallIds }) => ({
|
|
3105
|
+
groups: groups.map(({ count, failed, thoughtCount, lastToolCallId, toolCallIds }) => ({
|
|
2006
3106
|
count,
|
|
2007
3107
|
failed,
|
|
3108
|
+
thoughtCount,
|
|
2008
3109
|
lastToolCallId,
|
|
2009
3110
|
toolCallIds,
|
|
2010
3111
|
})),
|
|
@@ -2012,15 +3113,16 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2012
3113
|
});
|
|
2013
3114
|
|
|
2014
3115
|
const read = createReadTool(cwd);
|
|
3116
|
+
const readLabel = read.label || read.name;
|
|
2015
3117
|
pi.registerTool({
|
|
2016
3118
|
...read,
|
|
2017
3119
|
renderShell: "self",
|
|
2018
3120
|
renderCall(args: any, theme: any, context: any) {
|
|
2019
|
-
if (hideCleanTool(context.toolCallId, context.expanded, context.executionStarted)) return cleanToolCall(theme,
|
|
3121
|
+
if (hideCleanTool(context.toolCallId, context.expanded, context.executionStarted)) return cleanToolCall(theme, readLabel, context.toolCallId);
|
|
2020
3122
|
const range = args.offset || args.limit
|
|
2021
3123
|
? ` · lines ${args.offset ?? 1}${args.limit ? `–${(args.offset ?? 1) + args.limit - 1}` : "+"}`
|
|
2022
3124
|
: "";
|
|
2023
|
-
return call(theme,
|
|
3125
|
+
return call(theme, readLabel, `${args.path}${range}`, context.state);
|
|
2024
3126
|
},
|
|
2025
3127
|
renderResult(toolResult: any, options: any, theme: any, context: any) {
|
|
2026
3128
|
const output = textContent(toolResult);
|
|
@@ -2042,11 +3144,12 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2042
3144
|
} as any);
|
|
2043
3145
|
|
|
2044
3146
|
const bash = createBashTool(cwd);
|
|
3147
|
+
const bashLabel = bash.label || bash.name;
|
|
2045
3148
|
pi.registerTool({
|
|
2046
3149
|
...bash,
|
|
2047
3150
|
renderShell: "self",
|
|
2048
3151
|
renderCall(args: any, theme: any, context: any) {
|
|
2049
|
-
if (hideCleanTool(context.toolCallId, context.expanded, context.executionStarted)) return cleanToolCall(theme,
|
|
3152
|
+
if (hideCleanTool(context.toolCallId, context.expanded, context.executionStarted)) return cleanToolCall(theme, bashLabel, context.toolCallId);
|
|
2050
3153
|
const command = typeof args.command === "string" ? args.command : "";
|
|
2051
3154
|
if (useCompactToolView(context.toolCallId, context.expanded)) {
|
|
2052
3155
|
const commandLines = command.split(/\r\n|\r|\n/);
|
|
@@ -2055,9 +3158,9 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2055
3158
|
const detail = omitted > 0
|
|
2056
3159
|
? `${firstLine} … (${omitted} more ${omitted === 1 ? "line" : "lines"})`
|
|
2057
3160
|
: firstLine;
|
|
2058
|
-
return call(theme,
|
|
3161
|
+
return call(theme, bashLabel, detail, context.state);
|
|
2059
3162
|
}
|
|
2060
|
-
return call(theme,
|
|
3163
|
+
return call(theme, bashLabel, command, context.state);
|
|
2061
3164
|
},
|
|
2062
3165
|
renderResult(toolResult: any, options: any, theme: any, context: any) {
|
|
2063
3166
|
const output = textContent(toolResult);
|
|
@@ -2092,13 +3195,14 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2092
3195
|
} as any);
|
|
2093
3196
|
|
|
2094
3197
|
const edit = createEditTool(cwd);
|
|
3198
|
+
const editLabel = edit.label || edit.name;
|
|
2095
3199
|
pi.registerTool({
|
|
2096
3200
|
...edit,
|
|
2097
3201
|
renderShell: "self",
|
|
2098
3202
|
renderCall(args: any, theme: any, context: any) {
|
|
2099
|
-
if (hideCleanTool(context.toolCallId, context.expanded, context.executionStarted)) return cleanToolCall(theme,
|
|
3203
|
+
if (hideCleanTool(context.toolCallId, context.expanded, context.executionStarted)) return cleanToolCall(theme, editLabel, context.toolCallId);
|
|
2100
3204
|
const count = Array.isArray(args.edits) ? ` · ${args.edits.length} changes` : "";
|
|
2101
|
-
return call(theme,
|
|
3205
|
+
return call(theme, editLabel, `${args.path}${count}`, context.state);
|
|
2102
3206
|
},
|
|
2103
3207
|
renderResult(toolResult: any, options: any, theme: any, context: any) {
|
|
2104
3208
|
const output = textContent(toolResult);
|
|
@@ -2133,20 +3237,21 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2133
3237
|
} as any);
|
|
2134
3238
|
|
|
2135
3239
|
const write = createWriteTool(cwd);
|
|
3240
|
+
const writeLabel = write.label || write.name;
|
|
2136
3241
|
pi.registerTool({
|
|
2137
3242
|
...write,
|
|
2138
3243
|
renderShell: "self",
|
|
2139
3244
|
renderCall(args: any, theme: any, context: any) {
|
|
2140
|
-
if (hideCleanTool(context.toolCallId, context.expanded, context.executionStarted)) return cleanToolCall(theme,
|
|
3245
|
+
if (hideCleanTool(context.toolCallId, context.expanded, context.executionStarted)) return cleanToolCall(theme, writeLabel, context.toolCallId);
|
|
2141
3246
|
const content = typeof args.content === "string" ? args.content : "";
|
|
2142
3247
|
const path = String(args.path ?? "");
|
|
2143
3248
|
if (useCompactToolView(context.toolCallId, context.expanded)) {
|
|
2144
3249
|
const lines = content.replace(/\t/g, " ").split("\n");
|
|
2145
3250
|
while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
2146
3251
|
const total = lines.length;
|
|
2147
|
-
return call(theme,
|
|
3252
|
+
return call(theme, writeLabel, `${path} · ${total} ${total === 1 ? "line" : "lines"}`, context.state);
|
|
2148
3253
|
}
|
|
2149
|
-
return writeCall(theme, path, content, context.expanded, context.state);
|
|
3254
|
+
return writeCall(theme, writeLabel, path, content, context.expanded, context.state);
|
|
2150
3255
|
},
|
|
2151
3256
|
renderResult(toolResult: any, options: any, theme: any, context: any) {
|
|
2152
3257
|
const output = textContent(toolResult);
|
|
@@ -2164,14 +3269,15 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2164
3269
|
} as any);
|
|
2165
3270
|
|
|
2166
3271
|
const grep = createGrepTool(cwd);
|
|
3272
|
+
const grepLabel = grep.label || grep.name;
|
|
2167
3273
|
pi.registerTool({
|
|
2168
3274
|
...grep,
|
|
2169
3275
|
renderShell: "self",
|
|
2170
3276
|
renderCall(args: any, theme: any, context: any) {
|
|
2171
|
-
if (hideCleanTool(context.toolCallId, context.expanded, context.executionStarted)) return cleanToolCall(theme,
|
|
3277
|
+
if (hideCleanTool(context.toolCallId, context.expanded, context.executionStarted)) return cleanToolCall(theme, grepLabel, context.toolCallId);
|
|
2172
3278
|
const where = args.path ? ` · ${args.path}` : "";
|
|
2173
3279
|
const glob = args.glob ? ` · ${args.glob}` : "";
|
|
2174
|
-
return call(theme,
|
|
3280
|
+
return call(theme, grepLabel, `${args.pattern}${where}${glob}`, context.state);
|
|
2175
3281
|
},
|
|
2176
3282
|
renderResult(toolResult: any, options: any, theme: any, context: any) {
|
|
2177
3283
|
const output = textContent(toolResult);
|
|
@@ -2184,12 +3290,13 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2184
3290
|
} as any);
|
|
2185
3291
|
|
|
2186
3292
|
const find = createFindTool(cwd);
|
|
3293
|
+
const findLabel = find.label || find.name;
|
|
2187
3294
|
pi.registerTool({
|
|
2188
3295
|
...find,
|
|
2189
3296
|
renderShell: "self",
|
|
2190
3297
|
renderCall(args: any, theme: any, context: any) {
|
|
2191
|
-
if (hideCleanTool(context.toolCallId, context.expanded, context.executionStarted)) return cleanToolCall(theme,
|
|
2192
|
-
return call(theme,
|
|
3298
|
+
if (hideCleanTool(context.toolCallId, context.expanded, context.executionStarted)) return cleanToolCall(theme, findLabel, context.toolCallId);
|
|
3299
|
+
return call(theme, findLabel, `${args.pattern}${args.path ? ` · ${args.path}` : ""}`, context.state);
|
|
2193
3300
|
},
|
|
2194
3301
|
renderResult(toolResult: any, options: any, theme: any, context: any) {
|
|
2195
3302
|
const output = textContent(toolResult);
|
|
@@ -2202,12 +3309,13 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2202
3309
|
} as any);
|
|
2203
3310
|
|
|
2204
3311
|
const ls = createLsTool(cwd);
|
|
3312
|
+
const lsLabel = ls.label || ls.name;
|
|
2205
3313
|
pi.registerTool({
|
|
2206
3314
|
...ls,
|
|
2207
3315
|
renderShell: "self",
|
|
2208
3316
|
renderCall(args: any, theme: any, context: any) {
|
|
2209
|
-
if (hideCleanTool(context.toolCallId, context.expanded, context.executionStarted)) return cleanToolCall(theme,
|
|
2210
|
-
return call(theme,
|
|
3317
|
+
if (hideCleanTool(context.toolCallId, context.expanded, context.executionStarted)) return cleanToolCall(theme, lsLabel, context.toolCallId);
|
|
3318
|
+
return call(theme, lsLabel, args.path ?? ".", context.state);
|
|
2211
3319
|
},
|
|
2212
3320
|
renderResult(toolResult: any, options: any, theme: any, context: any) {
|
|
2213
3321
|
const output = textContent(toolResult);
|