pi-pretty-tui 0.1.7 → 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 +13 -9
- package/README.zh-CN.md +13 -9
- package/extensions/activity-timeline.ts +286 -0
- package/extensions/index.ts +1408 -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
|
+
};
|
|
93
184
|
};
|
|
94
185
|
|
|
95
|
-
const
|
|
96
|
-
|
|
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;
|
|
265
|
+
};
|
|
266
|
+
|
|
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),
|
|
346
838
|
);
|
|
347
|
-
|
|
348
|
-
|
|
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), "")
|
|
885
|
+
);
|
|
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
|
+
};
|
|
898
|
+
}
|
|
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 };
|
|
349
944
|
}
|
|
350
|
-
|
|
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,100 @@ 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
|
+
|
|
1343
|
+
if (token?.type === "heading") {
|
|
1344
|
+
const level = Math.max(1, Math.min(6, Number(token.depth) || 1));
|
|
1345
|
+
const maxWidth = Math.max(1, width);
|
|
1346
|
+
const addHeadingSpacing = (lines: string[]) => {
|
|
1347
|
+
if (nextTokenType && nextTokenType !== "space") lines.push("");
|
|
1348
|
+
return lines;
|
|
1349
|
+
};
|
|
1350
|
+
const headingStyle = (text: string) => this.theme.heading(text);
|
|
1351
|
+
const foregroundLuminance = (styled: string): number | undefined => {
|
|
1352
|
+
const trueColor = /\x1b\[38;2;(\d+);(\d+);(\d+)m/.exec(styled);
|
|
1353
|
+
if (!trueColor) return undefined;
|
|
1354
|
+
const [, red, green, blue] = trueColor.map(Number);
|
|
1355
|
+
return red * 0.2126 + green * 0.7152 + blue * 0.0722;
|
|
1356
|
+
};
|
|
1357
|
+
const defaultLuminance = foregroundLuminance(this.applyDefaultStyle("M"));
|
|
1358
|
+
const headingLuminance = foregroundLuminance(headingStyle("M"));
|
|
1359
|
+
const darkTheme =
|
|
1360
|
+
defaultLuminance !== undefined
|
|
1361
|
+
? defaultLuminance >= 128
|
|
1362
|
+
: (headingLuminance ?? 255) >= 160;
|
|
1363
|
+
const whiteStyle = (text: string) => `\x1b[97m${text}\x1b[39m`;
|
|
1364
|
+
const titleStyle =
|
|
1365
|
+
level === 1 || level === 3
|
|
1366
|
+
? (text: string) => headingStyle(this.theme.bold(this.theme.underline(text)))
|
|
1367
|
+
: level === 2
|
|
1368
|
+
? (text: string) =>
|
|
1369
|
+
(darkTheme ? whiteStyle : headingStyle)(
|
|
1370
|
+
this.theme.bold(this.theme.underline(text)),
|
|
1371
|
+
)
|
|
1372
|
+
: level === 4
|
|
1373
|
+
? (text: string) => headingStyle(this.theme.bold(text))
|
|
1374
|
+
: level === 5
|
|
1375
|
+
? headingStyle
|
|
1376
|
+
: (text: string) => this.theme.quote(this.theme.italic(text));
|
|
1377
|
+
const titleContext = {
|
|
1378
|
+
applyText: titleStyle,
|
|
1379
|
+
stylePrefix: this.getStylePrefix(titleStyle),
|
|
1380
|
+
};
|
|
1381
|
+
const title = this.renderInlineTokens(token.tokens || [], titleContext);
|
|
1382
|
+
|
|
1383
|
+
if (level === 1 && maxWidth >= 6) {
|
|
1384
|
+
const maximumInnerWidth = maxWidth - 4;
|
|
1385
|
+
const titleLines = wrapTextWithAnsi(title, Math.max(1, maximumInnerWidth));
|
|
1386
|
+
const innerWidth = Math.max(
|
|
1387
|
+
1,
|
|
1388
|
+
...titleLines.map((line: string) => visibleWidth(line)),
|
|
1389
|
+
);
|
|
1390
|
+
const frameWidth = Math.min(maxWidth, innerWidth + 4);
|
|
1391
|
+
const frameStyle = (text: string) => headingStyle(this.theme.bold(text));
|
|
1392
|
+
const lines = [
|
|
1393
|
+
frameStyle(`╔${"═".repeat(frameWidth - 2)}╗`),
|
|
1394
|
+
...titleLines.map((line: string) => {
|
|
1395
|
+
const padding = " ".repeat(Math.max(0, innerWidth - visibleWidth(line)));
|
|
1396
|
+
return frameStyle("║ ") + line + frameStyle(`${padding} ║`);
|
|
1397
|
+
}),
|
|
1398
|
+
frameStyle(`╚${"═".repeat(frameWidth - 2)}╝`),
|
|
1399
|
+
];
|
|
1400
|
+
return addHeadingSpacing(lines);
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
if (level === 2) {
|
|
1404
|
+
// Reverse only the rendered title cells: the theme's heading color
|
|
1405
|
+
// becomes a compact, content-width background label with no frame.
|
|
1406
|
+
const lines = wrapTextWithAnsi(title, maxWidth).map((line: string) =>
|
|
1407
|
+
darkTheme
|
|
1408
|
+
? `\x1b[48;5;94m${line}\x1b[49m`
|
|
1409
|
+
: `\x1b[107m\x1b[7m${line}\x1b[27m\x1b[49m`
|
|
1410
|
+
);
|
|
1411
|
+
return addHeadingSpacing(lines);
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
if (level === 5 && maxWidth >= 8) {
|
|
1415
|
+
const prefix = "┄┄ ";
|
|
1416
|
+
const suffix = " ┄┄";
|
|
1417
|
+
const contentWidth = Math.max(
|
|
1418
|
+
1,
|
|
1419
|
+
maxWidth - visibleWidth(prefix) - visibleWidth(suffix),
|
|
1420
|
+
);
|
|
1421
|
+
const wrapped = wrapTextWithAnsi(title, contentWidth);
|
|
1422
|
+
const lines = wrapped.map((line: string, index: number) => {
|
|
1423
|
+
const left = index === 0 ? this.theme.heading(prefix) : " ".repeat(visibleWidth(prefix));
|
|
1424
|
+
const right = index === wrapped.length - 1 ? this.theme.heading(suffix) : "";
|
|
1425
|
+
return left + line + right;
|
|
1426
|
+
});
|
|
1427
|
+
return addHeadingSpacing(lines);
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
return addHeadingSpacing(wrapTextWithAnsi(title, maxWidth));
|
|
1431
|
+
}
|
|
1432
|
+
|
|
607
1433
|
if (token?.type !== "code") {
|
|
608
1434
|
return originalRenderToken.call(this, token, width, nextTokenType, styleContext);
|
|
609
1435
|
}
|
|
@@ -676,6 +1502,15 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
676
1502
|
};
|
|
677
1503
|
|
|
678
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
|
+
}
|
|
679
1514
|
if (event.button === "left" && (event.type === "press" || event.type === "click")) {
|
|
680
1515
|
const region = (this[codeBlockRegionsKey] ?? []).find(
|
|
681
1516
|
(candidate: any) =>
|
|
@@ -823,10 +1658,24 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
823
1658
|
context.state.compactToolStatus = status;
|
|
824
1659
|
};
|
|
825
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
|
+
|
|
826
1675
|
const callRow = (theme: any, name: string, detail: string, state: any): DisplayRow => ({
|
|
827
1676
|
prefix: () => {
|
|
828
1677
|
const status = (state.compactToolStatus ?? "running") as ToolStatus;
|
|
829
|
-
const color = status === "success" ?
|
|
1678
|
+
const color = status === "success" ? successMarkerColor(theme) : status === "error" ? "error" : "dim";
|
|
830
1679
|
return theme.fg(color, "● ");
|
|
831
1680
|
},
|
|
832
1681
|
continuation: " ",
|
|
@@ -840,7 +1689,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
840
1689
|
const call = (theme: any, name: string, detail: string, state: any) =>
|
|
841
1690
|
block([callRow(theme, name, detail, state)]);
|
|
842
1691
|
|
|
843
|
-
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) => {
|
|
844
1693
|
const lines = content.replace(/\t/g, " ").split("\n");
|
|
845
1694
|
while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
846
1695
|
|
|
@@ -848,14 +1697,14 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
848
1697
|
const shown = lines.slice(0, expanded ? total : 10);
|
|
849
1698
|
const remaining = total - shown.length;
|
|
850
1699
|
const rows: DisplayRow[] = [
|
|
851
|
-
callRow(theme,
|
|
1700
|
+
callRow(theme, name, `${path} · ${total} ${total === 1 ? "line" : "lines"}`, state),
|
|
852
1701
|
];
|
|
853
1702
|
|
|
854
1703
|
for (let index = 0; index < shown.length; index++) {
|
|
855
1704
|
const isLast = index === shown.length - 1 && remaining === 0;
|
|
856
1705
|
rows.push({
|
|
857
1706
|
prefix: theme.fg("dim", isLast ? " └ " : " │ "),
|
|
858
|
-
continuation: theme.fg("dim", " │ "),
|
|
1707
|
+
continuation: isLast ? " " : theme.fg("dim", " │ "),
|
|
859
1708
|
content: theme.fg("toolOutput", shown[index] || " "),
|
|
860
1709
|
});
|
|
861
1710
|
}
|
|
@@ -888,19 +1737,22 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
888
1737
|
|
|
889
1738
|
if (expanded && output) {
|
|
890
1739
|
const lines = output.split("\n");
|
|
1740
|
+
while (lines.length > 1 && lines[lines.length - 1] === "") lines.pop();
|
|
891
1741
|
const shown = lines.slice(0, 40);
|
|
892
|
-
|
|
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;
|
|
893
1745
|
rows.push({
|
|
894
|
-
prefix: theme.fg("dim", " │ "),
|
|
895
|
-
continuation: theme.fg("dim", " │ "),
|
|
896
|
-
content: outputStyle(
|
|
1746
|
+
prefix: theme.fg("dim", isLast ? " └ " : " │ "),
|
|
1747
|
+
continuation: isLast ? " " : theme.fg("dim", " │ "),
|
|
1748
|
+
content: outputStyle(shown[index] || " "),
|
|
897
1749
|
});
|
|
898
1750
|
}
|
|
899
|
-
if (
|
|
1751
|
+
if (omitted > 0) {
|
|
900
1752
|
rows.push({
|
|
901
1753
|
prefix: theme.fg("muted", " └ "),
|
|
902
1754
|
continuation: " ",
|
|
903
|
-
content: theme.fg("muted", `… ${
|
|
1755
|
+
content: theme.fg("muted", `… ${omitted} more lines`),
|
|
904
1756
|
});
|
|
905
1757
|
}
|
|
906
1758
|
}
|
|
@@ -974,7 +1826,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
974
1826
|
const isLast = index === shown.length - 1;
|
|
975
1827
|
rows.push({
|
|
976
1828
|
prefix: theme.fg("dim", isLast ? " └ " : " │ "),
|
|
977
|
-
continuation: theme.fg("dim", " │ "),
|
|
1829
|
+
continuation: isLast ? " " : theme.fg("dim", " │ "),
|
|
978
1830
|
content: theme.fg("toolOutput", shown[index] || " "),
|
|
979
1831
|
});
|
|
980
1832
|
}
|
|
@@ -991,6 +1843,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
991
1843
|
type ToolSummaryGroup = {
|
|
992
1844
|
count: number;
|
|
993
1845
|
failed: number;
|
|
1846
|
+
thoughtCount?: number;
|
|
994
1847
|
lastToolCallId: string;
|
|
995
1848
|
toolCallIds?: string[];
|
|
996
1849
|
activity?: string;
|
|
@@ -998,12 +1851,12 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
998
1851
|
type ToolSummaryData = {
|
|
999
1852
|
count?: number;
|
|
1000
1853
|
failed?: number;
|
|
1854
|
+
thoughtCount?: number;
|
|
1001
1855
|
/** Identifies the last tool component for older single-group entries. */
|
|
1002
1856
|
lastToolCallId?: string;
|
|
1003
1857
|
/** All groups from a run, used to restore summaries after reload. */
|
|
1004
1858
|
groups?: ToolSummaryGroup[];
|
|
1005
1859
|
};
|
|
1006
|
-
const supportedTools = new Set(["read", "bash", "edit", "write", "grep", "find", "ls"]);
|
|
1007
1860
|
type ToolActivityHold = {
|
|
1008
1861
|
toolCallId: string;
|
|
1009
1862
|
name: string;
|
|
@@ -1012,7 +1865,12 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1012
1865
|
started: boolean;
|
|
1013
1866
|
timer?: ReturnType<typeof setTimeout>;
|
|
1014
1867
|
};
|
|
1015
|
-
const settledSummaries = new Map<string, {
|
|
1868
|
+
const settledSummaries = new Map<string, {
|
|
1869
|
+
count: number;
|
|
1870
|
+
failed: number;
|
|
1871
|
+
thoughtCount: number;
|
|
1872
|
+
activity: DisplayValue;
|
|
1873
|
+
}>();
|
|
1016
1874
|
const toolActivityHolds = new Map<string, ToolActivityHold>();
|
|
1017
1875
|
const legacySummaryLastToolCallIds = new Map<string, string>();
|
|
1018
1876
|
const knownToolCallIds = new Set<string>();
|
|
@@ -1040,8 +1898,64 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1040
1898
|
settled: false,
|
|
1041
1899
|
};
|
|
1042
1900
|
|
|
1043
|
-
const
|
|
1044
|
-
|
|
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
|
+
};
|
|
1045
1959
|
|
|
1046
1960
|
const clearToolActivityHolds = () => {
|
|
1047
1961
|
for (const hold of toolActivityHolds.values()) {
|
|
@@ -1088,7 +2002,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1088
2002
|
toolCallId,
|
|
1089
2003
|
name,
|
|
1090
2004
|
until: startedAt + CLEAN_TOOL_ACTIVITY_MIN_MS,
|
|
1091
|
-
after: "thinking
|
|
2005
|
+
after: "thinking",
|
|
1092
2006
|
started,
|
|
1093
2007
|
});
|
|
1094
2008
|
cleanRun.activeToolName = name;
|
|
@@ -1103,14 +2017,14 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1103
2017
|
toolCallId,
|
|
1104
2018
|
name,
|
|
1105
2019
|
until: now + CLEAN_TOOL_ACTIVITY_MIN_MS,
|
|
1106
|
-
after: "thinking
|
|
2020
|
+
after: "thinking",
|
|
1107
2021
|
started: true,
|
|
1108
2022
|
};
|
|
1109
2023
|
toolActivityHolds.set(toolCallId, hold);
|
|
1110
2024
|
}
|
|
1111
2025
|
hold.name = name;
|
|
1112
2026
|
hold.started = true;
|
|
1113
|
-
hold.after = "thinking
|
|
2027
|
+
hold.after = "thinking";
|
|
1114
2028
|
scheduleToolActivityRelease(hold);
|
|
1115
2029
|
};
|
|
1116
2030
|
|
|
@@ -1124,15 +2038,18 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1124
2038
|
const liveCleanToolCount = (): number =>
|
|
1125
2039
|
Math.max(cleanRun.count, cleanRun.currentToolCallIds.length);
|
|
1126
2040
|
|
|
2041
|
+
const thoughtCountForTool = (toolCallId: string | undefined): number =>
|
|
2042
|
+
toolCallId ? activityTimeline.groupForTool(toolCallId)?.thoughtCount ?? 0 : 0;
|
|
2043
|
+
|
|
1127
2044
|
const currentCleanActivity = (): string => {
|
|
1128
2045
|
if (cleanRun.activeToolCallId) {
|
|
1129
|
-
return cleanRun.activeToolName ?? cleanRun.activity ?? "thinking
|
|
2046
|
+
return cleanRun.activeToolName ?? cleanRun.activity ?? "thinking";
|
|
1130
2047
|
}
|
|
1131
2048
|
if (cleanRun.lastCompletedToolCallId) {
|
|
1132
2049
|
const hold = toolActivityHolds.get(cleanRun.lastCompletedToolCallId);
|
|
1133
2050
|
if (hold) return heldActivity(hold);
|
|
1134
2051
|
}
|
|
1135
|
-
return cleanRun.activeToolName ?? cleanRun.activity ?? (cleanRun.active ? "thinking
|
|
2052
|
+
return cleanRun.activeToolName ?? cleanRun.activity ?? (cleanRun.active ? "thinking" : "done");
|
|
1136
2053
|
};
|
|
1137
2054
|
|
|
1138
2055
|
pi.on("session_shutdown", clearToolActivityHolds);
|
|
@@ -1148,116 +2065,198 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1148
2065
|
const originalToolRender = toolExecutionPrototype.render;
|
|
1149
2066
|
const originalToolHandleMouse = toolExecutionPrototype.handleMouse;
|
|
1150
2067
|
const renderedModeKey = Symbol("pretty-tui.tool-rendered-mode");
|
|
1151
|
-
const
|
|
1152
|
-
|
|
1153
|
-
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);
|
|
1154
2093
|
ui?.requestRender?.();
|
|
1155
2094
|
};
|
|
1156
2095
|
const patchedMarkExecutionStarted = function (this: any) {
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
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);
|
|
1171
2110
|
return originalMarkExecutionStarted.call(this);
|
|
1172
2111
|
};
|
|
1173
2112
|
const patchedSetExpanded = function (this: any, expanded: boolean) {
|
|
1174
|
-
const
|
|
2113
|
+
const group = activityTimeline.groupForTool(this.toolCallId);
|
|
1175
2114
|
if (
|
|
1176
2115
|
renderMode === "clean" &&
|
|
1177
2116
|
!changingAllToolsExpansion &&
|
|
1178
2117
|
expanded &&
|
|
1179
2118
|
!this.expanded &&
|
|
1180
|
-
|
|
1181
|
-
|
|
2119
|
+
group &&
|
|
2120
|
+
!activityGroupRevealed(group)
|
|
1182
2121
|
) {
|
|
1183
|
-
revealCleanGroup(
|
|
2122
|
+
revealCleanGroup(group, this.ui);
|
|
1184
2123
|
return;
|
|
1185
2124
|
}
|
|
1186
2125
|
return originalSetExpanded.call(this, expanded);
|
|
1187
2126
|
};
|
|
1188
2127
|
const patchedToolRender = function (this: any, width: number): string[] {
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
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
|
+
);
|
|
1193
2135
|
if (this[renderedModeKey] !== renderMode) {
|
|
1194
2136
|
this[renderedModeKey] = renderMode;
|
|
1195
2137
|
this.updateDisplay();
|
|
1196
2138
|
}
|
|
1197
2139
|
|
|
1198
|
-
const
|
|
1199
|
-
|
|
1200
|
-
const
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
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
|
+
}
|
|
1204
2220
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
const rawContinuation = childIndex === groupToolCallIds.length - 1 ? " " : " │ ";
|
|
1208
|
-
const childTheme = cleanThemeForToolCall(this.toolCallId);
|
|
1209
|
-
const childPrefix = childTheme ? childTheme.fg("dim", rawChildPrefix) : rawChildPrefix;
|
|
1210
|
-
const continuation = childTheme ? childTheme.fg("dim", rawContinuation) : rawContinuation;
|
|
1211
|
-
const childWidth = Math.max(1, width - visibleWidth(rawChildPrefix));
|
|
1212
|
-
const lines = originalToolRender.call(this, childWidth);
|
|
1213
|
-
if (lines.length === 0) return lines;
|
|
1214
|
-
|
|
1215
|
-
const decoratedContent = lines.slice(1).map((line: string, index: number) =>
|
|
1216
|
-
(index === 0 ? childPrefix : continuation) + line
|
|
1217
|
-
);
|
|
1218
|
-
if (childIndex !== 0) return decoratedContent;
|
|
1219
|
-
return [lines[0], ...renderCleanGroupSummary(groupOwner, width), ...decoratedContent];
|
|
2221
|
+
if (!position.first) return decoratedContent;
|
|
2222
|
+
return ["", ...renderActivityGroupSummary(group, width), ...decoratedContent];
|
|
1220
2223
|
};
|
|
1221
2224
|
const patchedToolHandleMouse = function (this: any, event: any) {
|
|
1222
|
-
const
|
|
1223
|
-
const
|
|
1224
|
-
const childIndex = groupToolCallIds?.indexOf(this.toolCallId) ?? -1;
|
|
1225
|
-
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;
|
|
1226
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);
|
|
1227
2233
|
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
// only; it does not affect or delay execution.
|
|
1231
|
-
if (
|
|
1232
|
-
renderMode === "clean" &&
|
|
1233
|
-
!showAsChild &&
|
|
1234
|
-
isLeftClick &&
|
|
1235
|
-
this.toolCallId === groupOwner &&
|
|
1236
|
-
groupToolCallIds?.length
|
|
1237
|
-
) {
|
|
1238
|
-
revealCleanGroup(groupToolCallIds, this.ui);
|
|
2234
|
+
if (!revealed && position.first && isLeftClick) {
|
|
2235
|
+
revealCleanGroup(group, this.ui);
|
|
1239
2236
|
return { handled: true };
|
|
1240
2237
|
}
|
|
1241
|
-
if (!
|
|
2238
|
+
if (!revealed) return isLeftClick ? { handled: true } : undefined;
|
|
1242
2239
|
|
|
1243
|
-
const summaryHeight =
|
|
1244
|
-
if (
|
|
1245
|
-
childIndex === 0 &&
|
|
1246
|
-
event.y > 0 &&
|
|
1247
|
-
event.y <= summaryHeight &&
|
|
1248
|
-
isLeftClick
|
|
1249
|
-
) {
|
|
2240
|
+
const summaryHeight = position.first ? renderActivityGroupSummary(group, event.width).length : 0;
|
|
2241
|
+
if (position.first && event.y > 0 && event.y <= summaryHeight && isLeftClick) {
|
|
1250
2242
|
changingAllToolsExpansion = true;
|
|
1251
2243
|
try {
|
|
1252
|
-
|
|
1253
|
-
const component = cleanToolComponents.get(toolCallId);
|
|
1254
|
-
if (component?.expanded) originalSetExpanded.call(component, false);
|
|
1255
|
-
cleanCompactToolCallIds.delete(toolCallId);
|
|
1256
|
-
}
|
|
1257
|
-
for (const toolCallId of groupToolCallIds) cleanToolComponents.get(toolCallId)?.updateDisplay();
|
|
2244
|
+
collapseCleanGroup(group, this.ui);
|
|
1258
2245
|
} finally {
|
|
1259
2246
|
changingAllToolsExpansion = false;
|
|
1260
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);
|
|
1261
2260
|
this.ui?.requestRender?.();
|
|
1262
2261
|
return { handled: true };
|
|
1263
2262
|
}
|
|
@@ -1268,12 +2267,17 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1268
2267
|
return { handled: true };
|
|
1269
2268
|
}
|
|
1270
2269
|
|
|
1271
|
-
const
|
|
2270
|
+
const { prefixWidth } = activityTreeStyle(
|
|
2271
|
+
group,
|
|
2272
|
+
member,
|
|
2273
|
+
event.width,
|
|
2274
|
+
cleanThemeForToolCall(this.toolCallId),
|
|
2275
|
+
);
|
|
1272
2276
|
return originalToolHandleMouse.call(this, {
|
|
1273
2277
|
...event,
|
|
1274
|
-
x: Math.max(0, event.x -
|
|
1275
|
-
y: event.y - summaryHeight + (
|
|
1276
|
-
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),
|
|
1277
2281
|
});
|
|
1278
2282
|
};
|
|
1279
2283
|
|
|
@@ -1323,6 +2327,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1323
2327
|
const group: ToolSummaryGroup = {
|
|
1324
2328
|
count: cleanRun.count,
|
|
1325
2329
|
failed: cleanRun.failed,
|
|
2330
|
+
thoughtCount: thoughtCountForTool(cleanRun.lastCompletedToolCallId),
|
|
1326
2331
|
lastToolCallId: cleanRun.lastCompletedToolCallId,
|
|
1327
2332
|
toolCallIds: cleanRun.currentToolCallIds.slice(),
|
|
1328
2333
|
activity,
|
|
@@ -1331,6 +2336,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1331
2336
|
settledSummaries.set(group.lastToolCallId, {
|
|
1332
2337
|
count: group.count,
|
|
1333
2338
|
failed: group.failed,
|
|
2339
|
+
thoughtCount: group.thoughtCount ?? 0,
|
|
1334
2340
|
activity: activityValueForHold(group.lastToolCallId, activity),
|
|
1335
2341
|
});
|
|
1336
2342
|
setCleanGroupMembers(group.lastToolCallId, group.toolCallIds ?? []);
|
|
@@ -1342,6 +2348,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1342
2348
|
cleanRun.lastCompletedToolCallId = undefined;
|
|
1343
2349
|
cleanRun.activeToolCallId = undefined;
|
|
1344
2350
|
cleanRun.activeToolName = undefined;
|
|
2351
|
+
activityTimeline.boundary();
|
|
1345
2352
|
};
|
|
1346
2353
|
|
|
1347
2354
|
const settleLastCleanGroup = () => {
|
|
@@ -1351,30 +2358,44 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1351
2358
|
settledSummaries.set(group.lastToolCallId, {
|
|
1352
2359
|
count: group.count,
|
|
1353
2360
|
failed: group.failed,
|
|
2361
|
+
thoughtCount: group.thoughtCount ?? 0,
|
|
1354
2362
|
activity: activityValueForHold(group.lastToolCallId, "done"),
|
|
1355
2363
|
});
|
|
1356
2364
|
};
|
|
1357
2365
|
|
|
1358
|
-
const summaryText = (
|
|
1359
|
-
|
|
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;
|
|
1360
2377
|
const countLabel = `${count} tool ${count === 1 ? "call" : "calls"}`;
|
|
2378
|
+
const thoughtLabel = thoughtCount > 0
|
|
2379
|
+
? ` · ${thoughtCount} ${thoughtCount === 1 ? "thought" : "thoughts"}`
|
|
2380
|
+
: "";
|
|
1361
2381
|
const activityLabel = activityText !== "done" && activityText.trim()
|
|
1362
2382
|
? ` · ${activityText}`
|
|
1363
2383
|
: "";
|
|
1364
|
-
return `${countLabel}${activityLabel}`;
|
|
2384
|
+
return `${countLabel}${thoughtLabel}${activityLabel}`;
|
|
1365
2385
|
};
|
|
1366
2386
|
|
|
1367
2387
|
const summaryRow = (
|
|
1368
2388
|
theme: any,
|
|
1369
2389
|
count: number,
|
|
1370
2390
|
failed: number,
|
|
2391
|
+
thoughtCount: number,
|
|
1371
2392
|
activity: DisplayValue = "done",
|
|
1372
2393
|
collapsedDone = false,
|
|
1373
2394
|
): DisplayRow => ({
|
|
1374
2395
|
prefix: () => {
|
|
1375
2396
|
const currentActivity = typeof activity === "function" ? activity() : activity;
|
|
1376
2397
|
const color = currentActivity === "done"
|
|
1377
|
-
? collapsedDone ? "thinkingText" :
|
|
2398
|
+
? collapsedDone ? "thinkingText" : successMarkerColor(theme)
|
|
1378
2399
|
: "accent";
|
|
1379
2400
|
return theme.fg(color, "● ");
|
|
1380
2401
|
},
|
|
@@ -1383,12 +2404,13 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1383
2404
|
const currentActivity = typeof activity === "function" ? activity() : activity;
|
|
1384
2405
|
const label = currentActivity === "done" ? "Done" : "Running";
|
|
1385
2406
|
const color = label === "Done"
|
|
1386
|
-
? collapsedDone ? "thinkingText" :
|
|
2407
|
+
? collapsedDone ? "thinkingText" : successMarkerColor(theme)
|
|
1387
2408
|
: "accent";
|
|
1388
2409
|
const detailColor = label === "Done" && collapsedDone ? "thinkingText" : "text";
|
|
1389
2410
|
return theme.fg(color, theme.bold(label)) +
|
|
1390
2411
|
theme.fg("dim", "(") +
|
|
1391
|
-
theme.fg(detailColor, summaryText(count, failed, currentActivity)) +
|
|
2412
|
+
theme.fg(detailColor, summaryText(count, failed, thoughtCount, currentActivity)) +
|
|
2413
|
+
theme.fg("dim", ")");
|
|
1392
2414
|
},
|
|
1393
2415
|
});
|
|
1394
2416
|
|
|
@@ -1397,18 +2419,70 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1397
2419
|
if (!summaryTheme) return [];
|
|
1398
2420
|
const settled = settledSummaries.get(lastToolCallId);
|
|
1399
2421
|
if (settled) {
|
|
1400
|
-
return block([summaryRow(
|
|
2422
|
+
return block([summaryRow(
|
|
2423
|
+
summaryTheme,
|
|
2424
|
+
settled.count,
|
|
2425
|
+
settled.failed,
|
|
2426
|
+
settled.thoughtCount,
|
|
2427
|
+
settled.activity,
|
|
2428
|
+
)]).render(width);
|
|
1401
2429
|
}
|
|
1402
2430
|
if (cleanRun.activeToolCallId === lastToolCallId) {
|
|
1403
2431
|
return block([summaryRow(
|
|
1404
2432
|
summaryTheme,
|
|
1405
2433
|
liveCleanToolCount(),
|
|
1406
2434
|
cleanRun.failed,
|
|
2435
|
+
thoughtCountForTool(lastToolCallId),
|
|
1407
2436
|
() => cleanRun.activeToolName ?? currentCleanActivity(),
|
|
1408
2437
|
)]).render(width);
|
|
1409
2438
|
}
|
|
1410
2439
|
if (cleanRun.lastCompletedToolCallId === lastToolCallId && cleanRun.count > 0) {
|
|
1411
|
-
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));
|
|
1412
2486
|
}
|
|
1413
2487
|
return [];
|
|
1414
2488
|
};
|
|
@@ -1421,6 +2495,8 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1421
2495
|
const cleanToolCall = (theme: any, name: string, toolCallId: string): Component => {
|
|
1422
2496
|
knownToolCallIds.add(toolCallId);
|
|
1423
2497
|
cleanToolThemes.set(toolCallId, theme);
|
|
2498
|
+
const activityGroup = activityTimeline.groupForTool(toolCallId);
|
|
2499
|
+
if (activityGroup) activityFallbackThemes.set(activityGroup.id, theme);
|
|
1424
2500
|
return {
|
|
1425
2501
|
render(width: number): string[] {
|
|
1426
2502
|
if (renderMode !== "clean") return [];
|
|
@@ -1431,6 +2507,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1431
2507
|
theme,
|
|
1432
2508
|
settledSummary.count,
|
|
1433
2509
|
settledSummary.failed,
|
|
2510
|
+
settledSummary.thoughtCount,
|
|
1434
2511
|
settledSummary.activity,
|
|
1435
2512
|
true,
|
|
1436
2513
|
)]).render(width);
|
|
@@ -1442,6 +2519,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1442
2519
|
theme,
|
|
1443
2520
|
liveCleanToolCount(),
|
|
1444
2521
|
cleanRun.failed,
|
|
2522
|
+
thoughtCountForTool(toolCallId),
|
|
1445
2523
|
() => cleanRun.activeToolName ?? name,
|
|
1446
2524
|
)]).render(width);
|
|
1447
2525
|
}
|
|
@@ -1453,7 +2531,13 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1453
2531
|
cleanRun.count > 0 &&
|
|
1454
2532
|
!cleanRun.activeToolCallId
|
|
1455
2533
|
) {
|
|
1456
|
-
return block([summaryRow(
|
|
2534
|
+
return block([summaryRow(
|
|
2535
|
+
theme,
|
|
2536
|
+
cleanRun.count,
|
|
2537
|
+
cleanRun.failed,
|
|
2538
|
+
thoughtCountForTool(toolCallId),
|
|
2539
|
+
currentCleanActivity,
|
|
2540
|
+
)]).render(width);
|
|
1457
2541
|
}
|
|
1458
2542
|
|
|
1459
2543
|
// Do not fall back to the per-component pending state here: Pi may
|
|
@@ -1472,8 +2556,8 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1472
2556
|
return block([]);
|
|
1473
2557
|
};
|
|
1474
2558
|
|
|
1475
|
-
//
|
|
1476
|
-
//
|
|
2559
|
+
// Built-ins use specialized compact renderers, while the activity timeline
|
|
2560
|
+
// controls grouping for every tool, including third-party definitions.
|
|
1477
2561
|
const hideCleanTool = (
|
|
1478
2562
|
toolCallId: string,
|
|
1479
2563
|
expanded: boolean,
|
|
@@ -1496,6 +2580,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1496
2580
|
? [{
|
|
1497
2581
|
count: data.count ?? 0,
|
|
1498
2582
|
failed: data.failed ?? 0,
|
|
2583
|
+
thoughtCount: data.thoughtCount ?? 0,
|
|
1499
2584
|
lastToolCallId: data.lastToolCallId,
|
|
1500
2585
|
}]
|
|
1501
2586
|
: (() => {
|
|
@@ -1504,6 +2589,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1504
2589
|
? [{
|
|
1505
2590
|
count: data?.count ?? 0,
|
|
1506
2591
|
failed: data?.failed ?? 0,
|
|
2592
|
+
thoughtCount: data?.thoughtCount ?? 0,
|
|
1507
2593
|
lastToolCallId: summaryCallId,
|
|
1508
2594
|
}]
|
|
1509
2595
|
: [];
|
|
@@ -1513,11 +2599,10 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1513
2599
|
// absent from an uncompacted branch.
|
|
1514
2600
|
const missingGroups = groups.filter((group) => !knownToolCallIds.has(group.lastToolCallId));
|
|
1515
2601
|
if (missingGroups.length === 0) return [];
|
|
1516
|
-
// Persisted entries represent settled groups
|
|
1517
|
-
//
|
|
1518
|
-
// 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.
|
|
1519
2604
|
return block(missingGroups.map((group) =>
|
|
1520
|
-
summaryRow(theme, group.count, group.failed, "done", true)
|
|
2605
|
+
summaryRow(theme, group.count, group.failed, group.thoughtCount ?? 0, "done", true)
|
|
1521
2606
|
)).render(width);
|
|
1522
2607
|
},
|
|
1523
2608
|
invalidate() {},
|
|
@@ -1538,6 +2623,16 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1538
2623
|
cleanToolsExpanded = ctx.ui.getToolsExpanded();
|
|
1539
2624
|
settledSummaries.clear();
|
|
1540
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();
|
|
1541
2636
|
knownToolCallIds.clear();
|
|
1542
2637
|
cleanCompactToolCallIds.clear();
|
|
1543
2638
|
cleanGroupToolCallIds.clear();
|
|
@@ -1569,9 +2664,15 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1569
2664
|
const explicitSummaryIds = new Set<string>();
|
|
1570
2665
|
|
|
1571
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;
|
|
1572
2672
|
settledSummaries.set(group.lastToolCallId, {
|
|
1573
2673
|
count: group.count,
|
|
1574
2674
|
failed: group.failed,
|
|
2675
|
+
thoughtCount,
|
|
1575
2676
|
activity: "done",
|
|
1576
2677
|
});
|
|
1577
2678
|
if (group.toolCallIds?.length) {
|
|
@@ -1583,11 +2684,22 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1583
2684
|
|
|
1584
2685
|
const finishGroup = () => {
|
|
1585
2686
|
if (lastToolCallId && count > 0) {
|
|
1586
|
-
lastFinishedGroup = {
|
|
2687
|
+
lastFinishedGroup = {
|
|
2688
|
+
count,
|
|
2689
|
+
failed,
|
|
2690
|
+
thoughtCount: thoughtCountForTool(lastToolCallId),
|
|
2691
|
+
lastToolCallId,
|
|
2692
|
+
toolCallIds: [...toolCalls],
|
|
2693
|
+
};
|
|
1587
2694
|
inferredGroups.push(lastFinishedGroup);
|
|
1588
2695
|
setCleanGroupMembers(lastToolCallId, [...toolCalls]);
|
|
1589
2696
|
if (!explicitSummaryIds.has(lastToolCallId)) {
|
|
1590
|
-
settledSummaries.set(lastToolCallId, {
|
|
2697
|
+
settledSummaries.set(lastToolCallId, {
|
|
2698
|
+
count,
|
|
2699
|
+
failed,
|
|
2700
|
+
thoughtCount: lastFinishedGroup.thoughtCount ?? 0,
|
|
2701
|
+
activity: "done",
|
|
2702
|
+
});
|
|
1591
2703
|
}
|
|
1592
2704
|
}
|
|
1593
2705
|
count = 0;
|
|
@@ -1595,13 +2707,20 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1595
2707
|
lastToolCallId = undefined;
|
|
1596
2708
|
toolCalls.clear();
|
|
1597
2709
|
completedToolCalls.clear();
|
|
2710
|
+
activityTimeline.boundary();
|
|
1598
2711
|
};
|
|
1599
2712
|
|
|
1600
2713
|
const inferredGroupsOverlapping = (group: ToolSummaryGroup): ToolSummaryGroup[] => {
|
|
1601
2714
|
if (!group.toolCallIds?.length) return [];
|
|
1602
2715
|
const persistedIds = new Set(group.toolCallIds);
|
|
1603
2716
|
const currentGroup = lastToolCallId && count > 0
|
|
1604
|
-
? [{
|
|
2717
|
+
? [{
|
|
2718
|
+
count,
|
|
2719
|
+
failed,
|
|
2720
|
+
thoughtCount: thoughtCountForTool(lastToolCallId),
|
|
2721
|
+
lastToolCallId,
|
|
2722
|
+
toolCallIds: [...toolCalls],
|
|
2723
|
+
}]
|
|
1605
2724
|
: [];
|
|
1606
2725
|
return [...inferredGroups, ...currentGroup].filter((inferred) =>
|
|
1607
2726
|
inferred.toolCallIds?.some((toolCallId) => persistedIds.has(toolCallId)),
|
|
@@ -1680,6 +2799,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1680
2799
|
const group = {
|
|
1681
2800
|
count: data.count ?? 0,
|
|
1682
2801
|
failed: data.failed ?? 0,
|
|
2802
|
+
thoughtCount: data.thoughtCount ?? 0,
|
|
1683
2803
|
lastToolCallId: data.lastToolCallId,
|
|
1684
2804
|
};
|
|
1685
2805
|
rememberGroup(group);
|
|
@@ -1691,6 +2811,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1691
2811
|
rememberGroup({
|
|
1692
2812
|
count: data?.count ?? lastFinishedGroup.count,
|
|
1693
2813
|
failed: data?.failed ?? lastFinishedGroup.failed,
|
|
2814
|
+
thoughtCount: data?.thoughtCount ?? lastFinishedGroup.thoughtCount ?? 0,
|
|
1694
2815
|
lastToolCallId: lastFinishedGroup.lastToolCallId,
|
|
1695
2816
|
}, entry.id);
|
|
1696
2817
|
} else if (lastToolCallId && count > 0) {
|
|
@@ -1698,9 +2819,16 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1698
2819
|
rememberGroup({
|
|
1699
2820
|
count: data?.count ?? count,
|
|
1700
2821
|
failed: data?.failed ?? failed,
|
|
2822
|
+
thoughtCount: data?.thoughtCount ?? thoughtCountForTool(lastToolCallId),
|
|
1701
2823
|
lastToolCallId,
|
|
1702
2824
|
}, entry.id);
|
|
1703
2825
|
}
|
|
2826
|
+
activityTimeline.boundary();
|
|
2827
|
+
continue;
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
if (entry.type === "custom_message") {
|
|
2831
|
+
if (entry.display !== false) addPersistentActivityUpdate(entry);
|
|
1704
2832
|
continue;
|
|
1705
2833
|
}
|
|
1706
2834
|
|
|
@@ -1713,12 +2841,23 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1713
2841
|
continue;
|
|
1714
2842
|
}
|
|
1715
2843
|
|
|
2844
|
+
if (message.role === "custom") {
|
|
2845
|
+
if (message.display !== false) addPersistentActivityUpdate(message);
|
|
2846
|
+
continue;
|
|
2847
|
+
}
|
|
2848
|
+
|
|
1716
2849
|
if (message.role === "assistant") {
|
|
2850
|
+
const thinking = thinkingText(message);
|
|
2851
|
+
if (thinking && !assistantSystemBoundary(message)) {
|
|
2852
|
+
activityTimeline.addThinking(assistantMessageKey(message), thinking);
|
|
2853
|
+
}
|
|
1717
2854
|
if (messageHasVisibleText(message)) finishGroup();
|
|
1718
2855
|
for (const item of messageContentItems(message)) {
|
|
1719
|
-
if (item.type !== "toolCall" || !
|
|
2856
|
+
if (item.type !== "toolCall" || !item.id) continue;
|
|
1720
2857
|
toolCalls.add(item.id);
|
|
2858
|
+
activityTimeline.addTool(item.id, item.name ?? "tool");
|
|
1721
2859
|
}
|
|
2860
|
+
if (assistantSystemBoundary(message)) finishGroup();
|
|
1722
2861
|
continue;
|
|
1723
2862
|
}
|
|
1724
2863
|
|
|
@@ -1739,6 +2878,17 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1739
2878
|
|
|
1740
2879
|
pi.on("session_start", (_event, ctx) => restoreCleanSession(ctx));
|
|
1741
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
|
+
});
|
|
1742
2892
|
pi.on("session_compact", () => {
|
|
1743
2893
|
// Compaction is a hard chronological boundary. Keep tools completed before
|
|
1744
2894
|
// it in their own group so expanded hierarchy lines never cross the summary.
|
|
@@ -1747,31 +2897,55 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1747
2897
|
// Pre-compaction tools have been summarized intentionally. Their durable
|
|
1748
2898
|
// fallback rows should not be replayed beside the compacted transcript.
|
|
1749
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?.();
|
|
1750
2908
|
});
|
|
1751
2909
|
|
|
1752
2910
|
const hasVisibleAssistantText = (message: any): boolean =>
|
|
1753
2911
|
message?.role === "assistant" && messageHasVisibleText(message);
|
|
1754
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
|
+
|
|
1755
2922
|
const pendingToolCalls = (message: any): any[] =>
|
|
1756
2923
|
messageContentItems(message).filter(
|
|
1757
|
-
(item: any) => item.type === "toolCall" &&
|
|
2924
|
+
(item: any) => item.type === "toolCall" && item.id,
|
|
1758
2925
|
);
|
|
1759
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
|
+
|
|
1760
2933
|
const trackPendingToolActivity = (message: any): boolean => {
|
|
1761
2934
|
const toolCalls = pendingToolCalls(message);
|
|
1762
2935
|
if (toolCalls.length === 0) return false;
|
|
1763
2936
|
for (const toolCall of toolCalls) {
|
|
2937
|
+
activityTimeline.addTool(toolCall.id, toolCall.name ?? "tool");
|
|
1764
2938
|
if (!cleanRun.currentToolCallIds.includes(toolCall.id)) {
|
|
1765
2939
|
cleanRun.currentToolCallIds.push(toolCall.id);
|
|
1766
2940
|
}
|
|
1767
|
-
cleanToolNames.set(toolCall.id,
|
|
2941
|
+
cleanToolNames.set(toolCall.id, cleanToolNames.get(toolCall.id) ?? toolCall.name);
|
|
1768
2942
|
}
|
|
1769
2943
|
const toolCall = toolCalls[toolCalls.length - 1];
|
|
1770
2944
|
if (cleanRun.lastCompletedToolCallId === toolCall.id) return false;
|
|
1771
2945
|
setCleanGroupMembers(toolCall.id, cleanRun.currentToolCallIds.slice());
|
|
1772
2946
|
cleanRun.active = true;
|
|
1773
2947
|
cleanRun.activeToolCallId = toolCall.id;
|
|
1774
|
-
beginToolActivity(toolCall.id,
|
|
2948
|
+
beginToolActivity(toolCall.id, cleanToolNames.get(toolCall.id) ?? toolCall.name);
|
|
1775
2949
|
return true;
|
|
1776
2950
|
};
|
|
1777
2951
|
|
|
@@ -1791,29 +2965,42 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1791
2965
|
// message_start runs before Pi adds the user component to the transcript,
|
|
1792
2966
|
// so settling here keeps the previous parent row above that message.
|
|
1793
2967
|
pi.on("message_start", (event) => {
|
|
2968
|
+
if (event.message.role === "custom" && event.message.display !== false) {
|
|
2969
|
+
addPersistentActivityUpdate(event.message);
|
|
2970
|
+
return;
|
|
2971
|
+
}
|
|
1794
2972
|
if (event.message.role !== "user") return;
|
|
1795
2973
|
finishCleanGroup("done");
|
|
1796
2974
|
settleLastCleanGroup();
|
|
1797
2975
|
});
|
|
1798
2976
|
|
|
1799
|
-
// A visible assistant response is the boundary between tool groups.
|
|
1800
|
-
//
|
|
1801
|
-
//
|
|
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.
|
|
1802
2980
|
pi.on("message_update", (event) => {
|
|
1803
2981
|
if (hasVisibleAssistantText(event.message)) {
|
|
1804
|
-
|
|
1805
|
-
|
|
2982
|
+
trackThinkingActivity(event.message);
|
|
2983
|
+
clearToolActivityHolds();
|
|
2984
|
+
closeAtAssistantBoundary(event.message, "done");
|
|
1806
2985
|
return;
|
|
1807
2986
|
}
|
|
2987
|
+
if (assistantSystemBoundary(event.message)) {
|
|
2988
|
+
closeAtAssistantBoundary(event.message, "done");
|
|
2989
|
+
settleLastCleanGroup();
|
|
2990
|
+
return;
|
|
2991
|
+
}
|
|
2992
|
+
trackThinkingActivity(event.message);
|
|
1808
2993
|
trackPendingToolActivity(event.message);
|
|
1809
2994
|
});
|
|
1810
2995
|
pi.on("message_end", (event) => {
|
|
1811
|
-
if (hasVisibleAssistantText(event.message)) {
|
|
2996
|
+
if (hasVisibleAssistantText(event.message) || assistantSystemBoundary(event.message)) {
|
|
2997
|
+
trackThinkingActivity(event.message);
|
|
2998
|
+
closeAtAssistantBoundary(event.message, "done");
|
|
1812
2999
|
cleanRun.activity = "done";
|
|
1813
|
-
finishCleanGroup("done");
|
|
1814
3000
|
settleLastCleanGroup();
|
|
1815
3001
|
return;
|
|
1816
3002
|
}
|
|
3003
|
+
trackThinkingActivity(event.message);
|
|
1817
3004
|
trackPendingToolActivity(event.message);
|
|
1818
3005
|
});
|
|
1819
3006
|
|
|
@@ -1834,25 +3021,25 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1834
3021
|
cleanRun.activeToolCallIds.clear();
|
|
1835
3022
|
cleanRun.activeToolCallId = undefined;
|
|
1836
3023
|
cleanRun.activeToolName = undefined;
|
|
1837
|
-
cleanRun.activity = "thinking
|
|
3024
|
+
cleanRun.activity = "thinking";
|
|
1838
3025
|
});
|
|
1839
3026
|
pi.on("tool_execution_start", (event) => {
|
|
1840
|
-
|
|
3027
|
+
activityTimeline.addTool(event.toolCallId, event.toolName);
|
|
1841
3028
|
if (!cleanRun.currentToolCallIds.includes(event.toolCallId)) {
|
|
1842
3029
|
cleanRun.currentToolCallIds.push(event.toolCallId);
|
|
1843
3030
|
}
|
|
1844
3031
|
cleanRun.activeToolCallIds.add(event.toolCallId);
|
|
1845
|
-
cleanToolNames.
|
|
3032
|
+
const displayName = cleanToolNames.get(event.toolCallId) ?? event.toolName;
|
|
3033
|
+
cleanToolNames.set(event.toolCallId, displayName);
|
|
1846
3034
|
setCleanGroupMembers(event.toolCallId, cleanRun.currentToolCallIds.slice());
|
|
1847
3035
|
cleanRun.active = true;
|
|
1848
3036
|
cleanRun.activeToolCallId = event.toolCallId;
|
|
1849
|
-
beginToolActivity(event.toolCallId,
|
|
3037
|
+
beginToolActivity(event.toolCallId, displayName, true);
|
|
1850
3038
|
});
|
|
1851
3039
|
pi.on("tool_execution_end", (event) => {
|
|
1852
|
-
if (!supportedTools.has(event.toolName)) return;
|
|
1853
3040
|
const activityName = cleanRun.activeToolCallId === event.toolCallId
|
|
1854
|
-
? cleanRun.activeToolName ??
|
|
1855
|
-
:
|
|
3041
|
+
? cleanRun.activeToolName ?? cleanToolNames.get(event.toolCallId) ?? event.toolName
|
|
3042
|
+
: cleanToolNames.get(event.toolCallId) ?? event.toolName;
|
|
1856
3043
|
// Keep the tool activity visible until at least one second after start;
|
|
1857
3044
|
// this only delays the status transition, never the tool result itself.
|
|
1858
3045
|
holdToolActivity(event.toolCallId, activityName);
|
|
@@ -1867,7 +3054,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1867
3054
|
cleanRun.activeToolName = cleanRun.activeToolCallId
|
|
1868
3055
|
? cleanToolNames.get(cleanRun.activeToolCallId)
|
|
1869
3056
|
: undefined;
|
|
1870
|
-
cleanRun.activity = cleanRun.activeToolName ?? "thinking
|
|
3057
|
+
cleanRun.activity = cleanRun.activeToolName ?? "thinking";
|
|
1871
3058
|
}
|
|
1872
3059
|
const visibleSummaryToolCallId = cleanRun.activeToolCallId ?? cleanRun.lastCompletedToolCallId;
|
|
1873
3060
|
setCleanGroupMembers(visibleSummaryToolCallId, cleanRun.currentToolCallIds.slice());
|
|
@@ -1879,7 +3066,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1879
3066
|
cleanRun.activeToolCallIds.clear();
|
|
1880
3067
|
cleanRun.activeToolCallId = undefined;
|
|
1881
3068
|
cleanRun.activeToolName = undefined;
|
|
1882
|
-
cleanRun.activity = "thinking
|
|
3069
|
+
cleanRun.activity = "thinking";
|
|
1883
3070
|
});
|
|
1884
3071
|
pi.on("agent_settled", () => {
|
|
1885
3072
|
if (cleanRun.settled) return;
|
|
@@ -1892,6 +3079,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1892
3079
|
settledSummaries.set(group.lastToolCallId, {
|
|
1893
3080
|
count: group.count,
|
|
1894
3081
|
failed: group.failed,
|
|
3082
|
+
thoughtCount: group.thoughtCount ?? 0,
|
|
1895
3083
|
activity: activityValueForHold(group.lastToolCallId, "done"),
|
|
1896
3084
|
});
|
|
1897
3085
|
}
|
|
@@ -1907,14 +3095,17 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1907
3095
|
|
|
1908
3096
|
const count = groups.reduce((total, group) => total + group.count, 0);
|
|
1909
3097
|
const failed = groups.reduce((total, group) => total + group.failed, 0);
|
|
3098
|
+
const thoughtCount = groups.reduce((total, group) => total + (group.thoughtCount ?? 0), 0);
|
|
1910
3099
|
const lastToolCallId = groups[groups.length - 1]?.lastToolCallId;
|
|
1911
3100
|
pi.appendEntry<ToolSummaryData>("pretty-tui-tool-summary", {
|
|
1912
3101
|
count,
|
|
1913
3102
|
failed,
|
|
3103
|
+
thoughtCount,
|
|
1914
3104
|
lastToolCallId,
|
|
1915
|
-
groups: groups.map(({ count, failed, lastToolCallId, toolCallIds }) => ({
|
|
3105
|
+
groups: groups.map(({ count, failed, thoughtCount, lastToolCallId, toolCallIds }) => ({
|
|
1916
3106
|
count,
|
|
1917
3107
|
failed,
|
|
3108
|
+
thoughtCount,
|
|
1918
3109
|
lastToolCallId,
|
|
1919
3110
|
toolCallIds,
|
|
1920
3111
|
})),
|
|
@@ -1922,15 +3113,16 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1922
3113
|
});
|
|
1923
3114
|
|
|
1924
3115
|
const read = createReadTool(cwd);
|
|
3116
|
+
const readLabel = read.label || read.name;
|
|
1925
3117
|
pi.registerTool({
|
|
1926
3118
|
...read,
|
|
1927
3119
|
renderShell: "self",
|
|
1928
3120
|
renderCall(args: any, theme: any, context: any) {
|
|
1929
|
-
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);
|
|
1930
3122
|
const range = args.offset || args.limit
|
|
1931
3123
|
? ` · lines ${args.offset ?? 1}${args.limit ? `–${(args.offset ?? 1) + args.limit - 1}` : "+"}`
|
|
1932
3124
|
: "";
|
|
1933
|
-
return call(theme,
|
|
3125
|
+
return call(theme, readLabel, `${args.path}${range}`, context.state);
|
|
1934
3126
|
},
|
|
1935
3127
|
renderResult(toolResult: any, options: any, theme: any, context: any) {
|
|
1936
3128
|
const output = textContent(toolResult);
|
|
@@ -1952,11 +3144,12 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1952
3144
|
} as any);
|
|
1953
3145
|
|
|
1954
3146
|
const bash = createBashTool(cwd);
|
|
3147
|
+
const bashLabel = bash.label || bash.name;
|
|
1955
3148
|
pi.registerTool({
|
|
1956
3149
|
...bash,
|
|
1957
3150
|
renderShell: "self",
|
|
1958
3151
|
renderCall(args: any, theme: any, context: any) {
|
|
1959
|
-
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);
|
|
1960
3153
|
const command = typeof args.command === "string" ? args.command : "";
|
|
1961
3154
|
if (useCompactToolView(context.toolCallId, context.expanded)) {
|
|
1962
3155
|
const commandLines = command.split(/\r\n|\r|\n/);
|
|
@@ -1965,9 +3158,9 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1965
3158
|
const detail = omitted > 0
|
|
1966
3159
|
? `${firstLine} … (${omitted} more ${omitted === 1 ? "line" : "lines"})`
|
|
1967
3160
|
: firstLine;
|
|
1968
|
-
return call(theme,
|
|
3161
|
+
return call(theme, bashLabel, detail, context.state);
|
|
1969
3162
|
}
|
|
1970
|
-
return call(theme,
|
|
3163
|
+
return call(theme, bashLabel, command, context.state);
|
|
1971
3164
|
},
|
|
1972
3165
|
renderResult(toolResult: any, options: any, theme: any, context: any) {
|
|
1973
3166
|
const output = textContent(toolResult);
|
|
@@ -2002,13 +3195,14 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2002
3195
|
} as any);
|
|
2003
3196
|
|
|
2004
3197
|
const edit = createEditTool(cwd);
|
|
3198
|
+
const editLabel = edit.label || edit.name;
|
|
2005
3199
|
pi.registerTool({
|
|
2006
3200
|
...edit,
|
|
2007
3201
|
renderShell: "self",
|
|
2008
3202
|
renderCall(args: any, theme: any, context: any) {
|
|
2009
|
-
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);
|
|
2010
3204
|
const count = Array.isArray(args.edits) ? ` · ${args.edits.length} changes` : "";
|
|
2011
|
-
return call(theme,
|
|
3205
|
+
return call(theme, editLabel, `${args.path}${count}`, context.state);
|
|
2012
3206
|
},
|
|
2013
3207
|
renderResult(toolResult: any, options: any, theme: any, context: any) {
|
|
2014
3208
|
const output = textContent(toolResult);
|
|
@@ -2043,20 +3237,21 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2043
3237
|
} as any);
|
|
2044
3238
|
|
|
2045
3239
|
const write = createWriteTool(cwd);
|
|
3240
|
+
const writeLabel = write.label || write.name;
|
|
2046
3241
|
pi.registerTool({
|
|
2047
3242
|
...write,
|
|
2048
3243
|
renderShell: "self",
|
|
2049
3244
|
renderCall(args: any, theme: any, context: any) {
|
|
2050
|
-
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);
|
|
2051
3246
|
const content = typeof args.content === "string" ? args.content : "";
|
|
2052
3247
|
const path = String(args.path ?? "");
|
|
2053
3248
|
if (useCompactToolView(context.toolCallId, context.expanded)) {
|
|
2054
3249
|
const lines = content.replace(/\t/g, " ").split("\n");
|
|
2055
3250
|
while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
2056
3251
|
const total = lines.length;
|
|
2057
|
-
return call(theme,
|
|
3252
|
+
return call(theme, writeLabel, `${path} · ${total} ${total === 1 ? "line" : "lines"}`, context.state);
|
|
2058
3253
|
}
|
|
2059
|
-
return writeCall(theme, path, content, context.expanded, context.state);
|
|
3254
|
+
return writeCall(theme, writeLabel, path, content, context.expanded, context.state);
|
|
2060
3255
|
},
|
|
2061
3256
|
renderResult(toolResult: any, options: any, theme: any, context: any) {
|
|
2062
3257
|
const output = textContent(toolResult);
|
|
@@ -2074,14 +3269,15 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2074
3269
|
} as any);
|
|
2075
3270
|
|
|
2076
3271
|
const grep = createGrepTool(cwd);
|
|
3272
|
+
const grepLabel = grep.label || grep.name;
|
|
2077
3273
|
pi.registerTool({
|
|
2078
3274
|
...grep,
|
|
2079
3275
|
renderShell: "self",
|
|
2080
3276
|
renderCall(args: any, theme: any, context: any) {
|
|
2081
|
-
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);
|
|
2082
3278
|
const where = args.path ? ` · ${args.path}` : "";
|
|
2083
3279
|
const glob = args.glob ? ` · ${args.glob}` : "";
|
|
2084
|
-
return call(theme,
|
|
3280
|
+
return call(theme, grepLabel, `${args.pattern}${where}${glob}`, context.state);
|
|
2085
3281
|
},
|
|
2086
3282
|
renderResult(toolResult: any, options: any, theme: any, context: any) {
|
|
2087
3283
|
const output = textContent(toolResult);
|
|
@@ -2094,12 +3290,13 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2094
3290
|
} as any);
|
|
2095
3291
|
|
|
2096
3292
|
const find = createFindTool(cwd);
|
|
3293
|
+
const findLabel = find.label || find.name;
|
|
2097
3294
|
pi.registerTool({
|
|
2098
3295
|
...find,
|
|
2099
3296
|
renderShell: "self",
|
|
2100
3297
|
renderCall(args: any, theme: any, context: any) {
|
|
2101
|
-
if (hideCleanTool(context.toolCallId, context.expanded, context.executionStarted)) return cleanToolCall(theme,
|
|
2102
|
-
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);
|
|
2103
3300
|
},
|
|
2104
3301
|
renderResult(toolResult: any, options: any, theme: any, context: any) {
|
|
2105
3302
|
const output = textContent(toolResult);
|
|
@@ -2112,12 +3309,13 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2112
3309
|
} as any);
|
|
2113
3310
|
|
|
2114
3311
|
const ls = createLsTool(cwd);
|
|
3312
|
+
const lsLabel = ls.label || ls.name;
|
|
2115
3313
|
pi.registerTool({
|
|
2116
3314
|
...ls,
|
|
2117
3315
|
renderShell: "self",
|
|
2118
3316
|
renderCall(args: any, theme: any, context: any) {
|
|
2119
|
-
if (hideCleanTool(context.toolCallId, context.expanded, context.executionStarted)) return cleanToolCall(theme,
|
|
2120
|
-
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);
|
|
2121
3319
|
},
|
|
2122
3320
|
renderResult(toolResult: any, options: any, theme: any, context: any) {
|
|
2123
3321
|
const output = textContent(toolResult);
|