pi-ui-extend 1.0.1 → 1.0.4
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 +1 -1
- package/dist/app/app.d.ts +1 -1
- package/dist/app/app.js +15 -2
- package/dist/app/cli/startup-checks.d.ts +22 -0
- package/dist/app/cli/startup-checks.js +147 -0
- package/dist/app/rendering/tool-block-renderer.js +71 -37
- 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/README.md
CHANGED
|
@@ -439,7 +439,7 @@ cd /path/to/project
|
|
|
439
439
|
pix
|
|
440
440
|
```
|
|
441
441
|
|
|
442
|
-
`/idx-update`
|
|
442
|
+
When `indexer-cli` is installed and available on `PATH`, Pix checks it at startup and runs its official updater when needed. Pix silently skips this check when `idx` is absent. `/idx-update` remains available for a manual retry; neither flow refreshes a project's index.
|
|
443
443
|
|
|
444
444
|
### An extension behaves differently
|
|
445
445
|
|
package/dist/app/app.d.ts
CHANGED
|
@@ -72,7 +72,7 @@ export declare class PiUiExtendApp {
|
|
|
72
72
|
private applyPixConfig;
|
|
73
73
|
private updateOutputFilters;
|
|
74
74
|
start(): Promise<void>;
|
|
75
|
-
private
|
|
75
|
+
private checkUpdatesOnStartup;
|
|
76
76
|
private bindCurrentSession;
|
|
77
77
|
private awaitCurrentSessionExtensions;
|
|
78
78
|
private activateRuntime;
|
package/dist/app/app.js
CHANGED
|
@@ -43,6 +43,7 @@ import { AppTerminalController } from "./terminal/terminal-controller.js";
|
|
|
43
43
|
import { TerminalBellSoundController } from "./terminal/terminal-bell-sound-controller.js";
|
|
44
44
|
import { AppToastController } from "./rendering/toast-controller.js";
|
|
45
45
|
import { checkPiUpdate, checkPixUpdate, formatPixStartupUpdateDialog, formatPiStartupUpdateToast } from "./cli/update.js";
|
|
46
|
+
import { checkAndUpdateIdxOnStartup, formatIdxStartupUpdateNotice } from "./cli/startup-checks.js";
|
|
46
47
|
import { AppVoiceController } from "./input/voice-controller.js";
|
|
47
48
|
import { createIsolatedExtensionEventBus } from "./extensions/extension-event-bus.js";
|
|
48
49
|
import { setAppIconTheme } from "./icons.js";
|
|
@@ -864,9 +865,21 @@ export class PiUiExtendApp {
|
|
|
864
865
|
await this.sessionLifecycle.start();
|
|
865
866
|
this.modelUsageController.startPolling();
|
|
866
867
|
this.nerdFontController.ensureInstalledOnStartup();
|
|
867
|
-
this.
|
|
868
|
+
this.checkUpdatesOnStartup();
|
|
868
869
|
}
|
|
869
|
-
|
|
870
|
+
checkUpdatesOnStartup() {
|
|
871
|
+
void checkAndUpdateIdxOnStartup()
|
|
872
|
+
.then((result) => {
|
|
873
|
+
if (!this.running)
|
|
874
|
+
return;
|
|
875
|
+
const notice = formatIdxStartupUpdateNotice(result);
|
|
876
|
+
if (!notice)
|
|
877
|
+
return;
|
|
878
|
+
this.showToast(notice, result.status === "updated" ? "success" : "warning");
|
|
879
|
+
})
|
|
880
|
+
.catch(() => {
|
|
881
|
+
// Startup update checks should never interrupt the TUI.
|
|
882
|
+
});
|
|
870
883
|
void checkPiUpdate()
|
|
871
884
|
.then((result) => {
|
|
872
885
|
if (result.status !== "newer")
|
|
@@ -3,7 +3,29 @@ export type StartupAvailabilityIssue = {
|
|
|
3
3
|
kind: "warning" | "error";
|
|
4
4
|
message: string;
|
|
5
5
|
};
|
|
6
|
+
export type IdxStartupUpdateStatus = "current" | "updated" | "checked" | "skipped" | "unavailable" | "failed";
|
|
7
|
+
export type IdxStartupUpdateResult = {
|
|
8
|
+
status: IdxStartupUpdateStatus;
|
|
9
|
+
previousVersion?: string;
|
|
10
|
+
currentVersion?: string;
|
|
11
|
+
reason?: string;
|
|
12
|
+
};
|
|
13
|
+
type IdxUpdateCommandResult = {
|
|
14
|
+
code: number | null;
|
|
15
|
+
signal: NodeJS.Signals | null;
|
|
16
|
+
stdout: string;
|
|
17
|
+
stderr: string;
|
|
18
|
+
timedOut?: boolean;
|
|
19
|
+
};
|
|
20
|
+
export type IdxStartupUpdateOptions = {
|
|
21
|
+
timeoutMs?: number;
|
|
22
|
+
env?: NodeJS.ProcessEnv;
|
|
23
|
+
runUpdate?: (timeoutMs: number, env: NodeJS.ProcessEnv) => Promise<IdxUpdateCommandResult>;
|
|
24
|
+
};
|
|
6
25
|
export declare function collectStartupAvailabilityIssues(runtime: AgentSessionRuntime): Promise<StartupAvailabilityIssue[]>;
|
|
7
26
|
export declare function checkSelectedModelAuthAvailability(runtime: AgentSessionRuntime): StartupAvailabilityIssue[];
|
|
8
27
|
export declare function checkPiCliAvailability(pathValue?: string): Promise<StartupAvailabilityIssue[]>;
|
|
28
|
+
export declare function checkAndUpdateIdxOnStartup(options?: IdxStartupUpdateOptions): Promise<IdxStartupUpdateResult>;
|
|
29
|
+
export declare function formatIdxStartupUpdateNotice(result: IdxStartupUpdateResult): string | undefined;
|
|
9
30
|
export declare function checkPiToolsSuiteExtensionAvailability(extensionsResult: LoadExtensionsResult): StartupAvailabilityIssue[];
|
|
31
|
+
export {};
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
1
2
|
import { access } from "node:fs/promises";
|
|
2
3
|
import { delimiter, join } from "node:path";
|
|
3
4
|
import { constants as fsConstants } from "node:fs";
|
|
4
5
|
const PI_CLI_COMMAND = "pi";
|
|
5
6
|
const PI_TOOLS_SUITE_EXTENSION_ID = "pi-tools-suite";
|
|
7
|
+
const IDX_CLI_COMMAND = "idx";
|
|
8
|
+
const IDX_UPDATE_TIMEOUT_MS = 600_000;
|
|
9
|
+
const MAX_IDX_UPDATE_OUTPUT_BYTES = 32_000;
|
|
6
10
|
export async function collectStartupAvailabilityIssues(runtime) {
|
|
7
11
|
return [
|
|
8
12
|
...(await checkPiCliAvailability()),
|
|
@@ -33,6 +37,60 @@ export async function checkPiCliAvailability(pathValue = process.env.PATH ?? "")
|
|
|
33
37
|
message: "pi CLI is not available on PATH. Run `pix install` or add pi to PATH before starting pix.",
|
|
34
38
|
}];
|
|
35
39
|
}
|
|
40
|
+
export async function checkAndUpdateIdxOnStartup(options = {}) {
|
|
41
|
+
const env = options.env ?? process.env;
|
|
42
|
+
const disabledReason = startupVersionCheckDisabledReason(env);
|
|
43
|
+
if (disabledReason)
|
|
44
|
+
return { status: "skipped", reason: disabledReason };
|
|
45
|
+
if (!options.runUpdate && !(await executableExistsOnPath(IDX_CLI_COMMAND, env.PATH ?? ""))) {
|
|
46
|
+
return { status: "unavailable" };
|
|
47
|
+
}
|
|
48
|
+
let commandResult;
|
|
49
|
+
try {
|
|
50
|
+
commandResult = await (options.runUpdate ?? runIdxUpdate)(options.timeoutMs ?? IDX_UPDATE_TIMEOUT_MS, env);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (isCommandNotFoundError(error)) {
|
|
54
|
+
return { status: "unavailable" };
|
|
55
|
+
}
|
|
56
|
+
return { status: "failed", reason: errorMessage(error) };
|
|
57
|
+
}
|
|
58
|
+
const output = [commandResult.stdout, commandResult.stderr].filter(Boolean).join("\n").trim();
|
|
59
|
+
if (commandResult.timedOut) {
|
|
60
|
+
return { status: "failed", reason: compactCommandFailure("idx update timed out", output) };
|
|
61
|
+
}
|
|
62
|
+
if (commandResult.code !== 0) {
|
|
63
|
+
const termination = commandResult.signal
|
|
64
|
+
? `idx update terminated by signal ${commandResult.signal}`
|
|
65
|
+
: `idx update exited with code ${commandResult.code ?? "unknown"}`;
|
|
66
|
+
return { status: "failed", reason: compactCommandFailure(termination, output) };
|
|
67
|
+
}
|
|
68
|
+
const updatedVersions = parseUpdatedIdxVersions(output);
|
|
69
|
+
if (updatedVersions)
|
|
70
|
+
return { status: "updated", ...updatedVersions };
|
|
71
|
+
const currentVersion = parseCurrentIdxVersion(output);
|
|
72
|
+
if (currentVersion)
|
|
73
|
+
return { status: "current", currentVersion };
|
|
74
|
+
return { status: "checked" };
|
|
75
|
+
}
|
|
76
|
+
export function formatIdxStartupUpdateNotice(result) {
|
|
77
|
+
switch (result.status) {
|
|
78
|
+
case "updated": {
|
|
79
|
+
if (!result.currentVersion)
|
|
80
|
+
return "idx was updated to the latest version.";
|
|
81
|
+
const previousVersion = result.previousVersion ? ` from ${result.previousVersion}` : "";
|
|
82
|
+
return `idx updated${previousVersion} to ${result.currentVersion}.`;
|
|
83
|
+
}
|
|
84
|
+
case "unavailable":
|
|
85
|
+
return undefined;
|
|
86
|
+
case "failed":
|
|
87
|
+
return `idx startup update failed: ${result.reason ?? "unknown error"}`;
|
|
88
|
+
case "current":
|
|
89
|
+
case "checked":
|
|
90
|
+
case "skipped":
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
36
94
|
export function checkPiToolsSuiteExtensionAvailability(extensionsResult) {
|
|
37
95
|
if (extensionsResult.extensions.some(isPiToolsSuiteExtension))
|
|
38
96
|
return [];
|
|
@@ -64,6 +122,95 @@ async function executableExistsOnPath(command, pathValue) {
|
|
|
64
122
|
}
|
|
65
123
|
return false;
|
|
66
124
|
}
|
|
125
|
+
async function runIdxUpdate(timeoutMs, env) {
|
|
126
|
+
return await new Promise((resolve, reject) => {
|
|
127
|
+
const invocation = idxUpdateInvocation(env);
|
|
128
|
+
const child = spawn(invocation.command, invocation.args, {
|
|
129
|
+
env,
|
|
130
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
131
|
+
windowsHide: true,
|
|
132
|
+
});
|
|
133
|
+
let stdout = "";
|
|
134
|
+
let stderr = "";
|
|
135
|
+
let settled = false;
|
|
136
|
+
let timedOut = false;
|
|
137
|
+
const timer = setTimeout(() => {
|
|
138
|
+
timedOut = true;
|
|
139
|
+
child.kill();
|
|
140
|
+
}, timeoutMs);
|
|
141
|
+
timer.unref();
|
|
142
|
+
child.stdout?.on("data", (chunk) => {
|
|
143
|
+
stdout = appendBoundedOutput(stdout, chunk.toString());
|
|
144
|
+
});
|
|
145
|
+
child.stderr?.on("data", (chunk) => {
|
|
146
|
+
stderr = appendBoundedOutput(stderr, chunk.toString());
|
|
147
|
+
});
|
|
148
|
+
child.on("error", (error) => {
|
|
149
|
+
if (settled)
|
|
150
|
+
return;
|
|
151
|
+
settled = true;
|
|
152
|
+
clearTimeout(timer);
|
|
153
|
+
reject(error);
|
|
154
|
+
});
|
|
155
|
+
child.on("close", (code, signal) => {
|
|
156
|
+
if (settled)
|
|
157
|
+
return;
|
|
158
|
+
settled = true;
|
|
159
|
+
clearTimeout(timer);
|
|
160
|
+
resolve({ code, signal, stdout, stderr, ...(timedOut ? { timedOut: true } : {}) });
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
function idxUpdateInvocation(env) {
|
|
165
|
+
if (process.platform !== "win32")
|
|
166
|
+
return { command: IDX_CLI_COMMAND, args: ["update"] };
|
|
167
|
+
const commandProcessor = env.ComSpec ?? env.COMSPEC ?? process.env.ComSpec ?? process.env.COMSPEC ?? "cmd.exe";
|
|
168
|
+
return { command: commandProcessor, args: ["/d", "/s", "/c", `${IDX_CLI_COMMAND} update`] };
|
|
169
|
+
}
|
|
170
|
+
function appendBoundedOutput(existing, addition) {
|
|
171
|
+
const combined = `${existing}${addition}`;
|
|
172
|
+
if (Buffer.byteLength(combined, "utf8") <= MAX_IDX_UPDATE_OUTPUT_BYTES)
|
|
173
|
+
return combined;
|
|
174
|
+
return Buffer.from(combined, "utf8").subarray(-MAX_IDX_UPDATE_OUTPUT_BYTES).toString("utf8").replace(/^\uFFFD/u, "");
|
|
175
|
+
}
|
|
176
|
+
function parseUpdatedIdxVersions(output) {
|
|
177
|
+
const match = output.match(/(?:Updating|Updated) indexer-cli\s+v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\s*(?:→|->)\s*v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/iu);
|
|
178
|
+
const previousVersion = match?.[1];
|
|
179
|
+
const currentVersion = match?.[2];
|
|
180
|
+
if (!previousVersion || !currentVersion)
|
|
181
|
+
return undefined;
|
|
182
|
+
return { previousVersion, currentVersion };
|
|
183
|
+
}
|
|
184
|
+
function parseCurrentIdxVersion(output) {
|
|
185
|
+
return output.match(/already up to date\s*\(v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\)/iu)?.[1];
|
|
186
|
+
}
|
|
187
|
+
function startupVersionCheckDisabledReason(env) {
|
|
188
|
+
if (truthyEnv(env.PI_OFFLINE))
|
|
189
|
+
return "PI_OFFLINE is set";
|
|
190
|
+
if (truthyEnv(env.PI_SKIP_VERSION_CHECK))
|
|
191
|
+
return "PI_SKIP_VERSION_CHECK is set";
|
|
192
|
+
if (truthyEnv(env.PIX_SKIP_VERSION_CHECK))
|
|
193
|
+
return "PIX_SKIP_VERSION_CHECK is set";
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
function truthyEnv(value) {
|
|
197
|
+
if (!value)
|
|
198
|
+
return false;
|
|
199
|
+
return !["0", "false", "no", "off"].includes(value.toLowerCase());
|
|
200
|
+
}
|
|
201
|
+
function isCommandNotFoundError(error) {
|
|
202
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
203
|
+
}
|
|
204
|
+
function compactCommandFailure(prefix, output) {
|
|
205
|
+
if (!output)
|
|
206
|
+
return prefix;
|
|
207
|
+
const singleLine = output.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean).slice(-4).join(" | ");
|
|
208
|
+
const summary = singleLine.length > 900 ? `${singleLine.slice(0, 897)}...` : singleLine;
|
|
209
|
+
return `${prefix}: ${summary}`;
|
|
210
|
+
}
|
|
211
|
+
function errorMessage(error) {
|
|
212
|
+
return error instanceof Error ? error.message : String(error);
|
|
213
|
+
}
|
|
67
214
|
function isPiToolsSuiteExtension(extension) {
|
|
68
215
|
return [
|
|
69
216
|
extension.path,
|
|
@@ -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) {
|
|
@@ -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": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
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",
|