pi-pretty-tui 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -5
- package/README.zh-CN.md +10 -5
- package/extensions/activity-timeline.ts +77 -23
- package/extensions/index.ts +536 -215
- package/package.json +1 -1
package/extensions/index.ts
CHANGED
|
@@ -32,15 +32,17 @@ import { join } from "node:path";
|
|
|
32
32
|
import {
|
|
33
33
|
ActivityTimeline,
|
|
34
34
|
assistantMessageKey,
|
|
35
|
-
|
|
35
|
+
assistantTerminalState,
|
|
36
36
|
thinkingText,
|
|
37
37
|
visibleAssistantText,
|
|
38
38
|
type ActivityGroup,
|
|
39
39
|
type ActivityMember,
|
|
40
|
+
type ActivitySeverity,
|
|
40
41
|
} from "./activity-timeline.js";
|
|
41
42
|
|
|
42
43
|
type PrettyTuiMode = "full" | "compact" | "clean";
|
|
43
44
|
const CLEAN_TOOL_ACTIVITY_MIN_MS = 1000;
|
|
45
|
+
const TRANSIENT_UI_STATUS_MS = 10_000;
|
|
44
46
|
const SPECIALIZED_TOOL_NAMES = new Set(["read", "bash", "edit", "write", "grep", "find", "ls"]);
|
|
45
47
|
|
|
46
48
|
const stripAnsiBackgrounds = (value: string): string =>
|
|
@@ -95,14 +97,88 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
95
97
|
let currentTui: any;
|
|
96
98
|
let currentExtensionUi: any;
|
|
97
99
|
let changingAllToolsExpansion = false;
|
|
100
|
+
const latestActivityWidgetKey = "pretty-tui-latest-activity";
|
|
101
|
+
type LatestActivityStatus = {
|
|
102
|
+
title: string;
|
|
103
|
+
detail?: string;
|
|
104
|
+
severity?: TransientStatusSeverity;
|
|
105
|
+
};
|
|
106
|
+
// The editor-adjacent flash reports Pi UI state ("status") and pretty-tui's
|
|
107
|
+
// own UI outcomes, so it has its own severities. Activity members only ever
|
|
108
|
+
// carry info, warning, or error.
|
|
109
|
+
type TransientStatusSeverity = "status" | "success" | "error";
|
|
110
|
+
let latestActivityStatus: LatestActivityStatus | undefined;
|
|
111
|
+
let latestActivityWidgetInstalled = false;
|
|
112
|
+
let latestActivityStatusTimer: ReturnType<typeof setTimeout> | undefined;
|
|
113
|
+
let latestActivityStatusGeneration = 0;
|
|
114
|
+
const renderLatestActivityStatus = (status: LatestActivityStatus, width: number, theme: any): string => {
|
|
115
|
+
const severity = status.severity ?? "status";
|
|
116
|
+
const icon = severity === "error" ? "✕" : severity === "success" ? "✓" : "◇";
|
|
117
|
+
const color = severity === "error"
|
|
118
|
+
? "error"
|
|
119
|
+
: severity === "success"
|
|
120
|
+
? "success"
|
|
121
|
+
: "dim";
|
|
122
|
+
const detail = status.detail?.trim() ? ` — ${status.detail.trim()}` : "";
|
|
123
|
+
return truncateToWidth(` ${theme.fg(color, `${icon} ${status.title}`)}${theme.fg("dim", detail)}`, Math.max(1, width), "…");
|
|
124
|
+
};
|
|
125
|
+
const refreshLatestActivityWidget = () => {
|
|
126
|
+
if (!currentExtensionUi?.setWidget) return;
|
|
127
|
+
if (renderMode !== "clean" || !latestActivityStatus) {
|
|
128
|
+
if (latestActivityWidgetInstalled) {
|
|
129
|
+
currentExtensionUi.setWidget(latestActivityWidgetKey, undefined);
|
|
130
|
+
latestActivityWidgetInstalled = false;
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (latestActivityWidgetInstalled) {
|
|
135
|
+
currentTui?.requestRender?.();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
currentExtensionUi.setWidget(
|
|
139
|
+
latestActivityWidgetKey,
|
|
140
|
+
(_tui: any, theme: any) => ({
|
|
141
|
+
render: (width: number) => latestActivityStatus
|
|
142
|
+
? [renderLatestActivityStatus(latestActivityStatus, width, theme)]
|
|
143
|
+
: [],
|
|
144
|
+
invalidate() {},
|
|
145
|
+
}),
|
|
146
|
+
{ placement: "aboveEditor" },
|
|
147
|
+
);
|
|
148
|
+
latestActivityWidgetInstalled = true;
|
|
149
|
+
};
|
|
150
|
+
const clearLatestActivityStatus = () => {
|
|
151
|
+
latestActivityStatusGeneration++;
|
|
152
|
+
if (latestActivityStatusTimer) clearTimeout(latestActivityStatusTimer);
|
|
153
|
+
latestActivityStatusTimer = undefined;
|
|
154
|
+
latestActivityStatus = undefined;
|
|
155
|
+
if (latestActivityWidgetInstalled) {
|
|
156
|
+
currentExtensionUi?.setWidget?.(latestActivityWidgetKey, undefined);
|
|
157
|
+
latestActivityWidgetInstalled = false;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
const setLatestActivityStatus = (status: LatestActivityStatus) => {
|
|
161
|
+
const generation = ++latestActivityStatusGeneration;
|
|
162
|
+
if (latestActivityStatusTimer) clearTimeout(latestActivityStatusTimer);
|
|
163
|
+
latestActivityStatus = status;
|
|
164
|
+
refreshLatestActivityWidget();
|
|
165
|
+
latestActivityStatusTimer = setTimeout(() => {
|
|
166
|
+
if (generation !== latestActivityStatusGeneration) return;
|
|
167
|
+
clearLatestActivityStatus();
|
|
168
|
+
currentTui?.requestRender?.();
|
|
169
|
+
}, TRANSIENT_UI_STATUS_MS);
|
|
170
|
+
};
|
|
98
171
|
const activityTimeline = new ActivityTimeline();
|
|
99
172
|
const revealedActivityGroups = new Set<string>();
|
|
100
173
|
const expandedThinkingMembers = new Set<string>();
|
|
101
174
|
const thinkingComponents = new Map<string, any>();
|
|
102
175
|
const activityFallbackThemes = new Map<string, any>();
|
|
103
176
|
const activityUpdateComponents = new Map<string, Component>();
|
|
104
|
-
const
|
|
105
|
-
|
|
177
|
+
const activityUpdateMarkdownComponents = new Map<string, {
|
|
178
|
+
content: string;
|
|
179
|
+
theme: any;
|
|
180
|
+
component: Markdown;
|
|
181
|
+
}>();
|
|
106
182
|
let activityUpdateSequence = 0;
|
|
107
183
|
const assistantBoundaryKeys = new Set<string>();
|
|
108
184
|
const cleanCompactToolCallIds = new Set<string>();
|
|
@@ -113,6 +189,20 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
113
189
|
const cleanToolNames = new Map<string, string>();
|
|
114
190
|
let renderCleanGroupSummary = (_lastToolCallId: string, _width: number): string[] => [];
|
|
115
191
|
|
|
192
|
+
// Markdown enhancements are active only while pi-pretty-tui renders the main
|
|
193
|
+
// conversation transcript. Plugin overlays also use Pi's Markdown class, so
|
|
194
|
+
// a global always-on prototype patch would leak transcript styling into them.
|
|
195
|
+
let transcriptMarkdownDepth = 0;
|
|
196
|
+
const withTranscriptMarkdown = <T>(callback: () => T): T => {
|
|
197
|
+
transcriptMarkdownDepth++;
|
|
198
|
+
try {
|
|
199
|
+
return callback();
|
|
200
|
+
} finally {
|
|
201
|
+
transcriptMarkdownDepth--;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
const transcriptMarkdownActive = () => transcriptMarkdownDepth > 0;
|
|
205
|
+
|
|
116
206
|
const setCleanGroupMembers = (lastToolCallId: string, toolCallIds: string[]) => {
|
|
117
207
|
cleanGroupToolCallIds.set(lastToolCallId, toolCallIds);
|
|
118
208
|
for (const toolCallId of toolCallIds) cleanToolCallGroupOwners.set(toolCallId, lastToolCallId);
|
|
@@ -167,11 +257,17 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
167
257
|
};
|
|
168
258
|
|
|
169
259
|
const defaultActivityTheme = () => {
|
|
260
|
+
if (currentExtensionUi?.theme?.fg) return currentExtensionUi.theme;
|
|
170
261
|
const markdownTheme = getMarkdownTheme();
|
|
171
262
|
return {
|
|
172
263
|
fg: (color: string, text: string) => {
|
|
173
264
|
if (color === "accent") return markdownTheme.listBullet(text);
|
|
174
|
-
|
|
265
|
+
// Session UI normally supplies Pi's active Theme above. Keep portable
|
|
266
|
+
// semantic fallbacks for early startup and isolated renderer tests.
|
|
267
|
+
if (color === "warning") return `\x1b[33m${text}\x1b[39m`;
|
|
268
|
+
if (color === "error") return `\x1b[31m${text}\x1b[39m`;
|
|
269
|
+
if (color === "success") return `\x1b[32m${text}\x1b[39m`;
|
|
270
|
+
if (color === "syntaxComment") return markdownTheme.codeBlock(text);
|
|
175
271
|
if (color === "thinkingLow") return markdownTheme.link(text);
|
|
176
272
|
if (color === "dim" || color === "muted" || color === "thinkingText") {
|
|
177
273
|
return markdownTheme.quote(text);
|
|
@@ -204,6 +300,12 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
204
300
|
const activityGroupRevealed = (group: ActivityGroup): boolean =>
|
|
205
301
|
cleanToolsExpanded || revealedActivityGroups.has(group.id);
|
|
206
302
|
|
|
303
|
+
// A group is revealable once it owns anything renderable beneath its parent
|
|
304
|
+
// row. Thinking counts on its own: a tool-free turn still gets a parent row
|
|
305
|
+
// with its Thought as the child member.
|
|
306
|
+
const activityGroupCollapsible = (group: ActivityGroup): boolean =>
|
|
307
|
+
activityTimeline.hasWork(group);
|
|
308
|
+
|
|
207
309
|
const activityMemberPosition = (group: ActivityGroup, member: ActivityMember) => ({
|
|
208
310
|
first: group.members[0]?.id === member.id,
|
|
209
311
|
last: group.members[group.members.length - 1]?.id === member.id,
|
|
@@ -238,6 +340,50 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
238
340
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
239
341
|
.join(" ") || "Update";
|
|
240
342
|
|
|
343
|
+
const renderActivityUpdateMarkdown = (
|
|
344
|
+
member: ActivityMember,
|
|
345
|
+
width: number,
|
|
346
|
+
theme: any,
|
|
347
|
+
): string[] => {
|
|
348
|
+
const content = member.updateContent ?? "";
|
|
349
|
+
if (!content) return [];
|
|
350
|
+
let cached = activityUpdateMarkdownComponents.get(member.id);
|
|
351
|
+
if (!cached || cached.content !== content || cached.theme !== theme) {
|
|
352
|
+
cached = {
|
|
353
|
+
content,
|
|
354
|
+
theme,
|
|
355
|
+
component: new Markdown(
|
|
356
|
+
content,
|
|
357
|
+
0,
|
|
358
|
+
0,
|
|
359
|
+
getMarkdownTheme(),
|
|
360
|
+
{ color: (text: string) => theme.fg("muted", text) },
|
|
361
|
+
),
|
|
362
|
+
};
|
|
363
|
+
activityUpdateMarkdownComponents.set(member.id, cached);
|
|
364
|
+
}
|
|
365
|
+
const lines = withTranscriptMarkdown(() => cached.component.render(Math.max(1, width)));
|
|
366
|
+
while (lines.length > 0 && stripTerminalSequences(lines[lines.length - 1]).trim() === "") {
|
|
367
|
+
lines.pop();
|
|
368
|
+
}
|
|
369
|
+
return lines;
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const activityUpdateStyle = (member: ActivityMember, theme: any) => {
|
|
373
|
+
const severity = member.updateSeverity ?? "info";
|
|
374
|
+
const styles: Record<ActivitySeverity, { icon: string; color: string }> = {
|
|
375
|
+
info: { icon: "◇", color: "accent" },
|
|
376
|
+
warning: { icon: "⚠", color: "warning" },
|
|
377
|
+
error: { icon: "✕", color: "error" },
|
|
378
|
+
};
|
|
379
|
+
const style = styles[severity];
|
|
380
|
+
return {
|
|
381
|
+
severity,
|
|
382
|
+
icon: theme.fg(style.color, `${style.icon} `),
|
|
383
|
+
title: (text: string) => theme.fg(style.color, theme.bold(text)),
|
|
384
|
+
};
|
|
385
|
+
};
|
|
386
|
+
|
|
241
387
|
const renderActivityUpdate = (
|
|
242
388
|
group: ActivityGroup,
|
|
243
389
|
member: ActivityMember,
|
|
@@ -247,10 +393,11 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
247
393
|
if (renderMode !== "clean" || !activityGroupRevealed(group)) return [];
|
|
248
394
|
const { prefix, continuation, childWidth } = activityTreeStyle(group, member, width, theme);
|
|
249
395
|
const title = truncateToWidth(member.updateTitle ?? "Update", Math.max(1, childWidth - 2), "…");
|
|
250
|
-
const
|
|
396
|
+
const style = activityUpdateStyle(member, theme);
|
|
397
|
+
const lines = [style.icon + style.title(title)];
|
|
251
398
|
if (member.updateContent) {
|
|
252
399
|
const detailWidth = Math.max(1, childWidth - visibleWidth(" │ "));
|
|
253
|
-
const detailLines =
|
|
400
|
+
const detailLines = renderActivityUpdateMarkdown(member, detailWidth, theme);
|
|
254
401
|
lines.push(...detailLines.map((line, index) =>
|
|
255
402
|
theme.fg("dim", index === detailLines.length - 1 ? " └ " : " │ ") + line
|
|
256
403
|
));
|
|
@@ -267,10 +414,11 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
267
414
|
const renderPendingActivityUpdate = (member: ActivityMember, width: number): string[] => {
|
|
268
415
|
const theme = defaultActivityTheme();
|
|
269
416
|
const title = truncateToWidth(member.updateTitle ?? "Update", Math.max(1, width - 2), "…");
|
|
270
|
-
const
|
|
417
|
+
const style = activityUpdateStyle(member, theme);
|
|
418
|
+
const lines = [style.icon + style.title(title)];
|
|
271
419
|
if (member.updateContent) {
|
|
272
420
|
const detailWidth = Math.max(1, width - visibleWidth(" │ "));
|
|
273
|
-
const details =
|
|
421
|
+
const details = renderActivityUpdateMarkdown(member, detailWidth, theme);
|
|
274
422
|
lines.push(...details.map((line, index) =>
|
|
275
423
|
theme.fg("dim", index === details.length - 1 ? " └ " : " │ ") + line
|
|
276
424
|
));
|
|
@@ -278,13 +426,28 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
278
426
|
return ["", ...lines.map((line) => truncateToWidth(line, Math.max(1, width), ""))];
|
|
279
427
|
};
|
|
280
428
|
|
|
429
|
+
const renderActivityUpdateProjection = (
|
|
430
|
+
group: ActivityGroup,
|
|
431
|
+
member: ActivityMember,
|
|
432
|
+
width: number,
|
|
433
|
+
): string[] => {
|
|
434
|
+
if (!activityGroupCollapsible(group)) return renderPendingActivityUpdate(member, width);
|
|
435
|
+
const position = activityMemberPosition(group, member);
|
|
436
|
+
if (!activityGroupRevealed(group)) {
|
|
437
|
+
return position.first
|
|
438
|
+
? ["", ...renderActivityGroupSummary(group, width, true)]
|
|
439
|
+
: [];
|
|
440
|
+
}
|
|
441
|
+
return renderActivityUpdate(group, member, width);
|
|
442
|
+
};
|
|
443
|
+
|
|
281
444
|
const handleActivityUpdateMouse = (
|
|
282
445
|
group: ActivityGroup,
|
|
283
446
|
member: ActivityMember,
|
|
284
447
|
event: any,
|
|
285
448
|
) => {
|
|
286
449
|
const isLeftClick = event.type === "click" && event.button === "left";
|
|
287
|
-
if (!isLeftClick || group
|
|
450
|
+
if (!isLeftClick || !activityGroupCollapsible(group)) return undefined;
|
|
288
451
|
const position = activityMemberPosition(group, member);
|
|
289
452
|
if (!activityGroupRevealed(group)) {
|
|
290
453
|
if (!position.first) return undefined;
|
|
@@ -306,6 +469,8 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
306
469
|
cleanToolComponents.get(child.toolCallId)?.updateDisplay?.();
|
|
307
470
|
} else if (child.kind === "thinking" && child.messageKey) {
|
|
308
471
|
thinkingComponents.get(child.messageKey)?.invalidate?.();
|
|
472
|
+
} else if (child.kind === "update") {
|
|
473
|
+
activityUpdateComponents.get(child.id)?.invalidate?.();
|
|
309
474
|
}
|
|
310
475
|
}
|
|
311
476
|
currentTui?.requestRender?.();
|
|
@@ -399,6 +564,8 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
399
564
|
revealedActivityGroups.clear();
|
|
400
565
|
expandedThinkingMembers.clear();
|
|
401
566
|
renderMode = requested;
|
|
567
|
+
if (renderMode === "clean") refreshLatestActivityWidget();
|
|
568
|
+
else clearLatestActivityStatus();
|
|
402
569
|
for (const component of cleanToolComponents.values()) component.updateDisplay?.();
|
|
403
570
|
for (const component of thinkingComponents.values()) component.invalidate?.();
|
|
404
571
|
try {
|
|
@@ -415,20 +582,6 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
415
582
|
// render patches or override built-in tools until the next enabled reload.
|
|
416
583
|
if (!activeForSession) return;
|
|
417
584
|
|
|
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
|
-
|
|
432
585
|
// Give Pi's main prompt editor a complete rounded frame. Render the native
|
|
433
586
|
// editor at a two-column narrower width so cursor layout, wrapping, IME, and
|
|
434
587
|
// autocomplete remain native, then add one themed border column per side.
|
|
@@ -615,14 +768,31 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
615
768
|
.join("\n")
|
|
616
769
|
: "";
|
|
617
770
|
|
|
771
|
+
// Delegates to the timeline so the precondition is stated once. The `??` is
|
|
772
|
+
// deliberate: the callee may still decline, and a dropped notification must
|
|
773
|
+
// never throw inside a third-party extension's ctx.ui.notify() call.
|
|
774
|
+
const hasWorkActivity = (group: ActivityGroup | undefined): boolean =>
|
|
775
|
+
activityTimeline.hasWork(group);
|
|
776
|
+
|
|
618
777
|
const addPersistentActivityUpdate = (message: any): ActivityMember => {
|
|
619
778
|
const key = customMessageKey(message);
|
|
620
779
|
const title = humanizeCustomType(message?.customType ?? "update");
|
|
621
780
|
const content = customMessageText(message);
|
|
622
|
-
|
|
623
|
-
|
|
781
|
+
const existingGroup = activityTimeline.currentGroup();
|
|
782
|
+
const joinsWork = hasWorkActivity(existingGroup);
|
|
783
|
+
const member = joinsWork
|
|
784
|
+
? activityTimeline.addUpdate(key, title, content, true, "info") ??
|
|
785
|
+
activityTimeline.addPendingUpdate(key, title, content, true, "info")
|
|
786
|
+
: activityTimeline.addPendingUpdate(key, title, content, true, "info");
|
|
787
|
+
// A displayed custom message without current model/tool work is an
|
|
788
|
+
// independent durable update, never a pending member of the next run.
|
|
789
|
+
if (!hasWorkActivity(existingGroup)) activityTimeline.boundary();
|
|
790
|
+
return member;
|
|
624
791
|
};
|
|
625
792
|
|
|
793
|
+
const nativeCustomBoundaryKeys = new Set<string>();
|
|
794
|
+
let closeCleanAtNativeCustomBoundary = () => activityTimeline.boundary();
|
|
795
|
+
let closeCleanAtStandaloneAlert = () => activityTimeline.boundary();
|
|
626
796
|
const customMessagePrototype = CustomMessageComponent.prototype as any;
|
|
627
797
|
const customMessagePatchKey = Symbol.for("pretty-tui.clean-custom-message");
|
|
628
798
|
if (!customMessagePrototype[customMessagePatchKey]) {
|
|
@@ -631,9 +801,13 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
631
801
|
const releaseSemanticCustomMessage = (component: any): boolean => {
|
|
632
802
|
if (!component.customRenderer) return false;
|
|
633
803
|
const key = customMessageKey(component.message);
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
804
|
+
if (!nativeCustomBoundaryKeys.has(key)) {
|
|
805
|
+
nativeCustomBoundaryKeys.add(key);
|
|
806
|
+
const member = activityTimeline.memberForUpdate(key);
|
|
807
|
+
if (member) activityUpdateComponents.delete(member.id);
|
|
808
|
+
activityTimeline.removeUpdate(key);
|
|
809
|
+
closeCleanAtNativeCustomBoundary();
|
|
810
|
+
}
|
|
637
811
|
return true;
|
|
638
812
|
};
|
|
639
813
|
const patchedCustomRender = function (this: any, width: number): string[] {
|
|
@@ -643,37 +817,19 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
643
817
|
const message = this.message;
|
|
644
818
|
const key = customMessageKey(message);
|
|
645
819
|
let member = activityTimeline.memberForUpdate(key);
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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
|
-
}
|
|
820
|
+
// Extension-injected custom messages do not always emit Pi's model
|
|
821
|
+
// message_start event. Claim them at the component boundary so live
|
|
822
|
+
// rendering matches transcript restoration instead of failing open to
|
|
823
|
+
// the purple native box until the next reload.
|
|
824
|
+
if (!member && renderMode === "clean") {
|
|
825
|
+
member = addPersistentActivityUpdate(message);
|
|
663
826
|
}
|
|
664
827
|
const group = member ? activityTimeline.groupForMember(member.id) : undefined;
|
|
665
828
|
if (renderMode !== "clean" || !member || !group) {
|
|
666
829
|
return originalCustomRender.call(this, width);
|
|
667
830
|
}
|
|
668
831
|
activityUpdateComponents.set(member.id, this);
|
|
669
|
-
|
|
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);
|
|
832
|
+
return renderActivityUpdateProjection(group, member, width);
|
|
677
833
|
};
|
|
678
834
|
const patchedCustomHandleMouse = function (this: any, event: any) {
|
|
679
835
|
if (releaseSemanticCustomMessage(this)) {
|
|
@@ -684,7 +840,6 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
684
840
|
if (renderMode !== "clean" || !member || !group) {
|
|
685
841
|
return originalCustomHandleMouse?.call(this, event);
|
|
686
842
|
}
|
|
687
|
-
if (group.toolCallIds.length === 0) return undefined;
|
|
688
843
|
return handleActivityUpdateMouse(group, member, event);
|
|
689
844
|
};
|
|
690
845
|
customMessagePrototype[customMessagePatchKey] = {
|
|
@@ -728,14 +883,38 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
728
883
|
const cleanThinkingRenderCacheKey = Symbol("pretty-tui.clean-thinking-render-cache");
|
|
729
884
|
const cleanThinkingDetailKey = Symbol("pretty-tui.clean-thinking-detail");
|
|
730
885
|
|
|
886
|
+
const assistantTerminalUpdate = (message: any): ActivityMember | undefined => {
|
|
887
|
+
if (!assistantTerminalState(message)) return undefined;
|
|
888
|
+
const hasToolCalls = Array.isArray(message?.content) &&
|
|
889
|
+
message.content.some((item: any) => item?.type === "toolCall");
|
|
890
|
+
// Failed tools already preserve the error in their native result details;
|
|
891
|
+
// avoid duplicating that payload as a second durable member.
|
|
892
|
+
if (hasToolCalls) return undefined;
|
|
893
|
+
const key = `assistant-terminal:${assistantMessageKey(message)}`;
|
|
894
|
+
const existing = activityTimeline.memberForUpdate(key);
|
|
895
|
+
if (existing) return existing;
|
|
896
|
+
const stopReason = message?.stopReason;
|
|
897
|
+
const severity: ActivitySeverity = stopReason === "aborted" ? "warning" : "error";
|
|
898
|
+
const title = stopReason === "length"
|
|
899
|
+
? "Response truncated"
|
|
900
|
+
: stopReason === "aborted"
|
|
901
|
+
? "Operation aborted"
|
|
902
|
+
: "Error";
|
|
903
|
+
const content = String(message?.errorMessage ?? "").trim() ||
|
|
904
|
+
(stopReason === "length" ? "Response was truncated before completion." : "Unknown error");
|
|
905
|
+
activityTimeline.boundary();
|
|
906
|
+
const member = activityTimeline.addPendingUpdate(key, title, content, true, severity);
|
|
907
|
+
activityTimeline.boundary();
|
|
908
|
+
return member;
|
|
909
|
+
};
|
|
910
|
+
|
|
731
911
|
const eligibleThinking = (message: any) =>
|
|
732
912
|
Array.isArray(message?.content) &&
|
|
733
913
|
message.content.some(
|
|
734
914
|
(item: any) => item?.type === "thinking" &&
|
|
735
915
|
typeof item.thinking === "string" &&
|
|
736
916
|
item.thinking.length > 0,
|
|
737
|
-
)
|
|
738
|
-
!assistantSystemBoundary(message);
|
|
917
|
+
);
|
|
739
918
|
|
|
740
919
|
const refreshGroup = (group: ActivityGroup) => {
|
|
741
920
|
for (const member of group.members) {
|
|
@@ -743,11 +922,41 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
743
922
|
cleanToolComponents.get(member.toolCallId)?.updateDisplay?.();
|
|
744
923
|
} else if (member.kind === "thinking" && member.messageKey) {
|
|
745
924
|
thinkingComponents.get(member.messageKey)?.invalidate?.();
|
|
925
|
+
} else if (member.kind === "update") {
|
|
926
|
+
activityUpdateComponents.get(member.id)?.invalidate?.();
|
|
746
927
|
}
|
|
747
928
|
}
|
|
748
929
|
currentTui?.requestRender?.();
|
|
749
930
|
};
|
|
750
931
|
|
|
932
|
+
// Shared detail projection for a Thought, used both as a member of a tool
|
|
933
|
+
// group and as a standalone row. The cache records its theme because the
|
|
934
|
+
// same component can switch between the Markdown fallback and a real tool
|
|
935
|
+
// theme once the first tool of the group is constructed.
|
|
936
|
+
const thinkingDetailLines = (
|
|
937
|
+
owner: any,
|
|
938
|
+
text: string,
|
|
939
|
+
theme: any,
|
|
940
|
+
childWidth: number,
|
|
941
|
+
): string[] => {
|
|
942
|
+
let detail = owner[cleanThinkingDetailKey];
|
|
943
|
+
if (!detail || detail.text !== text || detail.theme !== theme) {
|
|
944
|
+
detail = {
|
|
945
|
+
text,
|
|
946
|
+
theme,
|
|
947
|
+
component: new Markdown(text, 0, 0, owner.markdownTheme, {
|
|
948
|
+
color: (line: string) => theme.fg("thinkingText", line),
|
|
949
|
+
italic: true,
|
|
950
|
+
}),
|
|
951
|
+
};
|
|
952
|
+
owner[cleanThinkingDetailKey] = detail;
|
|
953
|
+
}
|
|
954
|
+
const lines: string[] = detail.component.render(Math.max(1, childWidth));
|
|
955
|
+
return lines.map((line, index) =>
|
|
956
|
+
theme.fg("dim", index === lines.length - 1 ? " └ " : " │ ") + line
|
|
957
|
+
);
|
|
958
|
+
};
|
|
959
|
+
|
|
751
960
|
const patchedUpdateContent = function (this: any, message: any, isStreaming = this.isStreaming) {
|
|
752
961
|
this[originalMessageKey] = message;
|
|
753
962
|
this[renderedModeKey] = renderMode;
|
|
@@ -790,8 +999,14 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
790
999
|
) {
|
|
791
1000
|
patchedUpdateContent.call(this, message, this.isStreaming);
|
|
792
1001
|
}
|
|
793
|
-
if (renderMode !== "clean"
|
|
794
|
-
|
|
1002
|
+
if (renderMode !== "clean") return originalRender.call(this, width);
|
|
1003
|
+
const terminalMember = assistantTerminalUpdate(message);
|
|
1004
|
+
if (!eligibleThinking(message)) {
|
|
1005
|
+
if (!terminalMember) return originalRender.call(this, width);
|
|
1006
|
+
const terminalGroup = activityTimeline.groupForMember(terminalMember.id);
|
|
1007
|
+
if (!terminalGroup) return originalRender.call(this, width);
|
|
1008
|
+
activityUpdateComponents.set(terminalMember.id, this);
|
|
1009
|
+
return renderActivityUpdateProjection(terminalGroup, terminalMember, width);
|
|
795
1010
|
}
|
|
796
1011
|
|
|
797
1012
|
const key = assistantMessageKey(message);
|
|
@@ -803,25 +1018,29 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
803
1018
|
// assistant text is never lost.
|
|
804
1019
|
return originalRender.call(this, width);
|
|
805
1020
|
}
|
|
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
1021
|
thinkingComponents.set(key, this);
|
|
814
1022
|
if (!activityFallbackThemes.has(group.id)) {
|
|
815
1023
|
activityFallbackThemes.set(group.id, defaultActivityTheme());
|
|
816
1024
|
}
|
|
817
1025
|
const position = activityMemberPosition(group, member);
|
|
818
1026
|
const visibleLines = visibleAssistantText(message) ? originalRender.call(this, width) : [];
|
|
1027
|
+
// Pi's native renderer prints its own error line for messages with no
|
|
1028
|
+
// tool calls, so this component only adds the styled alert card when no
|
|
1029
|
+
// visible text can carry it.
|
|
1030
|
+
const terminalLines = terminalMember && visibleLines.length === 0
|
|
1031
|
+
? renderPendingActivityUpdate(terminalMember, width)
|
|
1032
|
+
: [];
|
|
1033
|
+
const appendTail = (activityLines: string[]) => [
|
|
1034
|
+
...activityLines,
|
|
1035
|
+
...terminalLines,
|
|
1036
|
+
...visibleLines,
|
|
1037
|
+
];
|
|
819
1038
|
const revealed = activityGroupRevealed(group);
|
|
820
1039
|
if (!revealed) {
|
|
821
1040
|
const activityLines = position.first
|
|
822
1041
|
? ["", ...renderActivityGroupSummary(group, width, true)]
|
|
823
1042
|
: [];
|
|
824
|
-
return
|
|
1043
|
+
return appendTail(activityLines);
|
|
825
1044
|
}
|
|
826
1045
|
|
|
827
1046
|
const groupTheme = activityGroupTheme(group);
|
|
@@ -831,10 +1050,16 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
831
1050
|
width,
|
|
832
1051
|
groupTheme,
|
|
833
1052
|
);
|
|
1053
|
+
// Clean mode owns thought disclosure: revealing a group shows the thought
|
|
1054
|
+
// member, while only a member click (or Ctrl+O) reveals its full text.
|
|
834
1055
|
const expanded = cleanToolsExpanded || expandedThinkingMembers.has(member.id);
|
|
835
1056
|
const settledOwner = position.first ? activityGroupOwner(group) : undefined;
|
|
836
|
-
|
|
837
|
-
|
|
1057
|
+
// A group without tools has no durable summary, so its own boundary is
|
|
1058
|
+
// the signal that it finished.
|
|
1059
|
+
const stableSummary = !position.first || (
|
|
1060
|
+
settledOwner
|
|
1061
|
+
? Boolean(settledSummaries.has(settledOwner) && !toolActivityHolds.has(settledOwner))
|
|
1062
|
+
: activityTimeline.isSettled(group.id)
|
|
838
1063
|
);
|
|
839
1064
|
const cacheable = !expanded && !this.isStreaming && stableSummary;
|
|
840
1065
|
const cached = cacheable ? this[cleanThinkingRenderCacheKey] : undefined;
|
|
@@ -848,7 +1073,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
848
1073
|
// Only the stable activity projection is cached. Visible assistant
|
|
849
1074
|
// text belongs outside the collapsible group and must be appended on
|
|
850
1075
|
// every render, including cache hits.
|
|
851
|
-
return
|
|
1076
|
+
return appendTail(cached.lines);
|
|
852
1077
|
}
|
|
853
1078
|
const stateLabel = this.isStreaming ? "thinking" : "thought";
|
|
854
1079
|
const label = truncateToWidth(stateLabel, Math.max(1, childWidth - 2), "…");
|
|
@@ -856,28 +1081,11 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
856
1081
|
groupTheme.fg("toolTitle", groupTheme.bold(label));
|
|
857
1082
|
let contentLines: string[] = [header];
|
|
858
1083
|
if (expanded) {
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
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
|
|
1084
|
+
contentLines.push(...thinkingDetailLines(
|
|
1085
|
+
this,
|
|
1086
|
+
member.thinking ?? thinkingText(message),
|
|
1087
|
+
groupTheme,
|
|
1088
|
+
Math.max(1, childWidth - visibleWidth(" │ ")),
|
|
881
1089
|
));
|
|
882
1090
|
}
|
|
883
1091
|
const decorated = contentLines.map((line, index) =>
|
|
@@ -896,7 +1104,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
896
1104
|
lines: output,
|
|
897
1105
|
};
|
|
898
1106
|
}
|
|
899
|
-
return
|
|
1107
|
+
return appendTail(output);
|
|
900
1108
|
};
|
|
901
1109
|
|
|
902
1110
|
const patchedRender = function (this: any, width: number): string[] {
|
|
@@ -905,14 +1113,20 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
905
1113
|
|
|
906
1114
|
const handleAssistantTranscriptMouse = function (this: any, event: any) {
|
|
907
1115
|
const message = this[originalMessageKey] ?? this.lastMessage;
|
|
908
|
-
if (renderMode !== "clean"
|
|
909
|
-
|
|
1116
|
+
if (renderMode !== "clean") return originalHandleMouse.call(this, event);
|
|
1117
|
+
if (!eligibleThinking(message)) {
|
|
1118
|
+
const terminalMember = assistantTerminalUpdate(message);
|
|
1119
|
+
const terminalGroup = terminalMember
|
|
1120
|
+
? activityTimeline.groupForMember(terminalMember.id)
|
|
1121
|
+
: undefined;
|
|
1122
|
+
return terminalMember && terminalGroup && activityGroupCollapsible(terminalGroup)
|
|
1123
|
+
? handleActivityUpdateMouse(terminalGroup, terminalMember, event)
|
|
1124
|
+
: originalHandleMouse.call(this, event);
|
|
910
1125
|
}
|
|
911
1126
|
const key = assistantMessageKey(message);
|
|
912
1127
|
const member = activityTimeline.memberForThinking(key);
|
|
913
1128
|
const group = member ? activityTimeline.groupForMember(member.id) : undefined;
|
|
914
1129
|
if (!member || !group) return originalHandleMouse.call(this, event);
|
|
915
|
-
if (group.toolCallIds.length === 0) return originalHandleMouse.call(this, event);
|
|
916
1130
|
const position = activityMemberPosition(group, member);
|
|
917
1131
|
const isLeftClick = event.type === "click" && event.button === "left";
|
|
918
1132
|
|
|
@@ -1027,53 +1241,42 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1027
1241
|
];
|
|
1028
1242
|
};
|
|
1029
1243
|
|
|
1030
|
-
const
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
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
|
-
}
|
|
1244
|
+
const appendRuntimeActivityUpdate = (
|
|
1245
|
+
mode: any,
|
|
1246
|
+
message: string,
|
|
1247
|
+
severity: ActivitySeverity,
|
|
1248
|
+
): boolean => {
|
|
1249
|
+
const [firstLine, ...remainingLines] = String(message).split("\n");
|
|
1250
|
+
const fallbackTitle = severity === "warning"
|
|
1251
|
+
? "Warning"
|
|
1252
|
+
: severity === "error"
|
|
1253
|
+
? "Error"
|
|
1254
|
+
: "Info";
|
|
1255
|
+
const title = firstLine || fallbackTitle;
|
|
1256
|
+
const content = remainingLines.join("\n");
|
|
1257
|
+
// Warnings and errors are standalone hard boundaries; only ordinary notices
|
|
1258
|
+
// join the work in progress.
|
|
1259
|
+
const standalone = severity === "warning" || severity === "error";
|
|
1260
|
+
const updateKey = `runtime:${severity}:${++activityUpdateSequence}`;
|
|
1261
|
+
if (standalone) closeCleanAtStandaloneAlert();
|
|
1262
|
+
const existingGroup = activityTimeline.currentGroup();
|
|
1263
|
+
const joinsWork = !standalone && hasWorkActivity(existingGroup);
|
|
1264
|
+
const member = joinsWork
|
|
1265
|
+
? activityTimeline.addUpdate(updateKey, title, content, false, severity) ??
|
|
1266
|
+
activityTimeline.addPendingUpdate(updateKey, title, content, false, severity)
|
|
1267
|
+
: activityTimeline.addPendingUpdate(updateKey, title, content, false, severity);
|
|
1268
|
+
if (!joinsWork) activityTimeline.boundary();
|
|
1269
|
+
const group = activityTimeline.groupForMember(member.id);
|
|
1270
|
+
if (!group || !mode.chatContainer?.addChild) return false;
|
|
1271
|
+
const component: Component = {
|
|
1272
|
+
render: (width: number) => renderActivityUpdateProjection(group, member, width),
|
|
1273
|
+
invalidate() {},
|
|
1274
|
+
handleMouse: (event: any) => handleActivityUpdateMouse(group, member, event),
|
|
1275
|
+
} as Component;
|
|
1276
|
+
activityUpdateComponents.set(member.id, component);
|
|
1277
|
+
mode.chatContainer.addChild(component);
|
|
1278
|
+
mode.ui?.requestRender?.();
|
|
1279
|
+
return true;
|
|
1077
1280
|
};
|
|
1078
1281
|
|
|
1079
1282
|
const interactiveModePrototype = InteractiveMode.prototype as any;
|
|
@@ -1083,6 +1286,10 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1083
1286
|
const originalRenderSessionEntries = interactiveModePrototype.renderSessionEntries;
|
|
1084
1287
|
const originalSwitchTuiMode = interactiveModePrototype.switchTuiMode;
|
|
1085
1288
|
const originalShowExtensionNotify = interactiveModePrototype.showExtensionNotify;
|
|
1289
|
+
const originalToggleThinkingBlockVisibility = interactiveModePrototype.toggleThinkingBlockVisibility;
|
|
1290
|
+
const originalShowStatus = interactiveModePrototype.showStatus;
|
|
1291
|
+
const originalShowWarning = interactiveModePrototype.showWarning;
|
|
1292
|
+
const originalShowError = interactiveModePrototype.showError;
|
|
1086
1293
|
const patchedSetToolsExpanded = function (this: any, expanded: boolean) {
|
|
1087
1294
|
cleanToolsExpanded = expanded;
|
|
1088
1295
|
changingAllToolsExpansion = true;
|
|
@@ -1107,7 +1314,6 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1107
1314
|
// while Pi's live compaction UI appends it chronologically. Keep reloads
|
|
1108
1315
|
// and transcript rebuilds consistent with that live presentation.
|
|
1109
1316
|
const orderedEntries = orderContextEntriesForTranscript(entries);
|
|
1110
|
-
indexDisplayedCustomUpdateHints(orderedEntries);
|
|
1111
1317
|
return originalRenderSessionEntries.call(this, orderedEntries, options);
|
|
1112
1318
|
};
|
|
1113
1319
|
|
|
@@ -1118,43 +1324,80 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1118
1324
|
return result;
|
|
1119
1325
|
};
|
|
1120
1326
|
|
|
1327
|
+
// Pi's own showExtensionNotify only dispatches to showStatus/showWarning/
|
|
1328
|
+
// showError, and all three are replaced above. Delegating back to it would
|
|
1329
|
+
// therefore re-enter these patches instead of restoring native behaviour,
|
|
1330
|
+
// which sent info notifications into the transient UI flash. Route straight
|
|
1331
|
+
// to the original method that matches the severity instead.
|
|
1332
|
+
const nativeExtensionNotify = function (
|
|
1333
|
+
this: any,
|
|
1334
|
+
text: string,
|
|
1335
|
+
severity: ActivitySeverity,
|
|
1336
|
+
): void {
|
|
1337
|
+
const direct = severity === "error"
|
|
1338
|
+
? originalShowError
|
|
1339
|
+
: severity === "warning"
|
|
1340
|
+
? originalShowWarning
|
|
1341
|
+
: originalShowStatus;
|
|
1342
|
+
try {
|
|
1343
|
+
if (typeof direct === "function") direct.call(this, text);
|
|
1344
|
+
else originalShowExtensionNotify.call(this, text, severity === "info" ? undefined : severity);
|
|
1345
|
+
} catch {
|
|
1346
|
+
// This host has no transcript to write to. The notification is already
|
|
1347
|
+
// recorded in the activity timeline, and it does not belong in the
|
|
1348
|
+
// transient UI flash, which only carries Pi UI state messages.
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
1351
|
+
|
|
1121
1352
|
const patchedShowExtensionNotify = function (
|
|
1122
1353
|
this: any,
|
|
1123
1354
|
message: string,
|
|
1124
1355
|
type?: "info" | "warning" | "error",
|
|
1125
1356
|
) {
|
|
1126
|
-
if (renderMode !== "clean"
|
|
1357
|
+
if (renderMode !== "clean") {
|
|
1127
1358
|
return originalShowExtensionNotify.call(this, message, type);
|
|
1128
1359
|
}
|
|
1129
|
-
const
|
|
1130
|
-
|
|
1131
|
-
|
|
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);
|
|
1360
|
+
const severity: ActivitySeverity = type ?? "info";
|
|
1361
|
+
if (!appendRuntimeActivityUpdate(this, message, severity)) {
|
|
1362
|
+
nativeExtensionNotify.call(this, message, severity);
|
|
1138
1363
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1364
|
+
};
|
|
1365
|
+
|
|
1366
|
+
const patchedToggleThinkingBlockVisibility = function (this: any) {
|
|
1367
|
+
if (renderMode !== "clean") {
|
|
1368
|
+
return originalToggleThinkingBlockVisibility.call(this);
|
|
1369
|
+
}
|
|
1370
|
+
// Do not change Pi's persisted hideThinkingBlock setting in clean mode.
|
|
1371
|
+
// Thought details use the activity hierarchy instead: click one thought,
|
|
1372
|
+
// or use Ctrl+O when every member should expand together.
|
|
1373
|
+
this.showStatus(
|
|
1374
|
+
"Thought details expand individually in clean mode\nClick a thought, or use Ctrl+O to expand all.",
|
|
1375
|
+
);
|
|
1376
|
+
};
|
|
1377
|
+
|
|
1378
|
+
const patchedShowStatus = function (this: any, message: string) {
|
|
1379
|
+
if (renderMode !== "clean") return originalShowStatus.call(this, message);
|
|
1380
|
+
const [firstLine, ...remainingLines] = String(message).split("\n");
|
|
1381
|
+
setLatestActivityStatus({
|
|
1382
|
+
title: firstLine || "Status",
|
|
1383
|
+
detail: remainingLines.join("\n"),
|
|
1384
|
+
severity: "status",
|
|
1385
|
+
});
|
|
1155
1386
|
this.ui?.requestRender?.();
|
|
1156
1387
|
};
|
|
1157
1388
|
|
|
1389
|
+
const patchedShowWarning = function (this: any, message: string) {
|
|
1390
|
+
if (renderMode !== "clean" || !appendRuntimeActivityUpdate(this, message, "warning")) {
|
|
1391
|
+
return originalShowWarning.call(this, message);
|
|
1392
|
+
}
|
|
1393
|
+
};
|
|
1394
|
+
|
|
1395
|
+
const patchedShowError = function (this: any, message: string) {
|
|
1396
|
+
if (renderMode !== "clean" || !appendRuntimeActivityUpdate(this, message, "error")) {
|
|
1397
|
+
return originalShowError.call(this, message);
|
|
1398
|
+
}
|
|
1399
|
+
};
|
|
1400
|
+
|
|
1158
1401
|
interactiveModePrototype[toolsExpansionPatchKey] = {
|
|
1159
1402
|
originalSetToolsExpanded,
|
|
1160
1403
|
patchedSetToolsExpanded,
|
|
@@ -1164,15 +1407,28 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1164
1407
|
patchedSwitchTuiMode,
|
|
1165
1408
|
originalShowExtensionNotify,
|
|
1166
1409
|
patchedShowExtensionNotify,
|
|
1410
|
+
originalToggleThinkingBlockVisibility,
|
|
1411
|
+
patchedToggleThinkingBlockVisibility,
|
|
1412
|
+
originalShowStatus,
|
|
1413
|
+
patchedShowStatus,
|
|
1414
|
+
originalShowWarning,
|
|
1415
|
+
patchedShowWarning,
|
|
1416
|
+
originalShowError,
|
|
1417
|
+
patchedShowError,
|
|
1167
1418
|
};
|
|
1168
1419
|
interactiveModePrototype.setToolsExpanded = patchedSetToolsExpanded;
|
|
1169
1420
|
interactiveModePrototype.renderSessionEntries = patchedRenderSessionEntries;
|
|
1170
1421
|
interactiveModePrototype.switchTuiMode = patchedSwitchTuiMode;
|
|
1171
1422
|
interactiveModePrototype.showExtensionNotify = patchedShowExtensionNotify;
|
|
1423
|
+
interactiveModePrototype.toggleThinkingBlockVisibility = patchedToggleThinkingBlockVisibility;
|
|
1424
|
+
interactiveModePrototype.showStatus = patchedShowStatus;
|
|
1425
|
+
interactiveModePrototype.showWarning = patchedShowWarning;
|
|
1426
|
+
interactiveModePrototype.showError = patchedShowError;
|
|
1172
1427
|
|
|
1173
1428
|
pi.on("session_shutdown", () => {
|
|
1174
1429
|
fullscreenTui = false;
|
|
1175
1430
|
currentTui = undefined;
|
|
1431
|
+
clearLatestActivityStatus();
|
|
1176
1432
|
currentExtensionUi = undefined;
|
|
1177
1433
|
const patch = interactiveModePrototype[toolsExpansionPatchKey];
|
|
1178
1434
|
if (!patch) return;
|
|
@@ -1188,11 +1444,27 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1188
1444
|
if (patch.patchedShowExtensionNotify === interactiveModePrototype.showExtensionNotify) {
|
|
1189
1445
|
interactiveModePrototype.showExtensionNotify = patch.originalShowExtensionNotify;
|
|
1190
1446
|
}
|
|
1447
|
+
if (patch.patchedToggleThinkingBlockVisibility === interactiveModePrototype.toggleThinkingBlockVisibility) {
|
|
1448
|
+
interactiveModePrototype.toggleThinkingBlockVisibility = patch.originalToggleThinkingBlockVisibility;
|
|
1449
|
+
}
|
|
1450
|
+
if (patch.patchedShowStatus === interactiveModePrototype.showStatus) {
|
|
1451
|
+
interactiveModePrototype.showStatus = patch.originalShowStatus;
|
|
1452
|
+
}
|
|
1453
|
+
if (patch.patchedShowWarning === interactiveModePrototype.showWarning) {
|
|
1454
|
+
interactiveModePrototype.showWarning = patch.originalShowWarning;
|
|
1455
|
+
}
|
|
1456
|
+
if (patch.patchedShowError === interactiveModePrototype.showError) {
|
|
1457
|
+
interactiveModePrototype.showError = patch.originalShowError;
|
|
1458
|
+
}
|
|
1191
1459
|
if (
|
|
1192
1460
|
interactiveModePrototype.setToolsExpanded === patch.originalSetToolsExpanded &&
|
|
1193
1461
|
interactiveModePrototype.renderSessionEntries === patch.originalRenderSessionEntries &&
|
|
1194
1462
|
interactiveModePrototype.switchTuiMode === patch.originalSwitchTuiMode &&
|
|
1195
|
-
interactiveModePrototype.showExtensionNotify === patch.originalShowExtensionNotify
|
|
1463
|
+
interactiveModePrototype.showExtensionNotify === patch.originalShowExtensionNotify &&
|
|
1464
|
+
interactiveModePrototype.toggleThinkingBlockVisibility === patch.originalToggleThinkingBlockVisibility &&
|
|
1465
|
+
interactiveModePrototype.showStatus === patch.originalShowStatus &&
|
|
1466
|
+
interactiveModePrototype.showWarning === patch.originalShowWarning &&
|
|
1467
|
+
interactiveModePrototype.showError === patch.originalShowError
|
|
1196
1468
|
) {
|
|
1197
1469
|
delete interactiveModePrototype[toolsExpansionPatchKey];
|
|
1198
1470
|
}
|
|
@@ -1527,8 +1799,14 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1527
1799
|
} else {
|
|
1528
1800
|
// Defensive fallback for older Pi versions without the fullscreen helper.
|
|
1529
1801
|
void copyToClipboard(region.code)
|
|
1530
|
-
.then(() =>
|
|
1531
|
-
|
|
1802
|
+
.then(() => setLatestActivityStatus({
|
|
1803
|
+
title: "Copied!",
|
|
1804
|
+
severity: "success",
|
|
1805
|
+
}))
|
|
1806
|
+
.catch(() => setLatestActivityStatus({
|
|
1807
|
+
title: "Copy failed",
|
|
1808
|
+
severity: "error",
|
|
1809
|
+
}));
|
|
1532
1810
|
}
|
|
1533
1811
|
}
|
|
1534
1812
|
return { handled: true, render: false };
|
|
@@ -1760,8 +2038,11 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1760
2038
|
return block(rows);
|
|
1761
2039
|
};
|
|
1762
2040
|
|
|
1763
|
-
|
|
1764
|
-
|
|
2041
|
+
// Pi's render context always carries the authoritative failure flag
|
|
2042
|
+
// (tool-execution.js builds it from result.isError). A tool's own output text
|
|
2043
|
+
// must never decide this: `grep -n Error`, `cat error.log`, or a test runner
|
|
2044
|
+
// printing "failed 0" would otherwise mark a successful call as failed.
|
|
2045
|
+
const isError = (renderContext: any): boolean => Boolean(renderContext?.isError);
|
|
1765
2046
|
|
|
1766
2047
|
const partialResult = (context: any, theme: any, label: string): Component => {
|
|
1767
2048
|
setStatus(context, "running");
|
|
@@ -1834,8 +2115,8 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1834
2115
|
return block(rows);
|
|
1835
2116
|
};
|
|
1836
2117
|
|
|
1837
|
-
const completeStatus = (context: any
|
|
1838
|
-
const failed = isError(context
|
|
2118
|
+
const completeStatus = (context: any): boolean => {
|
|
2119
|
+
const failed = isError(context);
|
|
1839
2120
|
setStatus(context, failed ? "error" : "success");
|
|
1840
2121
|
return failed;
|
|
1841
2122
|
};
|
|
@@ -2351,6 +2632,15 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2351
2632
|
activityTimeline.boundary();
|
|
2352
2633
|
};
|
|
2353
2634
|
|
|
2635
|
+
closeCleanAtNativeCustomBoundary = () => {
|
|
2636
|
+
finishCleanGroup("done");
|
|
2637
|
+
settleLastCleanGroup();
|
|
2638
|
+
};
|
|
2639
|
+
closeCleanAtStandaloneAlert = () => {
|
|
2640
|
+
finishCleanGroup("done");
|
|
2641
|
+
settleLastCleanGroup();
|
|
2642
|
+
};
|
|
2643
|
+
|
|
2354
2644
|
const settleLastCleanGroup = () => {
|
|
2355
2645
|
const group = cleanRun.groups[cleanRun.groups.length - 1];
|
|
2356
2646
|
if (!group) return;
|
|
@@ -2374,14 +2664,16 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2374
2664
|
const activityText = /^thinking(?:\.\.\.)?$/iu.test(normalizedActivityText)
|
|
2375
2665
|
? "thinking"
|
|
2376
2666
|
: rawActivityText;
|
|
2377
|
-
const countLabel = `${count} tool ${count === 1 ? "call" : "calls"}
|
|
2667
|
+
const countLabel = count > 0 ? `${count} tool ${count === 1 ? "call" : "calls"}` : "";
|
|
2378
2668
|
const thoughtLabel = thoughtCount > 0
|
|
2379
|
-
?
|
|
2669
|
+
? `${thoughtCount} ${thoughtCount === 1 ? "thought" : "thoughts"}`
|
|
2380
2670
|
: "";
|
|
2381
|
-
|
|
2382
|
-
|
|
2671
|
+
// Without tools there is no current tool name to report, and the generic
|
|
2672
|
+
// "thinking" placeholder would only repeat the thought count.
|
|
2673
|
+
const activityLabel = count > 0 && activityText !== "done" && activityText.trim()
|
|
2674
|
+
? activityText
|
|
2383
2675
|
: "";
|
|
2384
|
-
return
|
|
2676
|
+
return [countLabel, thoughtLabel, activityLabel].filter(Boolean).join(" · ");
|
|
2385
2677
|
};
|
|
2386
2678
|
|
|
2387
2679
|
const summaryRow = (
|
|
@@ -2455,36 +2747,53 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2455
2747
|
): string[] => {
|
|
2456
2748
|
const fit = (lines: string[]) => lines.map((line) => truncateToWidth(line, Math.max(1, width), ""));
|
|
2457
2749
|
const summaryTheme = activityGroupTheme(group);
|
|
2750
|
+
if (!summaryTheme || !activityTimeline.hasWork(group)) return [];
|
|
2458
2751
|
const owner = activityGroupOwner(group);
|
|
2459
|
-
|
|
2460
|
-
const
|
|
2752
|
+
const settled = owner ? settledSummaries.get(owner) : undefined;
|
|
2753
|
+
const toolCount = group.toolCallIds.length;
|
|
2754
|
+
const live = () => cleanRun.activeToolName ?? currentCleanActivity();
|
|
2461
2755
|
if (settled) {
|
|
2462
2756
|
return fit(block([summaryRow(
|
|
2463
2757
|
summaryTheme,
|
|
2464
|
-
Math.max(settled.count,
|
|
2465
|
-
settled.failed,
|
|
2758
|
+
group.hardBoundarySplit ? toolCount : Math.max(settled.count, toolCount),
|
|
2759
|
+
group.hardBoundarySplit ? 0 : settled.failed,
|
|
2466
2760
|
Math.max(settled.thoughtCount, group.thoughtCount),
|
|
2467
2761
|
settled.activity,
|
|
2468
2762
|
collapsedDone,
|
|
2469
2763
|
)]).render(width));
|
|
2470
2764
|
}
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
: false;
|
|
2474
|
-
const lastBelongs = cleanRun.lastCompletedToolCallId
|
|
2475
|
-
? group.toolCallIds.includes(cleanRun.lastCompletedToolCallId)
|
|
2476
|
-
: false;
|
|
2477
|
-
if (currentBelongs || lastBelongs || !cleanRun.settled) {
|
|
2765
|
+
if (owner && !cleanRun.settled) {
|
|
2766
|
+
// Live tool group: keep the current tool name and the running count.
|
|
2478
2767
|
return fit(block([summaryRow(
|
|
2479
2768
|
summaryTheme,
|
|
2480
|
-
Math.max(
|
|
2769
|
+
Math.max(toolCount, liveCleanToolCount()),
|
|
2481
2770
|
cleanRun.failed,
|
|
2482
2771
|
group.thoughtCount,
|
|
2483
|
-
|
|
2772
|
+
live,
|
|
2484
2773
|
collapsedDone,
|
|
2485
2774
|
)]).render(width));
|
|
2486
2775
|
}
|
|
2487
|
-
|
|
2776
|
+
if (!owner && !activityTimeline.isSettled(group.id)) {
|
|
2777
|
+
// Thought-only group while the model is still working.
|
|
2778
|
+
return fit(block([summaryRow(
|
|
2779
|
+
summaryTheme,
|
|
2780
|
+
toolCount,
|
|
2781
|
+
cleanRun.failed,
|
|
2782
|
+
group.thoughtCount,
|
|
2783
|
+
"thinking",
|
|
2784
|
+
collapsedDone,
|
|
2785
|
+
)]).render(width));
|
|
2786
|
+
}
|
|
2787
|
+
// Finished group with no durable summary: a completed Thought-only turn, or
|
|
2788
|
+
// a group split apart by a native custom renderer.
|
|
2789
|
+
return fit(block([summaryRow(
|
|
2790
|
+
summaryTheme,
|
|
2791
|
+
toolCount,
|
|
2792
|
+
0,
|
|
2793
|
+
group.thoughtCount,
|
|
2794
|
+
"done",
|
|
2795
|
+
collapsedDone,
|
|
2796
|
+
)]).render(width));
|
|
2488
2797
|
};
|
|
2489
2798
|
|
|
2490
2799
|
/**
|
|
@@ -2552,7 +2861,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2552
2861
|
|
|
2553
2862
|
const hiddenToolResult = (context: any, options: any, output: string): Component => {
|
|
2554
2863
|
if (options.isPartial) setStatus(context, "running");
|
|
2555
|
-
else completeStatus(context
|
|
2864
|
+
else completeStatus(context);
|
|
2556
2865
|
return block([]);
|
|
2557
2866
|
};
|
|
2558
2867
|
|
|
@@ -2620,17 +2929,18 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2620
2929
|
|
|
2621
2930
|
const restoreCleanSession = (ctx: any) => {
|
|
2622
2931
|
currentExtensionUi = ctx.ui;
|
|
2932
|
+
clearLatestActivityStatus();
|
|
2623
2933
|
cleanToolsExpanded = ctx.ui.getToolsExpanded();
|
|
2624
2934
|
settledSummaries.clear();
|
|
2625
2935
|
legacySummaryLastToolCallIds.clear();
|
|
2626
2936
|
activityTimeline.clear();
|
|
2937
|
+
nativeCustomBoundaryKeys.clear();
|
|
2627
2938
|
revealedActivityGroups.clear();
|
|
2628
2939
|
expandedThinkingMembers.clear();
|
|
2629
2940
|
thinkingComponents.clear();
|
|
2630
2941
|
activityFallbackThemes.clear();
|
|
2631
2942
|
activityUpdateComponents.clear();
|
|
2632
|
-
|
|
2633
|
-
customUpdateBeforeToolHints.clear();
|
|
2943
|
+
activityUpdateMarkdownComponents.clear();
|
|
2634
2944
|
activityUpdateSequence = 0;
|
|
2635
2945
|
assistantBoundaryKeys.clear();
|
|
2636
2946
|
knownToolCallIds.clear();
|
|
@@ -2848,7 +3158,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2848
3158
|
|
|
2849
3159
|
if (message.role === "assistant") {
|
|
2850
3160
|
const thinking = thinkingText(message);
|
|
2851
|
-
if (thinking
|
|
3161
|
+
if (thinking) {
|
|
2852
3162
|
activityTimeline.addThinking(assistantMessageKey(message), thinking);
|
|
2853
3163
|
}
|
|
2854
3164
|
if (messageHasVisibleText(message)) finishGroup();
|
|
@@ -2857,7 +3167,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2857
3167
|
toolCalls.add(item.id);
|
|
2858
3168
|
activityTimeline.addTool(item.id, item.name ?? "tool");
|
|
2859
3169
|
}
|
|
2860
|
-
if (
|
|
3170
|
+
if (assistantTerminalState(message)) finishGroup();
|
|
2861
3171
|
continue;
|
|
2862
3172
|
}
|
|
2863
3173
|
|
|
@@ -2924,8 +3234,16 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2924
3234
|
(item: any) => item.type === "toolCall" && item.id,
|
|
2925
3235
|
);
|
|
2926
3236
|
|
|
3237
|
+
const conciseActivityText = (value: string): string =>
|
|
3238
|
+
value
|
|
3239
|
+
.split("\n")
|
|
3240
|
+
.map((line) => line.trim())
|
|
3241
|
+
.find(Boolean)
|
|
3242
|
+
?.replace(/^#{1,6}\s+/u, "")
|
|
3243
|
+
.replace(/^\*\*(.*?)\*\*$/u, "$1") ?? "";
|
|
3244
|
+
|
|
2927
3245
|
const trackThinkingActivity = (message: any): void => {
|
|
2928
|
-
if (message?.role !== "assistant"
|
|
3246
|
+
if (message?.role !== "assistant") return;
|
|
2929
3247
|
const thinking = thinkingText(message);
|
|
2930
3248
|
if (thinking) activityTimeline.addThinking(assistantMessageKey(message), thinking);
|
|
2931
3249
|
};
|
|
@@ -2984,16 +3302,11 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2984
3302
|
closeAtAssistantBoundary(event.message, "done");
|
|
2985
3303
|
return;
|
|
2986
3304
|
}
|
|
2987
|
-
if (assistantSystemBoundary(event.message)) {
|
|
2988
|
-
closeAtAssistantBoundary(event.message, "done");
|
|
2989
|
-
settleLastCleanGroup();
|
|
2990
|
-
return;
|
|
2991
|
-
}
|
|
2992
3305
|
trackThinkingActivity(event.message);
|
|
2993
3306
|
trackPendingToolActivity(event.message);
|
|
2994
3307
|
});
|
|
2995
3308
|
pi.on("message_end", (event) => {
|
|
2996
|
-
if (hasVisibleAssistantText(event.message)
|
|
3309
|
+
if (hasVisibleAssistantText(event.message)) {
|
|
2997
3310
|
trackThinkingActivity(event.message);
|
|
2998
3311
|
closeAtAssistantBoundary(event.message, "done");
|
|
2999
3312
|
cleanRun.activity = "done";
|
|
@@ -3002,6 +3315,12 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3002
3315
|
}
|
|
3003
3316
|
trackThinkingActivity(event.message);
|
|
3004
3317
|
trackPendingToolActivity(event.message);
|
|
3318
|
+
if (assistantTerminalState(event.message)) {
|
|
3319
|
+
clearToolActivityHolds();
|
|
3320
|
+
finishCleanGroup("done");
|
|
3321
|
+
settleLastCleanGroup();
|
|
3322
|
+
cleanRun.activity = "done";
|
|
3323
|
+
}
|
|
3005
3324
|
});
|
|
3006
3325
|
|
|
3007
3326
|
pi.on("agent_start", () => {
|
|
@@ -3058,6 +3377,8 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3058
3377
|
}
|
|
3059
3378
|
const visibleSummaryToolCallId = cleanRun.activeToolCallId ?? cleanRun.lastCompletedToolCallId;
|
|
3060
3379
|
setCleanGroupMembers(visibleSummaryToolCallId, cleanRun.currentToolCallIds.slice());
|
|
3380
|
+
// Tool success and failure remain owned by the activity group. The bottom
|
|
3381
|
+
// line is reserved for transient UI status messages.
|
|
3061
3382
|
});
|
|
3062
3383
|
pi.on("agent_end", () => {
|
|
3063
3384
|
// No summary is appended here: this event may be followed by an automatic
|
|
@@ -3129,7 +3450,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3129
3450
|
if (hideCleanTool(context.toolCallId, options.expanded, context.executionStarted)) return hiddenToolResult(context, options, output);
|
|
3130
3451
|
if (options.isPartial) return partialResult(context, theme, "Reading…");
|
|
3131
3452
|
const image = toolResult.content?.find((item: any) => item.type === "image");
|
|
3132
|
-
const failed = completeStatus(context
|
|
3453
|
+
const failed = completeStatus(context);
|
|
3133
3454
|
if (image) return result(theme, "Read image", "", false, failed);
|
|
3134
3455
|
const lines = nonEmptyLines(output);
|
|
3135
3456
|
const truncated = toolResult.details?.truncation?.truncated ? " · truncated" : "";
|
|
@@ -3167,7 +3488,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3167
3488
|
if (hideCleanTool(context.toolCallId, options.expanded, context.executionStarted)) return hiddenToolResult(context, options, output);
|
|
3168
3489
|
if (useCompactToolView(context.toolCallId, options.expanded)) {
|
|
3169
3490
|
if (options.isPartial) return partialResult(context, theme, "Running…");
|
|
3170
|
-
const failed = completeStatus(context
|
|
3491
|
+
const failed = completeStatus(context);
|
|
3171
3492
|
return result(theme, failed ? "Command failed" : "Done", "", false, failed);
|
|
3172
3493
|
}
|
|
3173
3494
|
|
|
@@ -3175,7 +3496,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3175
3496
|
return bashResult(context, theme, "Running…", output, options.expanded, "running");
|
|
3176
3497
|
}
|
|
3177
3498
|
|
|
3178
|
-
const failed = isError(context
|
|
3499
|
+
const failed = isError(context);
|
|
3179
3500
|
const outputLines = terminalOutputLines(output);
|
|
3180
3501
|
const lineCount = outputLines.filter((line) => line.length > 0).length;
|
|
3181
3502
|
const summary = failed
|
|
@@ -3208,7 +3529,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3208
3529
|
const output = textContent(toolResult);
|
|
3209
3530
|
if (hideCleanTool(context.toolCallId, options.expanded, context.executionStarted)) return hiddenToolResult(context, options, output);
|
|
3210
3531
|
if (options.isPartial) return partialResult(context, theme, "Editing…");
|
|
3211
|
-
const failed = completeStatus(context
|
|
3532
|
+
const failed = completeStatus(context);
|
|
3212
3533
|
const diff = toolResult.details?.diff ?? "";
|
|
3213
3534
|
const additions = diff.split("\n").filter((line: string) => line.startsWith("+") && !line.startsWith("+++")).length;
|
|
3214
3535
|
const removals = diff.split("\n").filter((line: string) => line.startsWith("-") && !line.startsWith("---")).length;
|
|
@@ -3257,7 +3578,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3257
3578
|
const output = textContent(toolResult);
|
|
3258
3579
|
if (hideCleanTool(context.toolCallId, options.expanded, context.executionStarted)) return hiddenToolResult(context, options, output);
|
|
3259
3580
|
if (options.isPartial) return partialResult(context, theme, "Writing…");
|
|
3260
|
-
const failed = completeStatus(context
|
|
3581
|
+
const failed = completeStatus(context);
|
|
3261
3582
|
return result(
|
|
3262
3583
|
theme,
|
|
3263
3584
|
failed ? output.split("\n")[0] || "Write failed" : "Written",
|
|
@@ -3283,7 +3604,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3283
3604
|
const output = textContent(toolResult);
|
|
3284
3605
|
if (hideCleanTool(context.toolCallId, options.expanded, context.executionStarted)) return hiddenToolResult(context, options, output);
|
|
3285
3606
|
if (options.isPartial) return partialResult(context, theme, "Searching…");
|
|
3286
|
-
const failed = completeStatus(context
|
|
3607
|
+
const failed = completeStatus(context);
|
|
3287
3608
|
const matches = nonEmptyLines(output);
|
|
3288
3609
|
return result(theme, failed ? output.split("\n")[0] : `Found ${matches} matches`, output, options.expanded, failed);
|
|
3289
3610
|
},
|
|
@@ -3302,7 +3623,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3302
3623
|
const output = textContent(toolResult);
|
|
3303
3624
|
if (hideCleanTool(context.toolCallId, options.expanded, context.executionStarted)) return hiddenToolResult(context, options, output);
|
|
3304
3625
|
if (options.isPartial) return partialResult(context, theme, "Searching…");
|
|
3305
|
-
const failed = completeStatus(context
|
|
3626
|
+
const failed = completeStatus(context);
|
|
3306
3627
|
const matches = nonEmptyLines(output);
|
|
3307
3628
|
return result(theme, failed ? output.split("\n")[0] : `Found ${matches} paths`, output, options.expanded, failed);
|
|
3308
3629
|
},
|
|
@@ -3321,7 +3642,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3321
3642
|
const output = textContent(toolResult);
|
|
3322
3643
|
if (hideCleanTool(context.toolCallId, options.expanded, context.executionStarted)) return hiddenToolResult(context, options, output);
|
|
3323
3644
|
if (options.isPartial) return partialResult(context, theme, "Listing…");
|
|
3324
|
-
const failed = completeStatus(context
|
|
3645
|
+
const failed = completeStatus(context);
|
|
3325
3646
|
const entries = nonEmptyLines(output);
|
|
3326
3647
|
return result(theme, failed ? output.split("\n")[0] : `Listed ${entries} entries`, output, options.expanded, failed);
|
|
3327
3648
|
},
|