pi-ui-extend 0.1.19 → 0.1.21
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.d.ts +3 -0
- package/dist/app/app.js +68 -8
- package/dist/app/constants.js +1 -1
- package/dist/app/extensions/extension-ui-controller.js +2 -2
- package/dist/app/input/voice-controller.d.ts +3 -2
- package/dist/app/input/voice-controller.js +9 -0
- package/dist/app/rendering/conversation-entry-renderer.js +39 -9
- package/dist/app/rendering/conversation-tool-renderer.js +1 -1
- package/dist/app/rendering/conversation-viewport.d.ts +1 -5
- package/dist/app/rendering/conversation-viewport.js +9 -16
- package/dist/app/rendering/editor-layout-renderer.js +5 -5
- package/dist/app/rendering/render-controller.js +14 -24
- package/dist/app/rendering/status-line-renderer.d.ts +2 -0
- package/dist/app/rendering/status-line-renderer.js +75 -29
- package/dist/app/rendering/tool-block-renderer.d.ts +2 -0
- package/dist/app/rendering/tool-block-renderer.js +13 -1
- package/dist/app/runtime.d.ts +2 -0
- package/dist/app/runtime.js +27 -4
- package/dist/app/screen/mouse-controller.d.ts +1 -1
- package/dist/app/screen/mouse-controller.js +9 -3
- package/dist/app/screen/screen-styler.js +3 -3
- package/dist/app/session/session-lifecycle-controller.d.ts +2 -0
- package/dist/app/session/session-lifecycle-controller.js +43 -16
- package/dist/app/session/tabs-controller.d.ts +1 -1
- package/dist/app/session/tabs-controller.js +3 -7
- package/dist/app/types.d.ts +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +19 -7
- package/dist/markdown-format.d.ts +2 -0
- package/dist/markdown-format.js +5 -2
- package/dist/syntax-highlight.js +3 -1
- package/dist/theme.d.ts +11 -0
- package/dist/theme.js +56 -15
- package/extensions/question/tui.ts +1 -1
- package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +6 -1
- package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +1 -1
- package/external/pi-tools-suite/src/glm-coding-discipline/index.ts +123 -20
- package/package.json +1 -1
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { compactProgressBarSegments, formatCompactProgressBar } from "../../context-progress-bar.js";
|
|
2
|
-
import { padOrTrimPlain } from "./render-text.js";
|
|
2
|
+
import { ellipsizeDisplay, padOrTrimPlain } from "./render-text.js";
|
|
3
3
|
import { displayIndexForColumn, stringDisplayWidth } from "../../terminal-width.js";
|
|
4
4
|
import { APP_ICONS } from "../icons.js";
|
|
5
5
|
import { resolveColor, resolveModelColor } from "../../config.js";
|
|
6
6
|
const MODEL_USAGE_PROGRESS_BAR_WIDTH = stringDisplayWidth(formatCompactProgressBar(100));
|
|
7
|
-
const
|
|
8
|
-
const INPUT_BORDER_WIDGET_SEPARATOR = "─";
|
|
9
|
-
const STATUS_ICON_BUTTON_WIDTH = 3;
|
|
7
|
+
const STATUS_WIDGET_GAP = " ";
|
|
10
8
|
export class StatusLineRenderer {
|
|
11
9
|
host;
|
|
12
10
|
constructor(host) {
|
|
@@ -18,17 +16,29 @@ export class StatusLineRenderer {
|
|
|
18
16
|
const baseStatus = this.host.currentStatus();
|
|
19
17
|
const workspaceLabel = this.host.statusWorkspaceLabel();
|
|
20
18
|
const modelUsageLabel = this.host.modelUsageStatusLabel();
|
|
21
|
-
const
|
|
22
|
-
const
|
|
19
|
+
const widgetsLayout = this.inputBorderWidgetsLayout(contentWidth);
|
|
20
|
+
const leftWidth = widgetsLayout?.inputBorderWidgetStartColumn
|
|
21
|
+
? Math.max(0, widgetsLayout.inputBorderWidgetStartColumn - 2)
|
|
22
|
+
: contentWidth;
|
|
23
|
+
const fullWorkspaceDetailsLabel = modelUsageLabel ? `${workspaceLabel} ${modelUsageLabel}` : workspaceLabel;
|
|
24
|
+
const contextBarLabel = this.contextBarLabel(baseStatus, leftWidth, fullWorkspaceDetailsLabel);
|
|
23
25
|
const status = contextBarLabel ? `${baseStatus} ${contextBarLabel}` : baseStatus;
|
|
24
26
|
const sessionLabel = "";
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
+
const fittedWorkspaceLabel = this.fitWorkspaceLabel(statusDot, status, workspaceLabel, modelUsageLabel, leftWidth);
|
|
28
|
+
const workspaceDetailsLabel = modelUsageLabel
|
|
29
|
+
? `${fittedWorkspaceLabel ? `${fittedWorkspaceLabel} ` : ""}${modelUsageLabel}`
|
|
30
|
+
: fittedWorkspaceLabel;
|
|
31
|
+
const details = workspaceDetailsLabel ? `${status} ${workspaceDetailsLabel}` : status;
|
|
32
|
+
const leftText = padOrTrimPlain(`${statusDot} ${details}`, leftWidth);
|
|
33
|
+
const text = widgetsLayout?.inputBorderWidgetStartColumn
|
|
34
|
+
? overlayText(padOrTrimPlain(leftText, contentWidth), widgetsLayout.inputBorderWidgetStartColumn, widgetsLayout.text)
|
|
35
|
+
: padOrTrimPlain(leftText, contentWidth);
|
|
27
36
|
return {
|
|
37
|
+
...widgetsLayout,
|
|
28
38
|
details,
|
|
29
39
|
text,
|
|
30
40
|
sessionLabel,
|
|
31
|
-
workspaceLabel,
|
|
41
|
+
workspaceLabel: fittedWorkspaceLabel,
|
|
32
42
|
...(modelUsageLabel ? { modelUsageLabel } : {}),
|
|
33
43
|
...(contextBarLabel ? { contextBarLabel } : {}),
|
|
34
44
|
};
|
|
@@ -76,17 +86,18 @@ export class StatusLineRenderer {
|
|
|
76
86
|
if (!hasParts)
|
|
77
87
|
return undefined;
|
|
78
88
|
const parts = [];
|
|
79
|
-
const
|
|
80
|
-
const
|
|
89
|
+
const gapWidth = stringDisplayWidth(STATUS_WIDGET_GAP);
|
|
90
|
+
const totalWidth = widgets.reduce((total, widget, index) => total + stringDisplayWidth(widget.text) + (index > 0 ? gapWidth : 0), 0);
|
|
91
|
+
const endColumn = Math.max(1, width + 1);
|
|
81
92
|
const startColumn = endColumn - totalWidth;
|
|
82
|
-
if (startColumn <
|
|
93
|
+
if (startColumn < 1)
|
|
83
94
|
return undefined;
|
|
84
95
|
layout.inputBorderWidgetStartColumn = startColumn;
|
|
85
96
|
let nextColumn = startColumn;
|
|
86
97
|
for (const [index, widget] of widgets.entries()) {
|
|
87
98
|
if (index > 0) {
|
|
88
|
-
parts.push(
|
|
89
|
-
nextColumn +=
|
|
99
|
+
parts.push(STATUS_WIDGET_GAP);
|
|
100
|
+
nextColumn += gapWidth;
|
|
90
101
|
}
|
|
91
102
|
parts.push(widget.text);
|
|
92
103
|
widget.assign(nextColumn, widget.text);
|
|
@@ -110,7 +121,6 @@ export class StatusLineRenderer {
|
|
|
110
121
|
}
|
|
111
122
|
inputBorderWidgetSegments(layout, text) {
|
|
112
123
|
const colors = this.host.theme.colors;
|
|
113
|
-
const background = colors.inputBorderWidgetBackground;
|
|
114
124
|
const segments = [];
|
|
115
125
|
const pushWidgetSegment = (widget, foreground) => {
|
|
116
126
|
if (!widget)
|
|
@@ -119,7 +129,6 @@ export class StatusLineRenderer {
|
|
|
119
129
|
start: displayIndexForColumn(text, widget.startColumn),
|
|
120
130
|
end: displayIndexForColumn(text, widget.endColumn),
|
|
121
131
|
foreground,
|
|
122
|
-
background,
|
|
123
132
|
});
|
|
124
133
|
};
|
|
125
134
|
pushWidgetSegment(layout.draftQueueWidget, colors.info);
|
|
@@ -138,14 +147,12 @@ export class StatusLineRenderer {
|
|
|
138
147
|
start: displayIndexForColumn(text, voiceWidget.startColumn),
|
|
139
148
|
end: displayIndexForColumn(text, voiceWidget.micEndColumn),
|
|
140
149
|
foreground: this.host.voiceStatusWidgetActive() ? colors.error : colors.muted,
|
|
141
|
-
background,
|
|
142
150
|
});
|
|
143
151
|
if (voiceWidget.languageEndColumn > voiceWidget.languageStartColumn) {
|
|
144
152
|
segments.push({
|
|
145
153
|
start: displayIndexForColumn(text, voiceWidget.languageStartColumn),
|
|
146
154
|
end: displayIndexForColumn(text, voiceWidget.languageEndColumn),
|
|
147
155
|
foreground: colors.statusForeground,
|
|
148
|
-
background,
|
|
149
156
|
});
|
|
150
157
|
}
|
|
151
158
|
}
|
|
@@ -271,16 +278,10 @@ export class StatusLineRenderer {
|
|
|
271
278
|
end: statusDotStart + APP_ICONS.record.length,
|
|
272
279
|
foreground: this.statusDotColor(),
|
|
273
280
|
}] : [];
|
|
274
|
-
this.
|
|
275
|
-
this.pushUserJumpWidgetSegment(segments, statusText);
|
|
276
|
-
this.pushThinkingExpandWidgetSegment(segments, statusText, layout);
|
|
277
|
-
this.pushCompactToolsWidgetSegment(segments, statusText);
|
|
278
|
-
this.pushTerminalBellSoundWidgetSegment(segments, statusText);
|
|
281
|
+
this.pushStatusWidgetSegments(segments, statusText, layout);
|
|
279
282
|
this.pushWorkspaceSegments(segments, statusText, layout.workspaceLabel);
|
|
280
283
|
if (layout.modelUsageLabel)
|
|
281
284
|
this.pushModelUsageSegments(segments, statusText, layout.modelUsageLabel);
|
|
282
|
-
this.pushPromptEnhancerWidgetSegment(segments, statusText);
|
|
283
|
-
this.pushVoiceWidgetSegment(segments, statusText);
|
|
284
285
|
const session = this.host.session;
|
|
285
286
|
if (!session)
|
|
286
287
|
return segments;
|
|
@@ -304,6 +305,50 @@ export class StatusLineRenderer {
|
|
|
304
305
|
this.pushSegment(segments, statusText.lastIndexOf(layout.sessionLabel), layout.sessionLabel.length, this.host.theme.colors.selectionForeground);
|
|
305
306
|
return segments;
|
|
306
307
|
}
|
|
308
|
+
fitWorkspaceLabel(statusDot, status, workspaceLabel, modelUsageLabel, width) {
|
|
309
|
+
const modelUsageSuffix = modelUsageLabel ? ` ${modelUsageLabel}` : "";
|
|
310
|
+
const available = width - stringDisplayWidth(`${statusDot} ${status} `) - stringDisplayWidth(modelUsageSuffix);
|
|
311
|
+
if (available <= 0)
|
|
312
|
+
return "";
|
|
313
|
+
return ellipsizeDisplay(workspaceLabel, available);
|
|
314
|
+
}
|
|
315
|
+
pushStatusWidgetSegments(segments, statusText, layout) {
|
|
316
|
+
const colors = this.host.theme.colors;
|
|
317
|
+
const widgets = [
|
|
318
|
+
{ widget: layout.draftQueueWidget, foreground: colors.info },
|
|
319
|
+
{ widget: layout.promptEnhancerWidget, foreground: this.host.promptEnhancerStatusWidgetActive()
|
|
320
|
+
? colors.warning
|
|
321
|
+
: this.host.promptEnhancerStatusWidgetEnabled()
|
|
322
|
+
? colors.info
|
|
323
|
+
: colors.muted },
|
|
324
|
+
{ widget: layout.userJumpWidget, foreground: this.host.userMessageJumpMenuActive?.() ? colors.info : colors.muted },
|
|
325
|
+
{ widget: layout.terminalBellSoundWidget, foreground: this.host.terminalBellSoundStatusWidgetEnabled() ? colors.info : colors.muted },
|
|
326
|
+
{ widget: layout.thinkingExpandWidget, foreground: this.host.allThinkingExpandedActive?.() ? colors.info : colors.muted },
|
|
327
|
+
{ widget: layout.compactToolsWidget, foreground: this.host.superCompactToolsActive?.() ? colors.info : colors.muted },
|
|
328
|
+
].filter((entry) => Boolean(entry.widget));
|
|
329
|
+
for (const { widget, foreground } of widgets) {
|
|
330
|
+
segments.push({
|
|
331
|
+
start: displayIndexForColumn(statusText, widget.startColumn),
|
|
332
|
+
end: displayIndexForColumn(statusText, widget.endColumn),
|
|
333
|
+
foreground,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
const voiceWidget = layout.voiceWidget;
|
|
337
|
+
if (voiceWidget) {
|
|
338
|
+
segments.push({
|
|
339
|
+
start: displayIndexForColumn(statusText, voiceWidget.startColumn),
|
|
340
|
+
end: displayIndexForColumn(statusText, voiceWidget.micEndColumn),
|
|
341
|
+
foreground: this.host.voiceStatusWidgetActive() ? colors.error : colors.muted,
|
|
342
|
+
});
|
|
343
|
+
if (voiceWidget.languageEndColumn > voiceWidget.languageStartColumn) {
|
|
344
|
+
segments.push({
|
|
345
|
+
start: displayIndexForColumn(statusText, voiceWidget.languageStartColumn),
|
|
346
|
+
end: displayIndexForColumn(statusText, voiceWidget.languageEndColumn),
|
|
347
|
+
foreground: colors.statusForeground,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
307
352
|
pushPromptEnhancerWidgetSegment(segments, statusText) {
|
|
308
353
|
const widgetText = this.host.promptEnhancerStatusWidgetText();
|
|
309
354
|
const start = statusText.lastIndexOf(widgetText);
|
|
@@ -497,7 +542,7 @@ export class StatusLineRenderer {
|
|
|
497
542
|
};
|
|
498
543
|
}
|
|
499
544
|
iconButtonText(icon) {
|
|
500
|
-
return
|
|
545
|
+
return icon;
|
|
501
546
|
}
|
|
502
547
|
voiceBorderWidgetText(widgetText) {
|
|
503
548
|
const parts = this.voiceBorderWidgetParts(widgetText);
|
|
@@ -505,21 +550,22 @@ export class StatusLineRenderer {
|
|
|
505
550
|
return "";
|
|
506
551
|
const micButton = this.iconButtonText(parts.buttonIconText);
|
|
507
552
|
if (parts.languageText) {
|
|
508
|
-
return `${micButton}${
|
|
553
|
+
return `${micButton}${STATUS_WIDGET_GAP}${parts.languageText}`;
|
|
509
554
|
}
|
|
510
555
|
return micButton;
|
|
511
556
|
}
|
|
512
557
|
voiceWidgetLayout(startColumn, sourceText, widgetText) {
|
|
513
558
|
const parts = this.voiceBorderWidgetParts(sourceText);
|
|
559
|
+
const micWidth = parts?.buttonIconText ? stringDisplayWidth(parts.buttonIconText) : stringDisplayWidth(widgetText);
|
|
514
560
|
const languageStartOffset = parts?.languageText
|
|
515
|
-
?
|
|
561
|
+
? micWidth + stringDisplayWidth(STATUS_WIDGET_GAP)
|
|
516
562
|
: stringDisplayWidth(widgetText);
|
|
517
563
|
const languageEndOffset = parts?.languageText
|
|
518
564
|
? languageStartOffset + stringDisplayWidth(parts.languageText)
|
|
519
565
|
: languageStartOffset;
|
|
520
566
|
return {
|
|
521
567
|
startColumn,
|
|
522
|
-
micEndColumn: startColumn +
|
|
568
|
+
micEndColumn: startColumn + micWidth,
|
|
523
569
|
languageStartColumn: startColumn + languageStartOffset,
|
|
524
570
|
languageEndColumn: startColumn + languageEndOffset,
|
|
525
571
|
endColumn: startColumn + stringDisplayWidth(widgetText),
|
|
@@ -23,5 +23,7 @@ export type ToolBlockEntry = {
|
|
|
23
23
|
};
|
|
24
24
|
export type ToolBlockRenderOptions = {
|
|
25
25
|
superCompact?: boolean;
|
|
26
|
+
backgroundOverride?: string;
|
|
27
|
+
skipHeaderBackground?: boolean;
|
|
26
28
|
};
|
|
27
29
|
export declare function renderToolBlock(entry: ToolBlockEntry, rule: ResolvedToolRule, width: number, colors: Theme["colors"], options?: ToolBlockRenderOptions): RenderedLine[];
|
|
@@ -10,7 +10,10 @@ export function renderToolBlock(entry, rule, width, colors, options = {}) {
|
|
|
10
10
|
const stateIcon = toolStatusIcon(entry);
|
|
11
11
|
const toolColor = resolveColor(rule.color, colors);
|
|
12
12
|
const toolOutputColor = colors.statusForeground;
|
|
13
|
-
const headerLabel = entry.headerLabel ?? entry.toolName;
|
|
13
|
+
const headerLabel = (entry.headerLabel ?? entry.toolName).toLowerCase();
|
|
14
|
+
const bg = options.backgroundOverride;
|
|
15
|
+
const applyBackground = bg ? (lines) => { for (const line of lines)
|
|
16
|
+
line.backgroundOverride = bg; } : (_lines) => { };
|
|
14
17
|
const headerPrefix = headerLabel ? `${stateIcon} ${headerLabel}` : stateIcon;
|
|
15
18
|
const headerArgs = formatToolHeaderArgs(entry.headerArgs);
|
|
16
19
|
const headerArgsWidth = width - stringDisplayWidth(headerPrefix) - 1;
|
|
@@ -22,14 +25,22 @@ export function renderToolBlock(entry, rule, width, colors, options = {}) {
|
|
|
22
25
|
text: header,
|
|
23
26
|
target,
|
|
24
27
|
colorOverride: toolColor,
|
|
28
|
+
...(options.backgroundOverride && !options.skipHeaderBackground ? { backgroundOverride: options.backgroundOverride } : {}),
|
|
25
29
|
segments: [
|
|
26
30
|
{ start: 0, end: stateIcon.length, foreground: toolStatusIconColor(entry, colors), bold: true },
|
|
31
|
+
{ start: stateIcon.length, end: headerPrefix.length, bold: true },
|
|
27
32
|
...headerArgsStyledSegments(headerArgsStart, clippedHeaderArgs.length, entry.headerArgsSegments, colors),
|
|
28
33
|
],
|
|
29
34
|
};
|
|
30
35
|
const headerLines = [headerLine];
|
|
31
36
|
if (expanded) {
|
|
32
37
|
headerLines.push(...renderToolBodyLines(entry.expandedText, width, target, toolOutputColor, entry.bodyStyle, colors, entry.syntaxHighlight, entry.bodyWrap, hasLspDiagnostics, entry.bodyLineStyles, entry.preserveAnsi));
|
|
38
|
+
if (options.skipHeaderBackground && headerLines.length > 1) {
|
|
39
|
+
applyBackground(headerLines.slice(1));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
applyBackground(headerLines);
|
|
43
|
+
}
|
|
33
44
|
return headerLines;
|
|
34
45
|
}
|
|
35
46
|
if (rule.compactHidden || (rule.defaultExpanded === true && !options.superCompact))
|
|
@@ -39,6 +50,7 @@ export function renderToolBlock(entry, rule, width, colors, options = {}) {
|
|
|
39
50
|
return headerLines;
|
|
40
51
|
if (!options.superCompact) {
|
|
41
52
|
headerLines.push(...renderCollapsedPreviewLines(entry, body, rule, width, target, toolOutputColor, colors, hasLspDiagnostics));
|
|
53
|
+
applyBackground(headerLines);
|
|
42
54
|
return headerLines;
|
|
43
55
|
}
|
|
44
56
|
const preview = collapsedInlinePreview(body, rule, entry.preserveAnsi);
|
package/dist/app/runtime.d.ts
CHANGED
|
@@ -33,9 +33,11 @@ export declare function bundledSkillsInstallPath(homeDir?: string): string;
|
|
|
33
33
|
export declare function ensurePiToolsSuiteExtensionInstalled(options?: PiToolsSuiteInstallOptions): Promise<PiToolsSuiteInstallResult>;
|
|
34
34
|
export declare function ensureBundledSkillsInstalled(options?: BundledSkillsInstallOptions): Promise<BundledSkillsInstallResult>;
|
|
35
35
|
export declare function getBundledExtensionPaths(): string[];
|
|
36
|
+
export declare function getBundledExtensionPathsAsync(): Promise<string[]>;
|
|
36
37
|
export declare function prioritizeBundledQuestionExtension(base: LoadExtensionsResult, questionExtensionPath?: string): LoadExtensionsResult;
|
|
37
38
|
export type CreatePixRuntimeOptions = {
|
|
38
39
|
eventBus?: EventBus;
|
|
40
|
+
config?: PixConfig;
|
|
39
41
|
};
|
|
40
42
|
type RuntimeSessionManagerModelState = Pick<SessionManager, "getEntries" | "getBranch">;
|
|
41
43
|
export declare function resolvePixRuntimeModelRef(options: Pick<AppOptions, "modelRef">, sessionManager: RuntimeSessionManagerModelState, config?: PixConfig): string | undefined;
|
package/dist/app/runtime.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { cp, lstat, mkdir, readlink, realpath, rm, symlink } from "node:fs/promises";
|
|
2
|
+
import { access, cp, lstat, mkdir, readlink, realpath, rm, symlink } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -40,7 +40,7 @@ export function bundledSkillsInstallPath(homeDir = homedir()) {
|
|
|
40
40
|
export async function ensurePiToolsSuiteExtensionInstalled(options = {}) {
|
|
41
41
|
const sourcePath = resolve(options.sourcePath ?? piToolsSuiteExtensionSourcePath());
|
|
42
42
|
const targetPath = resolve(options.targetPath ?? piToolsSuiteExtensionInstallPath(options.agentDir));
|
|
43
|
-
if (!
|
|
43
|
+
if (!(await extensionEntryExistsAsync(sourcePath))) {
|
|
44
44
|
return { action: "missing-source", sourcePath, targetPath };
|
|
45
45
|
}
|
|
46
46
|
await mkdir(dirname(targetPath), { recursive: true });
|
|
@@ -84,6 +84,14 @@ export function getBundledExtensionPaths() {
|
|
|
84
84
|
bundledTerminalBellExtensionPath(),
|
|
85
85
|
].filter(extensionEntryExists);
|
|
86
86
|
}
|
|
87
|
+
export async function getBundledExtensionPathsAsync() {
|
|
88
|
+
const paths = await Promise.all([
|
|
89
|
+
bundledQuestionExtensionPath(),
|
|
90
|
+
bundledSessionTitleExtensionPath(),
|
|
91
|
+
bundledTerminalBellExtensionPath(),
|
|
92
|
+
].map(async (extensionPath) => await extensionEntryExistsAsync(extensionPath) ? extensionPath : undefined));
|
|
93
|
+
return paths.filter((path) => path !== undefined);
|
|
94
|
+
}
|
|
87
95
|
export function prioritizeBundledQuestionExtension(base, questionExtensionPath = bundledQuestionExtensionPath()) {
|
|
88
96
|
const bundledQuestionExtensions = base.extensions.filter((extension) => isBundledQuestionExtension(extension, questionExtensionPath));
|
|
89
97
|
if (bundledQuestionExtensions.length === 0)
|
|
@@ -101,6 +109,21 @@ export function prioritizeBundledQuestionExtension(base, questionExtensionPath =
|
|
|
101
109
|
function extensionEntryExists(extensionPath) {
|
|
102
110
|
return existsSync(join(extensionPath, "index.ts")) || existsSync(join(extensionPath, "index.js"));
|
|
103
111
|
}
|
|
112
|
+
async function extensionEntryExistsAsync(extensionPath) {
|
|
113
|
+
try {
|
|
114
|
+
await access(join(extensionPath, "index.ts"));
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
try {
|
|
119
|
+
await access(join(extensionPath, "index.js"));
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
104
127
|
function extensionSymlinkType() {
|
|
105
128
|
return process.platform === "win32" ? "junction" : "dir";
|
|
106
129
|
}
|
|
@@ -198,13 +221,13 @@ export function resolveSessionModelRefFromTail(entries) {
|
|
|
198
221
|
export async function createPixRuntime(options, runtimeOptions = {}) {
|
|
199
222
|
const agentDir = getAgentDir();
|
|
200
223
|
const createRuntime = async ({ cwd, sessionManager, sessionStartEvent }) => {
|
|
201
|
-
const config = loadPixConfig(cwd);
|
|
224
|
+
const config = runtimeOptions.config ?? loadPixConfig(cwd);
|
|
202
225
|
const effectiveModelRef = resolvePixRuntimeModelRef(options, sessionManager, config);
|
|
203
226
|
const parsedModel = effectiveModelRef ? parseModelRef(effectiveModelRef) : undefined;
|
|
204
227
|
const initialThinkingLevel = resolvePixRuntimeInitialThinkingLevel(options, sessionManager, config);
|
|
205
228
|
await ensureBundledSkillsInstalledOnce();
|
|
206
229
|
await ensurePiToolsSuiteExtensionInstalledOnce({ agentDir });
|
|
207
|
-
const bundledExtensionPaths =
|
|
230
|
+
const bundledExtensionPaths = await getBundledExtensionPathsAsync();
|
|
208
231
|
const services = await createAgentSessionServices({
|
|
209
232
|
cwd,
|
|
210
233
|
agentDir,
|
|
@@ -20,7 +20,7 @@ export type InputFrameCopyRows = {
|
|
|
20
20
|
inputStartRow: number;
|
|
21
21
|
inputEndRow: number;
|
|
22
22
|
inputSeparatorRow: number;
|
|
23
|
-
inputBottomSeparatorRow
|
|
23
|
+
inputBottomSeparatorRow?: number;
|
|
24
24
|
};
|
|
25
25
|
export type AppMouseControllerHost = {
|
|
26
26
|
terminalColumns(): number;
|
|
@@ -784,12 +784,15 @@ export class AppMouseController {
|
|
|
784
784
|
const topOffset = editorLayoutTopOffset(tabPanelRows);
|
|
785
785
|
const toScreenRow = (layoutRow) => Math.max(1, Math.min(terminalRows, topOffset + layoutRow));
|
|
786
786
|
const toScreenRowExclusive = (layoutRow) => Math.max(1, Math.min(terminalRows + 1, topOffset + layoutRow));
|
|
787
|
-
|
|
787
|
+
const inputFrame = {
|
|
788
788
|
inputStartRow: toScreenRow(layout.inputStartRow),
|
|
789
789
|
inputEndRow: toScreenRowExclusive(layout.inputStartRow + layout.renderedInput.lines.length),
|
|
790
790
|
inputSeparatorRow: toScreenRow(layout.inputSeparatorRow),
|
|
791
|
-
inputBottomSeparatorRow: toScreenRow(layout.inputBottomSeparatorRow),
|
|
792
791
|
};
|
|
792
|
+
if (layout.inputBottomSeparatorRow !== undefined) {
|
|
793
|
+
inputFrame.inputBottomSeparatorRow = toScreenRow(layout.inputBottomSeparatorRow);
|
|
794
|
+
}
|
|
795
|
+
return inputFrame;
|
|
793
796
|
}
|
|
794
797
|
getSelectedConversationText(anchor, current) {
|
|
795
798
|
const range = orderedConversationSelection(anchor, current);
|
|
@@ -935,7 +938,10 @@ function orderedConversationSelection(anchor, current) {
|
|
|
935
938
|
return anchor.x <= current.x ? { start: anchor, end: current } : { start: current, end: anchor };
|
|
936
939
|
}
|
|
937
940
|
export function screenSelectionLineText(row, text, startColumn, endColumn, inputFrame) {
|
|
938
|
-
if (inputFrame &&
|
|
941
|
+
if (inputFrame && row === inputFrame.inputSeparatorRow) {
|
|
942
|
+
return undefined;
|
|
943
|
+
}
|
|
944
|
+
if (inputFrame?.inputBottomSeparatorRow !== undefined && row === inputFrame.inputBottomSeparatorRow) {
|
|
939
945
|
return undefined;
|
|
940
946
|
}
|
|
941
947
|
return sliceByDisplayColumns(text, startColumn, endColumn);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ANSI_RESET, colorize } from "../../theme.js";
|
|
1
|
+
import { ANSI_RESET, ansiStylePrefix, colorize } from "../../theme.js";
|
|
2
2
|
import { renderMarkdownLine } from "../../markdown-format.js";
|
|
3
3
|
import { syntaxHighlightSegmentsForLine } from "../../syntax-highlight.js";
|
|
4
4
|
import { displayIndexForColumn } from "../../terminal-width.js";
|
|
@@ -75,7 +75,7 @@ export class ScreenStyler {
|
|
|
75
75
|
}
|
|
76
76
|
styleInputLine(row, text, tagSpans, suggestionSpans, width, tagColor, suggestionColor, frameColor) {
|
|
77
77
|
const colors = this.host.theme.colors;
|
|
78
|
-
const baseOptions = { foreground: colors.
|
|
78
|
+
const baseOptions = { foreground: colors.userForeground };
|
|
79
79
|
if (this.selectionRangeForRow(row, width, text))
|
|
80
80
|
return this.styleLine(row, text, width, baseOptions);
|
|
81
81
|
const plain = padOrTrimPlain(text, width);
|
|
@@ -105,7 +105,7 @@ export class ScreenStyler {
|
|
|
105
105
|
return chunks.join("");
|
|
106
106
|
}
|
|
107
107
|
styleAnsiLine(text, options) {
|
|
108
|
-
const prefix =
|
|
108
|
+
const prefix = ansiStylePrefix(options);
|
|
109
109
|
if (!prefix)
|
|
110
110
|
return text;
|
|
111
111
|
return `${prefix}${text.replaceAll(ANSI_RESET, `${ANSI_RESET}${prefix}`)}${ANSI_RESET}`;
|
|
@@ -14,6 +14,7 @@ export type AppSessionLifecycleHost = {
|
|
|
14
14
|
inputEditor(): InputEditor;
|
|
15
15
|
enableTerminal(): void;
|
|
16
16
|
disposeRuntimeForQuit(runtime: AgentSessionRuntime): Promise<void>;
|
|
17
|
+
loadStartupConfig(): Promise<void>;
|
|
17
18
|
loadRequestHistory(): Promise<void>;
|
|
18
19
|
startSubagentsPolling(): void;
|
|
19
20
|
closeSdkMenuForBind(): void;
|
|
@@ -67,6 +68,7 @@ export declare class AppSessionLifecycleController {
|
|
|
67
68
|
loadSessionHistory(): void;
|
|
68
69
|
requireRuntime(): AgentSessionRuntime;
|
|
69
70
|
private bindSessionExtensions;
|
|
71
|
+
private collectAvailabilityIssues;
|
|
70
72
|
private isCurrentRuntimeSession;
|
|
71
73
|
private extensionUiScope;
|
|
72
74
|
}
|
|
@@ -16,11 +16,17 @@ export class AppSessionLifecycleController {
|
|
|
16
16
|
throw new Error("pi-ui-extend needs an interactive TTY");
|
|
17
17
|
}
|
|
18
18
|
this.host.enableTerminal();
|
|
19
|
-
await this.host.loadRequestHistory();
|
|
20
19
|
this.host.setRunning(true);
|
|
21
20
|
this.host.startSubagentsPolling();
|
|
22
21
|
this.host.render();
|
|
22
|
+
void this.host.loadRequestHistory().catch((error) => {
|
|
23
|
+
if (!this.host.isRunning())
|
|
24
|
+
return;
|
|
25
|
+
this.host.addEntry({ id: createId("warning"), kind: "system", text: `Request history failed to load: ${stringifyUnknown(error)}` });
|
|
26
|
+
this.host.render();
|
|
27
|
+
});
|
|
23
28
|
try {
|
|
29
|
+
await this.host.loadStartupConfig();
|
|
24
30
|
const runtime = await this.host.createRuntime();
|
|
25
31
|
if (!this.host.isRunning()) {
|
|
26
32
|
await this.host.disposeRuntimeForQuit(runtime);
|
|
@@ -34,21 +40,6 @@ export class AppSessionLifecycleController {
|
|
|
34
40
|
if (isEmptyStartupSession(runtime)) {
|
|
35
41
|
this.host.addEntry({ id: createId("system"), kind: "system", text: createStartupInfoMessage(runtime) });
|
|
36
42
|
}
|
|
37
|
-
await this.host.restoreTabsAfterStartup();
|
|
38
|
-
const availabilityIssues = await collectStartupAvailabilityIssues(runtime);
|
|
39
|
-
for (const issue of availabilityIssues) {
|
|
40
|
-
this.host.addEntry({
|
|
41
|
-
id: createId(issue.kind),
|
|
42
|
-
kind: issue.kind === "error" ? "error" : "system",
|
|
43
|
-
text: issue.message,
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
if (availabilityIssues.some((issue) => issue.kind === "error")) {
|
|
47
|
-
this.host.showToast("Startup dependency unavailable", "error");
|
|
48
|
-
}
|
|
49
|
-
else if (availabilityIssues.length > 0) {
|
|
50
|
-
this.host.showToast("Startup dependency warning", "warning");
|
|
51
|
-
}
|
|
52
43
|
if (runtime.modelFallbackMessage) {
|
|
53
44
|
this.host.addEntry({ id: createId("system"), kind: "system", text: runtime.modelFallbackMessage });
|
|
54
45
|
}
|
|
@@ -59,6 +50,14 @@ export class AppSessionLifecycleController {
|
|
|
59
50
|
this.host.setSessionStatus(runtime.session);
|
|
60
51
|
this.host.setSessionActivity(runtime.session.isStreaming ? "running" : "idle");
|
|
61
52
|
this.host.render();
|
|
53
|
+
void this.collectAvailabilityIssues(runtime);
|
|
54
|
+
void this.host.restoreTabsAfterStartup().catch((error) => {
|
|
55
|
+
if (!this.host.isRunning())
|
|
56
|
+
return;
|
|
57
|
+
this.host.addEntry({ id: createId("warning"), kind: "system", text: `Tab restore failed: ${stringifyUnknown(error)}` });
|
|
58
|
+
this.host.showToast("Could not restore tabs", "warning");
|
|
59
|
+
this.host.render();
|
|
60
|
+
});
|
|
62
61
|
}
|
|
63
62
|
catch (error) {
|
|
64
63
|
this.host.addEntry({ id: createId("error"), kind: "error", text: stringifyUnknown(error) });
|
|
@@ -154,6 +153,34 @@ export class AppSessionLifecycleController {
|
|
|
154
153
|
this.extensionBindSession = session;
|
|
155
154
|
return promise;
|
|
156
155
|
}
|
|
156
|
+
async collectAvailabilityIssues(runtime) {
|
|
157
|
+
try {
|
|
158
|
+
const availabilityIssues = await collectStartupAvailabilityIssues(runtime);
|
|
159
|
+
if (!this.host.isRunning() || this.host.runtime() !== runtime)
|
|
160
|
+
return;
|
|
161
|
+
for (const issue of availabilityIssues) {
|
|
162
|
+
this.host.addEntry({
|
|
163
|
+
id: createId(issue.kind),
|
|
164
|
+
kind: issue.kind === "error" ? "error" : "system",
|
|
165
|
+
text: issue.message,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
if (availabilityIssues.some((issue) => issue.kind === "error")) {
|
|
169
|
+
this.host.showToast("Startup dependency unavailable", "error");
|
|
170
|
+
}
|
|
171
|
+
else if (availabilityIssues.length > 0) {
|
|
172
|
+
this.host.showToast("Startup dependency warning", "warning");
|
|
173
|
+
}
|
|
174
|
+
if (availabilityIssues.length > 0)
|
|
175
|
+
this.host.render();
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (!this.host.isRunning() || this.host.runtime() !== runtime)
|
|
179
|
+
return;
|
|
180
|
+
this.host.addEntry({ id: createId("warning"), kind: "system", text: `Startup dependency check failed: ${stringifyUnknown(error)}` });
|
|
181
|
+
this.host.render();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
157
184
|
isCurrentRuntimeSession(runtime, session) {
|
|
158
185
|
return this.host.isRunning() && this.host.runtime() === runtime && runtime.session === session;
|
|
159
186
|
}
|
|
@@ -6,7 +6,7 @@ import type { AppOptions, Entry, SessionActivity, SessionTab, SubmittedUserMessa
|
|
|
6
6
|
export type TabInputState = InputEditorDraftState;
|
|
7
7
|
export type AppTabsControllerHost = {
|
|
8
8
|
readonly options: AppOptions;
|
|
9
|
-
readonly maxProjectSessions?: number;
|
|
9
|
+
readonly maxProjectSessions?: number | (() => number | undefined);
|
|
10
10
|
readonly blinkController: AppBlinkController;
|
|
11
11
|
runtime(): AgentSessionRuntime | undefined;
|
|
12
12
|
createRuntimeForNewSession(): Promise<AgentSessionRuntime>;
|
|
@@ -220,17 +220,12 @@ export class AppTabsController {
|
|
|
220
220
|
}
|
|
221
221
|
this.syncActiveTabFromRuntime({ save: false });
|
|
222
222
|
this.settleStartupTabPlaceholders();
|
|
223
|
-
this.host.resetSessionView();
|
|
224
|
-
if (this.activeTabId)
|
|
225
|
-
this.restoreDeferredUserMessages(this.activeTabId);
|
|
226
|
-
this.host.loadSessionHistory();
|
|
227
|
-
this.host.setSessionStatus(restoredRuntime.session);
|
|
228
|
-
this.host.setSessionActivity(this.sessionActivity(restoredRuntime.session));
|
|
229
223
|
if (this.activeTabId)
|
|
230
224
|
this.restoreInputState(this.activeTabId);
|
|
231
225
|
await this.saveTabs();
|
|
232
226
|
this.scheduleProjectSessionRetention();
|
|
233
227
|
this.scheduleTabPrewarm();
|
|
228
|
+
await this.loadActiveSessionHistory(restoredRuntime);
|
|
234
229
|
}
|
|
235
230
|
async openNewTab() {
|
|
236
231
|
if (this.pendingActiveTabId) {
|
|
@@ -1377,7 +1372,8 @@ export class AppTabsController {
|
|
|
1377
1372
|
return preserved;
|
|
1378
1373
|
}
|
|
1379
1374
|
maxProjectSessions() {
|
|
1380
|
-
const
|
|
1375
|
+
const configured = this.host.maxProjectSessions;
|
|
1376
|
+
const value = typeof configured === "function" ? configured() : configured;
|
|
1381
1377
|
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
1382
1378
|
}
|
|
1383
1379
|
}
|
package/dist/app/types.d.ts
CHANGED
|
@@ -447,7 +447,7 @@ export type EditorLayout = {
|
|
|
447
447
|
belowEditorLines: readonly RenderedLine[];
|
|
448
448
|
inputStartRow: number;
|
|
449
449
|
inputSeparatorRow: number;
|
|
450
|
-
inputBottomSeparatorRow
|
|
450
|
+
inputBottomSeparatorRow?: number;
|
|
451
451
|
bodyHeight: number;
|
|
452
452
|
};
|
|
453
453
|
export type MouseEvent = {
|
package/dist/config.d.ts
CHANGED
|
@@ -72,6 +72,7 @@ export type PixConfig = {
|
|
|
72
72
|
};
|
|
73
73
|
export declare function getPixConfigPath(homeDir?: string): string;
|
|
74
74
|
export declare function getProjectPixConfigPath(cwd: string): string;
|
|
75
|
+
export declare function defaultPixConfig(): PixConfig;
|
|
75
76
|
export declare function resolveDefaultModelRef(config: PixConfig): string | undefined;
|
|
76
77
|
export declare function savePixDefaultModel(modelRef: string): DefaultModelConfig | undefined;
|
|
77
78
|
export declare function savePixDefaultThinking(thinking: string, fallbackModelRef?: string): DefaultModelConfig | undefined;
|
package/dist/config.js
CHANGED
|
@@ -285,19 +285,31 @@ function numberInRange(value, fallback, min, max) {
|
|
|
285
285
|
const rounded = Math.round(value);
|
|
286
286
|
return Math.min(max, Math.max(min, rounded));
|
|
287
287
|
}
|
|
288
|
-
function defaultPixConfig() {
|
|
288
|
+
export function defaultPixConfig() {
|
|
289
289
|
return {
|
|
290
|
-
toolRenderer: DEFAULT_TOOL_RENDERER,
|
|
291
|
-
outputFilters: DEFAULT_OUTPUT_FILTERS,
|
|
292
|
-
promptEnhancer: DEFAULT_PROMPT_ENHANCER,
|
|
293
|
-
autocomplete: DEFAULT_AUTOCOMPLETE,
|
|
294
|
-
modelColors: DEFAULT_MODEL_COLORS,
|
|
290
|
+
toolRenderer: cloneToolRendererConfig(DEFAULT_TOOL_RENDERER),
|
|
291
|
+
outputFilters: { patterns: [...DEFAULT_OUTPUT_FILTERS.patterns] },
|
|
292
|
+
promptEnhancer: { ...DEFAULT_PROMPT_ENHANCER },
|
|
293
|
+
autocomplete: { ...DEFAULT_AUTOCOMPLETE },
|
|
294
|
+
modelColors: { rules: { ...DEFAULT_MODEL_COLORS.rules } },
|
|
295
295
|
iconTheme: { name: resolveAppIconThemeNameFromEnv() },
|
|
296
|
-
dictation: DEFAULT_DICTATION,
|
|
296
|
+
dictation: cloneDictationConfig(DEFAULT_DICTATION),
|
|
297
297
|
ignoreContextFiles: false,
|
|
298
298
|
maxProjectSessions: 0,
|
|
299
299
|
};
|
|
300
300
|
}
|
|
301
|
+
function cloneToolRendererConfig(config) {
|
|
302
|
+
return {
|
|
303
|
+
default: { ...config.default },
|
|
304
|
+
tools: Object.fromEntries(Object.entries(config.tools).map(([name, rule]) => [name, { ...rule }])),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function cloneDictationConfig(config) {
|
|
308
|
+
return {
|
|
309
|
+
languages: Object.fromEntries(Object.entries(config.languages).map(([language, model]) => [language, { ...model }])),
|
|
310
|
+
...(config.language === undefined ? {} : { language: config.language }),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
301
313
|
function pixConfigFromParsed(parsed, fallback = defaultPixConfig()) {
|
|
302
314
|
const toolRenderer = extractToolRendererConfig(parsed) ?? fallback.toolRenderer;
|
|
303
315
|
const outputFilters = extractOutputFiltersConfig(parsed) ?? fallback.outputFilters;
|
|
@@ -6,6 +6,7 @@ export type RenderedMarkdownLine = {
|
|
|
6
6
|
end: number;
|
|
7
7
|
bold: true;
|
|
8
8
|
}[];
|
|
9
|
+
heading?: boolean;
|
|
9
10
|
};
|
|
10
11
|
export type RenderedMarkdownTextLine = {
|
|
11
12
|
text: string;
|
|
@@ -15,6 +16,7 @@ export type RenderedMarkdownTextLine = {
|
|
|
15
16
|
bold: true;
|
|
16
17
|
}[] | undefined;
|
|
17
18
|
syntaxHighlight?: SyntaxLineHighlight | undefined;
|
|
19
|
+
heading?: boolean;
|
|
18
20
|
};
|
|
19
21
|
export declare function formatMarkdownTables(text: string, maxWidth?: number): string;
|
|
20
22
|
export declare function renderMarkdownLine(text: string, start?: number): RenderedMarkdownLine;
|