pi-ui-extend 0.1.78 → 1.0.2
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/dist/app/app.js +3 -0
- package/dist/app/rendering/toast-controller.d.ts +21 -6
- package/dist/app/rendering/toast-controller.js +37 -1
- package/dist/app/rendering/toast-renderer.d.ts +2 -2
- package/dist/app/rendering/toast-renderer.js +35 -2
- package/dist/app/rendering/tool-block-renderer.js +71 -37
- package/dist/app/screen/mouse-controller.d.ts +1 -0
- package/dist/app/screen/mouse-controller.js +5 -0
- package/dist/app/session/queued-message-controller.d.ts +1 -0
- package/dist/app/session/queued-message-controller.js +3 -0
- package/dist/app/session/session-event-controller.d.ts +10 -1
- package/dist/app/session/session-event-controller.js +54 -2
- package/dist/app/types.d.ts +1 -1
- package/dist/bundled-extensions/session-title/index.js +19 -7
- package/external/pi-tools-suite/package.json +3 -3
- package/external/pi-tools-suite/src/antigravity-auth/stream.ts +7 -3
- package/package.json +4 -4
package/dist/app/app.js
CHANGED
|
@@ -387,6 +387,7 @@ export class PiUiExtendApp {
|
|
|
387
387
|
this.sessionEvents = new AppSessionEventController({
|
|
388
388
|
entries: this.entries,
|
|
389
389
|
runtime: () => this.runtime,
|
|
390
|
+
awaitSessionExtensions: (runtime) => this.awaitCurrentSessionExtensions(runtime),
|
|
390
391
|
conversationViewport: () => this.conversationViewport,
|
|
391
392
|
conversationViewportColumns: () => this.terminalColumns(),
|
|
392
393
|
onHistoryWindowPruned: (edge, lineCount) => this.scrollController.adjustForHistoryWindowPrune(edge, lineCount),
|
|
@@ -421,6 +422,7 @@ export class PiUiExtendApp {
|
|
|
421
422
|
this.queuedMessages = new AppQueuedMessageController({
|
|
422
423
|
runtime: () => this.runtime,
|
|
423
424
|
requireRuntime: () => this.requireRuntime(),
|
|
425
|
+
awaitCurrentSessionExtensions: (runtime) => this.awaitCurrentSessionExtensions(runtime),
|
|
424
426
|
visibleEntries: () => this.conversationViewport.entries(),
|
|
425
427
|
isRunning: () => this.running,
|
|
426
428
|
render: () => this.render(),
|
|
@@ -605,6 +607,7 @@ export class PiUiExtendApp {
|
|
|
605
607
|
toastEntry: (toastId) => this.toastController.entry(toastId),
|
|
606
608
|
showToast: (message, kind, options) => this.showToast(message, kind, options),
|
|
607
609
|
dismissToast: (toastId) => this.toastController.dismissToast(toastId),
|
|
610
|
+
activateToastAction: (toastId) => this.toastController.activateAction(toastId),
|
|
608
611
|
refreshModelUsageStatus: () => this.refreshModelUsageStatusFromClick(),
|
|
609
612
|
refreshUserMessageJumpMenuItems: () => this.menuItems.refreshUserMessageJumpMenuItems(),
|
|
610
613
|
queueInputFromStatus: () => {
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import { type ToastEntry, type ToastKind, type ToastVariant } from "../../ui.js";
|
|
2
|
+
export type AppToastEntry = ToastEntry & {
|
|
3
|
+
action?: {
|
|
4
|
+
label: string;
|
|
5
|
+
};
|
|
6
|
+
};
|
|
7
|
+
export type AppToastAction = {
|
|
8
|
+
label: string;
|
|
9
|
+
onSelect: () => void;
|
|
10
|
+
};
|
|
11
|
+
export type AppToastOptions = {
|
|
12
|
+
durationMs?: number;
|
|
13
|
+
variant?: ToastVariant;
|
|
14
|
+
scopeKey?: string;
|
|
15
|
+
action?: AppToastAction;
|
|
16
|
+
};
|
|
2
17
|
export type AppToastControllerHost = {
|
|
3
18
|
activeScope?(): string | undefined;
|
|
4
19
|
render(): void;
|
|
@@ -7,19 +22,19 @@ export declare class AppToastController {
|
|
|
7
22
|
private readonly host;
|
|
8
23
|
private readonly toastsByScope;
|
|
9
24
|
private readonly timers;
|
|
25
|
+
private readonly actions;
|
|
10
26
|
constructor(host: AppToastControllerHost);
|
|
11
|
-
showToast(message: string, kind?: ToastKind, options?:
|
|
12
|
-
durationMs?: number;
|
|
13
|
-
variant?: ToastVariant;
|
|
14
|
-
scopeKey?: string;
|
|
15
|
-
}): void;
|
|
27
|
+
showToast(message: string, kind?: ToastKind, options?: AppToastOptions): void;
|
|
16
28
|
dismissToast(toastId: number, scopeKey?: string): void;
|
|
29
|
+
activateAction(toastId: number, scopeKey?: string): boolean;
|
|
17
30
|
dismissActiveDialog(scopeKey?: string): boolean;
|
|
18
|
-
visibleStates(scopeKey?: string): readonly
|
|
31
|
+
visibleStates(scopeKey?: string): readonly AppToastEntry[];
|
|
19
32
|
entry(toastId: number, scopeKey?: string): ToastEntry | undefined;
|
|
20
33
|
clearToastTimers(): void;
|
|
21
34
|
private toastForScope;
|
|
22
35
|
private timersForScope;
|
|
36
|
+
private actionsForScope;
|
|
37
|
+
private removeAction;
|
|
23
38
|
private deleteScopeIfEmpty;
|
|
24
39
|
private normalizeScopeKey;
|
|
25
40
|
}
|
|
@@ -4,13 +4,17 @@ export class AppToastController {
|
|
|
4
4
|
host;
|
|
5
5
|
toastsByScope = new Map();
|
|
6
6
|
timers = new Map();
|
|
7
|
+
actions = new Map();
|
|
7
8
|
constructor(host) {
|
|
8
9
|
this.host = host;
|
|
9
10
|
}
|
|
10
11
|
showToast(message, kind = "info", options = {}) {
|
|
11
12
|
const scopeKey = this.normalizeScopeKey(options.scopeKey ?? this.host.activeScope?.());
|
|
12
13
|
const toast = this.toastForScope(scopeKey);
|
|
14
|
+
const action = options.variant === "dialog" ? undefined : options.action;
|
|
13
15
|
const toastId = toast.show(message, kind, options.variant ? { variant: options.variant } : {});
|
|
16
|
+
if (action)
|
|
17
|
+
this.actionsForScope(scopeKey).set(toastId, action);
|
|
14
18
|
if (kind === "error" || options.variant === "dialog") {
|
|
15
19
|
this.host.render();
|
|
16
20
|
return;
|
|
@@ -21,6 +25,7 @@ export class AppToastController {
|
|
|
21
25
|
const timer = setTimeout(() => {
|
|
22
26
|
toast.hide(toastId);
|
|
23
27
|
this.timers.get(scopeKey)?.delete(toastId);
|
|
28
|
+
this.removeAction(scopeKey, toastId);
|
|
24
29
|
this.deleteScopeIfEmpty(scopeKey);
|
|
25
30
|
this.host.render();
|
|
26
31
|
}, durationMs);
|
|
@@ -36,9 +41,18 @@ export class AppToastController {
|
|
|
36
41
|
timers?.delete(toastId);
|
|
37
42
|
}
|
|
38
43
|
this.toastsByScope.get(scopeKey)?.hide(toastId);
|
|
44
|
+
this.removeAction(scopeKey, toastId);
|
|
39
45
|
this.deleteScopeIfEmpty(scopeKey);
|
|
40
46
|
this.host.render();
|
|
41
47
|
}
|
|
48
|
+
activateAction(toastId, scopeKey = this.normalizeScopeKey(this.host.activeScope?.())) {
|
|
49
|
+
const action = this.actions.get(scopeKey)?.get(toastId);
|
|
50
|
+
if (!action)
|
|
51
|
+
return false;
|
|
52
|
+
this.dismissToast(toastId, scopeKey);
|
|
53
|
+
action.onSelect();
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
42
56
|
dismissActiveDialog(scopeKey = this.normalizeScopeKey(this.host.activeScope?.())) {
|
|
43
57
|
const states = this.visibleStates(scopeKey);
|
|
44
58
|
let dialog;
|
|
@@ -57,7 +71,14 @@ export class AppToastController {
|
|
|
57
71
|
return true;
|
|
58
72
|
}
|
|
59
73
|
visibleStates(scopeKey = this.normalizeScopeKey(this.host.activeScope?.())) {
|
|
60
|
-
|
|
74
|
+
const states = this.toastsByScope.get(scopeKey)?.visibleStates ?? [];
|
|
75
|
+
const actions = this.actions.get(scopeKey);
|
|
76
|
+
if (!actions || actions.size === 0)
|
|
77
|
+
return states;
|
|
78
|
+
return states.map((state) => {
|
|
79
|
+
const action = actions.get(state.id);
|
|
80
|
+
return action ? { ...state, action: { label: action.label } } : state;
|
|
81
|
+
});
|
|
61
82
|
}
|
|
62
83
|
entry(toastId, scopeKey = this.normalizeScopeKey(this.host.activeScope?.())) {
|
|
63
84
|
return this.toastsByScope.get(scopeKey)?.entry(toastId);
|
|
@@ -68,6 +89,7 @@ export class AppToastController {
|
|
|
68
89
|
clearTimeout(timer);
|
|
69
90
|
}
|
|
70
91
|
this.timers.clear();
|
|
92
|
+
this.actions.clear();
|
|
71
93
|
for (const toast of this.toastsByScope.values())
|
|
72
94
|
toast.hide();
|
|
73
95
|
this.toastsByScope.clear();
|
|
@@ -88,6 +110,20 @@ export class AppToastController {
|
|
|
88
110
|
}
|
|
89
111
|
return timers;
|
|
90
112
|
}
|
|
113
|
+
actionsForScope(scopeKey) {
|
|
114
|
+
let actions = this.actions.get(scopeKey);
|
|
115
|
+
if (!actions) {
|
|
116
|
+
actions = new Map();
|
|
117
|
+
this.actions.set(scopeKey, actions);
|
|
118
|
+
}
|
|
119
|
+
return actions;
|
|
120
|
+
}
|
|
121
|
+
removeAction(scopeKey, toastId) {
|
|
122
|
+
const actions = this.actions.get(scopeKey);
|
|
123
|
+
actions?.delete(toastId);
|
|
124
|
+
if (actions?.size === 0)
|
|
125
|
+
this.actions.delete(scopeKey);
|
|
126
|
+
}
|
|
91
127
|
deleteScopeIfEmpty(scopeKey) {
|
|
92
128
|
const timers = this.timers.get(scopeKey);
|
|
93
129
|
if (timers && timers.size === 0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Theme } from "../../theme.js";
|
|
2
|
-
import type { ToastEntry } from "../../ui.js";
|
|
3
2
|
import type { ToastLineTarget } from "../types.js";
|
|
3
|
+
import type { AppToastEntry } from "./toast-controller.js";
|
|
4
4
|
export type ToastOverlay = {
|
|
5
5
|
id: number;
|
|
6
6
|
row: number;
|
|
@@ -9,4 +9,4 @@ export type ToastOverlay = {
|
|
|
9
9
|
output: string;
|
|
10
10
|
target?: ToastLineTarget;
|
|
11
11
|
};
|
|
12
|
-
export declare function renderToastOverlays(states: readonly
|
|
12
|
+
export declare function renderToastOverlays(states: readonly AppToastEntry[], width: number, maxRows: number, theme: Theme): ToastOverlay[];
|
|
@@ -15,10 +15,13 @@ export function renderToastOverlays(states, width, maxRows, theme) {
|
|
|
15
15
|
}
|
|
16
16
|
const icon = toastKindIcon(state.kind);
|
|
17
17
|
const lines = toastMessageLines(state.message, icon, Math.max(1, width - 6));
|
|
18
|
-
const
|
|
18
|
+
const availableRows = Math.max(0, maxRows - overlays.length);
|
|
19
|
+
const actionLabel = compactToastActionLabel(state);
|
|
20
|
+
const includeAction = actionLabel !== undefined && availableRows >= 2;
|
|
21
|
+
const visibleLines = lines.slice(0, Math.max(0, availableRows - (includeAction ? 1 : 0)));
|
|
19
22
|
if (visibleLines.length === 0)
|
|
20
23
|
continue;
|
|
21
|
-
const contentWidth = Math.max(...visibleLines.map((line) => stringDisplayWidth(line)));
|
|
24
|
+
const contentWidth = Math.max(...visibleLines.map((line) => stringDisplayWidth(line)), ...(includeAction ? [stringDisplayWidth(actionLabel)] : []));
|
|
22
25
|
const toastWidth = Math.min(Math.max(12, contentWidth + 2), Math.max(1, width - 4));
|
|
23
26
|
const leftWidth = Math.max(0, width - toastWidth - 2);
|
|
24
27
|
const column = leftWidth + 1;
|
|
@@ -39,9 +42,39 @@ export function renderToastOverlays(states, width, maxRows, theme) {
|
|
|
39
42
|
target: { kind: "toast", id: state.id, action: "toast", startColumn: column, endColumn: column + toastWidth },
|
|
40
43
|
});
|
|
41
44
|
}
|
|
45
|
+
if (includeAction) {
|
|
46
|
+
const innerWidth = Math.max(0, toastWidth - 2);
|
|
47
|
+
const renderedLabel = padOrTrimPlain(actionLabel, innerWidth).trimEnd();
|
|
48
|
+
const labelWidth = stringDisplayWidth(renderedLabel);
|
|
49
|
+
if (labelWidth > 0) {
|
|
50
|
+
const leftPadding = Math.max(0, innerWidth - labelWidth);
|
|
51
|
+
const message = ` ${" ".repeat(leftPadding)}${renderedLabel} `;
|
|
52
|
+
const startColumn = column + 1 + leftPadding;
|
|
53
|
+
overlays.push({
|
|
54
|
+
id: state.id,
|
|
55
|
+
row: overlays.length + 1,
|
|
56
|
+
column,
|
|
57
|
+
text: padOrTrimPlain(message, toastWidth),
|
|
58
|
+
output: colorToastLine(message, toastWidth, { ...style, bold: true }),
|
|
59
|
+
target: {
|
|
60
|
+
kind: "toast",
|
|
61
|
+
id: state.id,
|
|
62
|
+
action: "action",
|
|
63
|
+
startColumn,
|
|
64
|
+
endColumn: startColumn + labelWidth,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
42
69
|
}
|
|
43
70
|
return overlays;
|
|
44
71
|
}
|
|
72
|
+
function compactToastActionLabel(state) {
|
|
73
|
+
if (!state.action)
|
|
74
|
+
return undefined;
|
|
75
|
+
const label = stripToastAnsi(sanitizeToastText(state.action.label)).split("\n", 1)[0]?.trim();
|
|
76
|
+
return label ? `[${label}]` : undefined;
|
|
77
|
+
}
|
|
45
78
|
function toastMessageLines(message, icon, maxWidth) {
|
|
46
79
|
const firstPrefix = `${icon} `;
|
|
47
80
|
const continuationPrefix = " ".repeat(stringDisplayWidth(firstPrefix));
|
|
@@ -27,39 +27,34 @@ export function renderToolBlock(entry, rule, width, colors, options = {}) {
|
|
|
27
27
|
const headerPrefix = headerLabel ? `${stateIcon} ${headerLabel}` : stateIcon;
|
|
28
28
|
const headerArgs = formatToolHeaderArgs(entry.headerArgs);
|
|
29
29
|
const headerArgsWidth = width - stringDisplayWidth(headerPrefix) - 1;
|
|
30
|
-
const clippedHeaderArgs = headerArgsWidth > 0 ? sliceByDisplayWidth(headerArgs, headerArgsWidth) : "";
|
|
31
|
-
const header = clippedHeaderArgs ? `${headerPrefix} ${clippedHeaderArgs}` : headerPrefix;
|
|
32
|
-
const headerArgsStart = headerPrefix.length + 1;
|
|
33
|
-
const clippedHeaderArgSegments = clippedHeaderArgs
|
|
34
|
-
? clipAndShiftSegments(entry.headerArgSegments, headerArgsStart, clippedHeaderArgs.length)
|
|
35
|
-
: [];
|
|
36
|
-
const headerArgBaseSegments = clippedHeaderArgs
|
|
37
|
-
? uncoveredSegments(headerArgsStart, header.length, toolOutputColor, clippedHeaderArgSegments)
|
|
38
|
-
: [];
|
|
39
30
|
const target = { kind: "tool", id: entry.id };
|
|
40
31
|
const showGutter = options.showGutter ?? true;
|
|
41
|
-
const
|
|
42
|
-
|
|
32
|
+
const headerLines = renderToolHeaderLines({
|
|
33
|
+
entry,
|
|
34
|
+
expanded,
|
|
35
|
+
headerPrefix,
|
|
36
|
+
headerArgs,
|
|
37
|
+
headerArgsWidth,
|
|
38
|
+
stateIcon,
|
|
39
|
+
width,
|
|
43
40
|
target,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
]
|
|
52
|
-
};
|
|
53
|
-
const headerLines = [headerLine];
|
|
41
|
+
toolColor,
|
|
42
|
+
toolOutputColor,
|
|
43
|
+
statusIconColor: toolStatusIconColor(entry, colors),
|
|
44
|
+
backgroundOverride: options.backgroundOverride && !options.skipHeaderBackground ? options.backgroundOverride : undefined,
|
|
45
|
+
});
|
|
46
|
+
const headerLine = headerLines[0];
|
|
47
|
+
if (!headerLine)
|
|
48
|
+
return [];
|
|
54
49
|
if (expanded) {
|
|
55
|
-
|
|
56
|
-
if (options.skipHeaderBackground
|
|
57
|
-
applyBackground(
|
|
50
|
+
const bodyLines = renderToolBodyLines(entry.expandedText, width, target, toolOutputColor, entry.bodyStyle, colors, entry.syntaxHighlight, entry.bodyWrap, hasLspDiagnostics, entry.bodyLineStyles, entry.preserveAnsi, showGutter);
|
|
51
|
+
if (options.skipHeaderBackground) {
|
|
52
|
+
applyBackground(bodyLines);
|
|
58
53
|
}
|
|
59
54
|
else {
|
|
60
|
-
applyBackground(headerLines);
|
|
55
|
+
applyBackground([...headerLines, ...bodyLines]);
|
|
61
56
|
}
|
|
62
|
-
return headerLines;
|
|
57
|
+
return [...headerLines, ...bodyLines];
|
|
63
58
|
}
|
|
64
59
|
if (rule.compactHidden || (rule.defaultExpanded === true && !options.superCompact))
|
|
65
60
|
return headerLines;
|
|
@@ -75,7 +70,7 @@ export function renderToolBlock(entry, rule, width, colors, options = {}) {
|
|
|
75
70
|
if (!preview.text)
|
|
76
71
|
return headerLines;
|
|
77
72
|
const separator = " — ";
|
|
78
|
-
const availablePreviewWidth = width - stringDisplayWidth(
|
|
73
|
+
const availablePreviewWidth = width - stringDisplayWidth(headerLine.text) - stringDisplayWidth(separator);
|
|
79
74
|
if (availablePreviewWidth <= 0)
|
|
80
75
|
return headerLines;
|
|
81
76
|
const markerPrefix = truncatedPreviewMarker();
|
|
@@ -83,8 +78,8 @@ export function renderToolBlock(entry, rule, width, colors, options = {}) {
|
|
|
83
78
|
const clippedPreview = sliceByDisplayWidth(previewText, availablePreviewWidth);
|
|
84
79
|
if (!clippedPreview)
|
|
85
80
|
return headerLines;
|
|
86
|
-
headerLine.text = `${
|
|
87
|
-
const previewStart =
|
|
81
|
+
headerLine.text = `${headerLine.text}${separator}${clippedPreview}`;
|
|
82
|
+
const previewStart = headerLine.text.length - clippedPreview.length;
|
|
88
83
|
const previewTextStart = previewStart + (preview.overflow ? markerPrefix.length : 0);
|
|
89
84
|
headerLine.segments = [
|
|
90
85
|
...(headerLine.segments ?? []),
|
|
@@ -93,15 +88,54 @@ export function renderToolBlock(entry, rule, width, colors, options = {}) {
|
|
|
93
88
|
];
|
|
94
89
|
return headerLines;
|
|
95
90
|
}
|
|
96
|
-
function
|
|
97
|
-
|
|
91
|
+
function renderToolHeaderLines(input) {
|
|
92
|
+
const visibleArgs = input.expanded
|
|
93
|
+
? indexedWrappedText(input.headerArgs, Math.max(1, input.headerArgsWidth))
|
|
94
|
+
: indexedClippedText(input.headerArgs, input.headerArgsWidth);
|
|
95
|
+
const chunks = visibleArgs.length > 0 ? visibleArgs : [{ text: "", start: 0, end: 0 }];
|
|
96
|
+
const continuationIndent = " ".repeat(Math.min(stringDisplayWidth(input.headerPrefix) + 1, Math.max(0, input.width - 1)));
|
|
97
|
+
return chunks.map((chunk, index) => {
|
|
98
|
+
let linePrefix = continuationIndent;
|
|
99
|
+
if (index === 0)
|
|
100
|
+
linePrefix = chunk.text ? `${input.headerPrefix} ` : input.headerPrefix;
|
|
101
|
+
const text = `${linePrefix}${chunk.text}`;
|
|
102
|
+
const argStart = linePrefix.length;
|
|
103
|
+
const customSegments = input.entry.headerArgSegments
|
|
104
|
+
?.flatMap((segment) => shiftSegmentToRange(segment, chunk.start, chunk.end))
|
|
105
|
+
.map((segment) => ({ ...segment, start: segment.start + argStart, end: segment.end + argStart })) ?? [];
|
|
106
|
+
const baseSegments = uncoveredSegments(argStart, text.length, input.toolOutputColor, customSegments);
|
|
107
|
+
return {
|
|
108
|
+
text,
|
|
109
|
+
target: input.target,
|
|
110
|
+
colorOverride: input.toolColor,
|
|
111
|
+
...(input.backgroundOverride ? { backgroundOverride: input.backgroundOverride } : {}),
|
|
112
|
+
segments: [
|
|
113
|
+
...(index === 0 ? [
|
|
114
|
+
{ start: 0, end: input.stateIcon.length, foreground: input.statusIconColor, bold: true },
|
|
115
|
+
{ start: input.stateIcon.length, end: input.headerPrefix.length, bold: true },
|
|
116
|
+
] : []),
|
|
117
|
+
...baseSegments,
|
|
118
|
+
...customSegments,
|
|
119
|
+
],
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function indexedClippedText(text, width) {
|
|
124
|
+
if (width <= 0)
|
|
125
|
+
return [];
|
|
126
|
+
const clipped = sliceByDisplayWidth(text, width);
|
|
127
|
+
return clipped ? [{ text: clipped, start: 0, end: clipped.length }] : [];
|
|
128
|
+
}
|
|
129
|
+
function indexedWrappedText(text, width) {
|
|
130
|
+
if (!text)
|
|
98
131
|
return [];
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
132
|
+
let cursor = 0;
|
|
133
|
+
return wrapDisplayLineByWords(text, width).map((chunk) => {
|
|
134
|
+
const foundAt = text.indexOf(chunk, cursor);
|
|
135
|
+
const start = foundAt >= 0 ? foundAt : cursor;
|
|
136
|
+
const end = start + chunk.length;
|
|
137
|
+
cursor = end;
|
|
138
|
+
return { text: chunk, start, end };
|
|
105
139
|
});
|
|
106
140
|
}
|
|
107
141
|
function uncoveredSegments(start, end, foreground, coveredSegments) {
|
|
@@ -53,6 +53,7 @@ export type AppMouseControllerHost = {
|
|
|
53
53
|
variant?: ToastVariant;
|
|
54
54
|
}): void;
|
|
55
55
|
dismissToast(toastId: number): void;
|
|
56
|
+
activateToastAction(toastId: number): boolean;
|
|
56
57
|
refreshModelUsageStatus(): void | Promise<void>;
|
|
57
58
|
refreshUserMessageJumpMenuItems?(): Promise<void>;
|
|
58
59
|
queueInputFromStatus?(): void | Promise<void>;
|
|
@@ -117,6 +117,11 @@ export class AppMouseController {
|
|
|
117
117
|
return;
|
|
118
118
|
if (target.action === "body")
|
|
119
119
|
return;
|
|
120
|
+
if (target.action === "action") {
|
|
121
|
+
if (this.host.activateToastAction(target.id))
|
|
122
|
+
this.showClickFlashForEvent(event);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
120
125
|
if (this.copyErrorToast(target.id)) {
|
|
121
126
|
this.showClickFlashForEvent(event);
|
|
122
127
|
return;
|
|
@@ -4,6 +4,7 @@ import type { Entry, SessionActivity, SubmittedUserMessage } from "../types.js";
|
|
|
4
4
|
export type AppQueuedMessageControllerHost = {
|
|
5
5
|
runtime(): AgentSessionRuntime | undefined;
|
|
6
6
|
requireRuntime(): AgentSessionRuntime;
|
|
7
|
+
awaitCurrentSessionExtensions(runtime: AgentSessionRuntime): Promise<void>;
|
|
7
8
|
visibleEntries(): readonly Entry[];
|
|
8
9
|
isRunning(): boolean;
|
|
9
10
|
render(): void;
|
|
@@ -59,6 +59,9 @@ export class AppQueuedMessageController {
|
|
|
59
59
|
this.promptSubmissionsInFlight.add(targetSession);
|
|
60
60
|
this.host.setSessionActivity("running");
|
|
61
61
|
try {
|
|
62
|
+
const runtime = this.host.runtime();
|
|
63
|
+
if (runtime?.session === targetSession)
|
|
64
|
+
await this.host.awaitCurrentSessionExtensions(runtime);
|
|
62
65
|
const opts = {};
|
|
63
66
|
if (targetSession.isStreaming)
|
|
64
67
|
opts.streamingBehavior = options.streamingBehavior ?? "steer";
|
|
@@ -29,6 +29,7 @@ export type AppSessionEventControllerState = {
|
|
|
29
29
|
export type AppSessionEventControllerHost = {
|
|
30
30
|
readonly entries: Entry[];
|
|
31
31
|
runtime(): AgentSessionRuntime | undefined;
|
|
32
|
+
awaitSessionExtensions?(runtime: AgentSessionRuntime): Promise<void>;
|
|
32
33
|
conversationViewport(): ConversationViewport;
|
|
33
34
|
conversationViewportColumns?(): number;
|
|
34
35
|
onHistoryWindowPruned?(edge: "top" | "bottom", lineCount: number): void;
|
|
@@ -63,7 +64,12 @@ export type AppSessionEventControllerHost = {
|
|
|
63
64
|
showSnapshot?: boolean;
|
|
64
65
|
}): void;
|
|
65
66
|
observeTodoToolResult(toolName: string, details: unknown, isError?: boolean): void;
|
|
66
|
-
showToast(message: string, kind: "success" | "error" | "warning" | "info"
|
|
67
|
+
showToast(message: string, kind: "success" | "error" | "warning" | "info", options?: {
|
|
68
|
+
action?: {
|
|
69
|
+
label: string;
|
|
70
|
+
onSelect: () => void;
|
|
71
|
+
};
|
|
72
|
+
}): void;
|
|
67
73
|
};
|
|
68
74
|
export declare class AppSessionEventController {
|
|
69
75
|
private readonly host;
|
|
@@ -118,6 +124,9 @@ export declare class AppSessionEventController {
|
|
|
118
124
|
render?: boolean;
|
|
119
125
|
}): Promise<boolean>;
|
|
120
126
|
handleSessionEvent(event: AgentSessionEvent): void;
|
|
127
|
+
private retryFailedTurn;
|
|
128
|
+
private retryFailedTurnAsync;
|
|
129
|
+
private isActiveRuntimeSession;
|
|
121
130
|
addCustomMessageEntry(message: Record<string, unknown>): void;
|
|
122
131
|
findEntry(id: string): Entry | undefined;
|
|
123
132
|
findUserEntry(id: string): Extract<Entry, {
|
|
@@ -328,18 +328,70 @@ export class AppSessionEventController {
|
|
|
328
328
|
this.host.setStatus(`retry ${event.attempt}/${event.maxAttempts}`);
|
|
329
329
|
this.host.emitExtensionEvent(RETRY_ACTIVE_EVENT, { active: true });
|
|
330
330
|
break;
|
|
331
|
-
case "auto_retry_end":
|
|
331
|
+
case "auto_retry_end": {
|
|
332
332
|
this.host.setSessionActivity(this.host.runtime()?.session.isStreaming ? "running" : "idle");
|
|
333
333
|
this.host.restoreSessionStatus();
|
|
334
334
|
this.host.flushAutoUserMessages();
|
|
335
335
|
this.host.emitExtensionEvent(RETRY_ACTIVE_EVENT, { active: false });
|
|
336
|
-
|
|
336
|
+
if (event.success) {
|
|
337
|
+
this.host.showToast("Retry succeeded", "success");
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
const session = this.host.runtime()?.session;
|
|
341
|
+
this.host.showToast(`Retry failed: ${event.finalError}`, "error", session
|
|
342
|
+
? { action: { label: "Retry", onSelect: () => this.retryFailedTurn(session) } }
|
|
343
|
+
: undefined);
|
|
337
344
|
break;
|
|
345
|
+
}
|
|
338
346
|
default:
|
|
339
347
|
break;
|
|
340
348
|
}
|
|
341
349
|
this.host.scheduleRender();
|
|
342
350
|
}
|
|
351
|
+
retryFailedTurn(session) {
|
|
352
|
+
void this.retryFailedTurnAsync(session);
|
|
353
|
+
}
|
|
354
|
+
async retryFailedTurnAsync(session) {
|
|
355
|
+
const runtime = this.host.runtime();
|
|
356
|
+
if (!runtime || !this.isActiveRuntimeSession(runtime, session))
|
|
357
|
+
return;
|
|
358
|
+
if (session.isStreaming || session.isCompacting) {
|
|
359
|
+
this.host.showToast("Retry is unavailable while the session is busy", "warning");
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
this.host.setStatus("retrying failed turn");
|
|
363
|
+
this.host.setSessionActivity("running");
|
|
364
|
+
this.host.render();
|
|
365
|
+
try {
|
|
366
|
+
await this.host.awaitSessionExtensions?.(runtime);
|
|
367
|
+
if (!this.isActiveRuntimeSession(runtime, session))
|
|
368
|
+
return;
|
|
369
|
+
if (session.isStreaming || session.isCompacting) {
|
|
370
|
+
this.host.showToast("Retry is unavailable while the session is busy", "warning");
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
await session.sendCustomMessage({
|
|
374
|
+
customType: "pix-retry",
|
|
375
|
+
content: "Continue the previous task from where you stopped.",
|
|
376
|
+
display: false,
|
|
377
|
+
}, { triggerTurn: true });
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
if (this.isActiveRuntimeSession(runtime, session)) {
|
|
381
|
+
this.host.showToast(`Retry could not start: ${stringifyUnknown(error)}`, "error");
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
finally {
|
|
385
|
+
if (this.isActiveRuntimeSession(runtime, session)) {
|
|
386
|
+
this.host.setSessionStatus(session);
|
|
387
|
+
this.host.setSessionActivity(session.isStreaming || session.isCompacting ? "running" : "idle");
|
|
388
|
+
this.host.render();
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
isActiveRuntimeSession(runtime, session) {
|
|
393
|
+
return this.host.isRunning() && this.host.runtime() === runtime && runtime.session === session;
|
|
394
|
+
}
|
|
343
395
|
addCustomMessageEntry(message) {
|
|
344
396
|
const entry = customMessageEntry(message);
|
|
345
397
|
if (entry)
|
package/dist/app/types.d.ts
CHANGED
|
@@ -81,6 +81,7 @@ export default function sessionTitle(pi) {
|
|
|
81
81
|
const refreshTimers = new Set();
|
|
82
82
|
let pendingGeneration;
|
|
83
83
|
let forkTitleState;
|
|
84
|
+
let resourceCommandTitleSessionId;
|
|
84
85
|
function abortCurrentRequest() {
|
|
85
86
|
controller?.abort();
|
|
86
87
|
controller = undefined;
|
|
@@ -349,6 +350,7 @@ export default function sessionTitle(pi) {
|
|
|
349
350
|
sessionId = ctx.sessionManager.getSessionId();
|
|
350
351
|
pendingGeneration = undefined;
|
|
351
352
|
forkTitleState = undefined;
|
|
353
|
+
resourceCommandTitleSessionId = undefined;
|
|
352
354
|
lastRenderedName = undefined;
|
|
353
355
|
lastRenderedTitle = undefined;
|
|
354
356
|
refreshSessionUi(ctx, { force: true });
|
|
@@ -360,6 +362,7 @@ export default function sessionTitle(pi) {
|
|
|
360
362
|
clearRetryTimer();
|
|
361
363
|
clearRefreshTimers();
|
|
362
364
|
forkTitleState = undefined;
|
|
365
|
+
resourceCommandTitleSessionId = undefined;
|
|
363
366
|
});
|
|
364
367
|
function refreshOnEvent(ctx) {
|
|
365
368
|
refreshSessionUi(ctx);
|
|
@@ -378,24 +381,33 @@ export default function sessionTitle(pi) {
|
|
|
378
381
|
scheduleSessionUiRefresh(ctx);
|
|
379
382
|
if (event.source === "extension")
|
|
380
383
|
return { action: "continue" };
|
|
381
|
-
const fallbackInput = fallbackTitleInputFromPrompt(event.text, event.images);
|
|
382
|
-
if (!fallbackInput)
|
|
383
|
-
return { action: "continue" };
|
|
384
|
-
const titleInput = titleGenerationInputFromPrompt(event.text, event.images) ?? fallbackInput;
|
|
385
|
-
if (event.text.trimStart().startsWith("/"))
|
|
386
|
-
return { action: "continue" };
|
|
387
384
|
const currentSessionId = ctx.sessionManager.getSessionId();
|
|
388
385
|
sessionId = currentSessionId;
|
|
389
386
|
const currentName = currentSessionName(ctx);
|
|
390
387
|
const activeForkTitleState = forkTitleState?.sessionId === currentSessionId ? forkTitleState : undefined;
|
|
391
|
-
if (
|
|
388
|
+
if (event.text.trimStart().startsWith("/")) {
|
|
389
|
+
if (!activeForkTitleState
|
|
390
|
+
&& !currentName
|
|
391
|
+
&& (resourceCommandTitleSessionId === currentSessionId || !hasExistingUserMessage(ctx))) {
|
|
392
|
+
resourceCommandTitleSessionId = currentSessionId;
|
|
393
|
+
}
|
|
394
|
+
return { action: "continue" };
|
|
395
|
+
}
|
|
396
|
+
const fallbackInput = fallbackTitleInputFromPrompt(event.text, event.images);
|
|
397
|
+
if (!fallbackInput)
|
|
398
|
+
return { action: "continue" };
|
|
399
|
+
const titleInput = titleGenerationInputFromPrompt(event.text, event.images) ?? fallbackInput;
|
|
400
|
+
const titleEligibleAfterResourceCommand = resourceCommandTitleSessionId === currentSessionId;
|
|
401
|
+
if (!activeForkTitleState && !titleEligibleAfterResourceCommand && hasExistingUserMessage(ctx)) {
|
|
392
402
|
forkTitleState = undefined;
|
|
393
403
|
return { action: "continue" };
|
|
394
404
|
}
|
|
395
405
|
if (currentName && (!activeForkTitleState || currentName !== activeForkTitleState.inheritedSessionName)) {
|
|
396
406
|
forkTitleState = undefined;
|
|
407
|
+
resourceCommandTitleSessionId = undefined;
|
|
397
408
|
return { action: "continue" };
|
|
398
409
|
}
|
|
410
|
+
resourceCommandTitleSessionId = undefined;
|
|
399
411
|
if (!currentConfig.enabled) {
|
|
400
412
|
applyFallbackSessionTitle(ctx, currentConfig, activeForkTitleState
|
|
401
413
|
? buildForkTitleInput(activeForkTitleState.parentTitle, fallbackInput)
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"vscode-languageserver-protocol": "^3.17.5"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@earendil-works/pi-ai": "0.
|
|
47
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
48
|
-
"@earendil-works/pi-tui": "0.
|
|
46
|
+
"@earendil-works/pi-ai": "0.83.0",
|
|
47
|
+
"@earendil-works/pi-coding-agent": "0.83.0",
|
|
48
|
+
"@earendil-works/pi-tui": "0.83.0",
|
|
49
49
|
"typebox": "*"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
@@ -132,7 +132,7 @@ export function streamAntigravity(model: AntigravityModel, context: Context, opt
|
|
|
132
132
|
provider: model.provider,
|
|
133
133
|
model: model.id,
|
|
134
134
|
usage: baseUsage(),
|
|
135
|
-
stopReason: "
|
|
135
|
+
stopReason: "pending",
|
|
136
136
|
timestamp: Date.now(),
|
|
137
137
|
};
|
|
138
138
|
|
|
@@ -258,12 +258,16 @@ export function streamAntigravity(model: AntigravityModel, context: Context, opt
|
|
|
258
258
|
stream.push({ type: "toolcall_end", contentIndex, toolCall, partial: output });
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
|
-
if (candidate?.finishReason)
|
|
262
|
-
|
|
261
|
+
if (candidate?.finishReason) {
|
|
262
|
+
output.rawStopReason = candidate.finishReason;
|
|
263
|
+
output.stopReason = mapStopReason(candidate.finishReason);
|
|
264
|
+
if (output.content.some((block) => block.type === "toolCall")) output.stopReason = "toolUse";
|
|
265
|
+
}
|
|
263
266
|
}
|
|
264
267
|
closeOpenText();
|
|
265
268
|
closeOpenThinking();
|
|
266
269
|
if (options?.signal?.aborted) throw new Error("Request was aborted");
|
|
270
|
+
if (output.stopReason === "pending") throw new Error("Antigravity stream ended without a finish reason");
|
|
267
271
|
if (output.stopReason === "error" || output.stopReason === "aborted") throw new Error("Antigravity stopped with an error finish reason");
|
|
268
272
|
stream.push({ type: "done", reason: output.stopReason, message: output });
|
|
269
273
|
stream.end();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-ui-extend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Pix: a workspace-first terminal UI for Pi with tabs, readable tool activity, voice input, and bundled agent tools.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"repository": {
|
|
@@ -75,9 +75,9 @@
|
|
|
75
75
|
"prepublishOnly": "npm run check && npm run build:pix && npm run generate-schemas"
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"@earendil-works/pi-ai": "0.
|
|
79
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
80
|
-
"@earendil-works/pi-tui": "0.
|
|
78
|
+
"@earendil-works/pi-ai": "0.83.0",
|
|
79
|
+
"@earendil-works/pi-coding-agent": "0.83.0",
|
|
80
|
+
"@earendil-works/pi-tui": "0.83.0",
|
|
81
81
|
"@mariozechner/clipboard": "^0.3.9",
|
|
82
82
|
"jsonc-parser": "3.3.1",
|
|
83
83
|
"typebox": "1.1.38",
|