pi-ui-extend 0.1.18 → 0.1.20
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 +8 -6
- package/dist/app/constants.d.ts +1 -0
- package/dist/app/constants.js +2 -1
- package/dist/app/input/voice-controller.js +16 -12
- package/dist/app/popup/popup-menu-controller.d.ts +1 -5
- package/dist/app/popup/popup-menu-controller.js +7 -8
- package/dist/app/process.js +7 -0
- package/dist/app/rendering/conversation-entry-renderer.js +17 -16
- package/dist/app/rendering/conversation-viewport.js +4 -35
- package/dist/app/rendering/editor-layout-renderer.d.ts +5 -1
- package/dist/app/rendering/editor-layout-renderer.js +29 -20
- package/dist/app/rendering/popup-menu-renderer.d.ts +1 -5
- package/dist/app/rendering/popup-menu-renderer.js +24 -34
- package/dist/app/rendering/render-controller.d.ts +2 -0
- package/dist/app/rendering/render-controller.js +40 -49
- package/dist/app/rendering/render-text.js +2 -2
- 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/tab-line-renderer.js +3 -3
- package/dist/app/runtime.js +29 -3
- package/dist/app/screen/file-link-opener.d.ts +2 -0
- package/dist/app/screen/file-link-opener.js +84 -17
- package/dist/app/screen/mouse-controller.d.ts +1 -3
- package/dist/app/screen/mouse-controller.js +14 -14
- package/dist/app/screen/screen-styler.js +1 -1
- package/dist/app/session/lazy-session-manager.d.ts +1 -1
- package/dist/app/session/lazy-session-manager.js +64 -52
- package/dist/app/session/queued-message-controller.d.ts +6 -0
- package/dist/app/session/queued-message-controller.js +9 -1
- package/dist/app/session/queued-message-entries.d.ts +8 -0
- package/dist/app/session/queued-message-entries.js +41 -0
- package/dist/app/session/session-lifecycle-controller.d.ts +9 -1
- package/dist/app/session/session-lifecycle-controller.js +45 -11
- package/dist/app/session/tabs-controller.d.ts +11 -1
- package/dist/app/session/tabs-controller.js +197 -30
- package/dist/app/terminal/terminal-controller.d.ts +2 -0
- package/dist/app/terminal/terminal-controller.js +7 -5
- package/dist/app/types.d.ts +1 -1
- package/dist/schemas/pi-tools-suite-schema.d.ts +3 -0
- package/dist/schemas/pi-tools-suite-schema.js +3 -0
- package/dist/theme.d.ts +11 -0
- package/dist/theme.js +26 -4
- package/extensions/question/tui.ts +1 -1
- package/extensions/session-title/config.ts +3 -3
- package/extensions/session-title/index.ts +60 -5
- package/external/pi-tools-suite/README.md +3 -2
- package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +1 -0
- package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +1 -0
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +0 -3
- package/external/pi-tools-suite/src/async-subagents/core/notifications.ts +64 -0
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +1 -0
- package/external/pi-tools-suite/src/async-subagents/index.ts +54 -8
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +4 -4
- package/external/pi-tools-suite/src/config.ts +13 -0
- package/external/pi-tools-suite/src/dcp/state.ts +9 -4
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +5 -1
- package/external/pi-tools-suite/src/glm-coding-discipline/index.ts +683 -0
- package/external/pi-tools-suite/src/index.ts +1 -0
- package/external/pi-tools-suite/src/lib/lsp.ts +2 -5
- package/external/pi-tools-suite/src/lsp/_shared/config.ts +2 -0
- package/external/pi-tools-suite/src/lsp/_shared/types.ts +2 -0
- package/external/pi-tools-suite/src/lsp/manager.ts +15 -9
- package/external/pi-tools-suite/src/telegram-mirror/ipc.ts +1 -0
- package/external/pi-tools-suite/src/todo/index.ts +81 -4
- package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +5 -0
- package/external/pi-tools-suite/src/tool-descriptions.ts +4 -4
- package/package.json +3 -14
- package/schemas/pi-tools-suite.json +19 -0
- package/apps/desktop-tauri/README.md +0 -103
- package/apps/desktop-tauri/bin/pix-desktop.mjs +0 -89
|
@@ -6,10 +6,6 @@ import { ANSI_RESET, colorLine, colorize } from "../../theme.js";
|
|
|
6
6
|
import { stringDisplayWidth } from "../../terminal-width.js";
|
|
7
7
|
import { padOrTrimPlain } from "./render-text.js";
|
|
8
8
|
const INPUT_FRAME = {
|
|
9
|
-
topLeft: "╭",
|
|
10
|
-
topRight: "╮",
|
|
11
|
-
bottomLeft: "╰",
|
|
12
|
-
bottomRight: "╯",
|
|
13
9
|
horizontal: "─",
|
|
14
10
|
};
|
|
15
11
|
export class AppRenderController {
|
|
@@ -129,6 +125,11 @@ export class AppRenderController {
|
|
|
129
125
|
setRenderedBackground(row, rendered?.backgroundOverride);
|
|
130
126
|
appendFrameOutput("conversation", row, this.renderFrameRow(row, this.deps.screenStyler.styleBaseLine(row, rendered, conversationColumns)));
|
|
131
127
|
}
|
|
128
|
+
const loadingConversationOverlay = this.renderConversationLoadingOverlay(this.deps.loadingConversationOverlayText?.(), conversationColumns, topReservedRows, bodyHeight);
|
|
129
|
+
if (loadingConversationOverlay) {
|
|
130
|
+
this.deps.mouseController.renderedRowTexts.set(loadingConversationOverlay.row, loadingConversationOverlay.text);
|
|
131
|
+
appendFrameOutput("conversation", loadingConversationOverlay.row, this.renderFrameRow(loadingConversationOverlay.row, loadingConversationOverlay.output));
|
|
132
|
+
}
|
|
132
133
|
const aboveEditorStartRow = inputSeparatorRow + 1;
|
|
133
134
|
for (let index = 0; index < aboveEditorLines.length; index += 1) {
|
|
134
135
|
const rendered = frameRenderedLine(aboveEditorLines[index], columns, this.deps.theme, this.deps.screenStyler);
|
|
@@ -149,7 +150,7 @@ export class AppRenderController {
|
|
|
149
150
|
if (row < statusRow) {
|
|
150
151
|
this.deps.mouseController.renderedRowTexts.set(row, separatorText);
|
|
151
152
|
appendFrameOutput("inputStatus", row, this.renderFrameRow(row, this.deps.screenStyler.styleLine(row, separatorText, columns, {
|
|
152
|
-
foreground: this.deps.theme.colors.
|
|
153
|
+
foreground: this.deps.theme.colors.tabBorder,
|
|
153
154
|
})));
|
|
154
155
|
}
|
|
155
156
|
}
|
|
@@ -160,7 +161,7 @@ export class AppRenderController {
|
|
|
160
161
|
const row = toScreenRow(inputStartRow + index);
|
|
161
162
|
this.deps.mouseController.renderedRowTexts.set(row, inputLine);
|
|
162
163
|
const tagColor = this.deps.theme.colors.accent;
|
|
163
|
-
const styledLine = this.deps.screenStyler.styleInputLine(row, inputLine, tagSpans, suggestionSpans, columns, tagColor, this.deps.theme.colors.muted
|
|
164
|
+
const styledLine = this.deps.screenStyler.styleInputLine(row, inputLine, tagSpans, suggestionSpans, columns, tagColor, this.deps.theme.colors.muted);
|
|
164
165
|
appendFrameOutput("inputStatus", row, this.renderFrameRow(row, styledLine));
|
|
165
166
|
}
|
|
166
167
|
if (renderedInput.scrollBar && columns > 0) {
|
|
@@ -175,7 +176,15 @@ export class AppRenderController {
|
|
|
175
176
|
})}`);
|
|
176
177
|
}
|
|
177
178
|
}
|
|
178
|
-
|
|
179
|
+
if (inputBottomSeparatorRow && inputBottomSeparatorRow > inputSeparatorRow && inputBottomSeparatorRow < statusRow) {
|
|
180
|
+
const separatorText = inputFrameLine(columns, "bottom");
|
|
181
|
+
const row = toScreenRow(inputBottomSeparatorRow);
|
|
182
|
+
this.deps.mouseController.renderedRowTexts.set(row, separatorText);
|
|
183
|
+
appendFrameOutput("inputStatus", row, this.renderFrameRow(row, this.deps.screenStyler.styleLine(row, separatorText, columns, {
|
|
184
|
+
foreground: this.deps.theme.colors.tabBorder,
|
|
185
|
+
})));
|
|
186
|
+
}
|
|
187
|
+
const belowEditorStartRow = (inputBottomSeparatorRow ?? (inputStartRow + renderedInput.lines.length - 1)) + 1;
|
|
179
188
|
for (let index = 0; index < belowEditorLines.length; index += 1) {
|
|
180
189
|
const rendered = frameRenderedLine(belowEditorLines[index], columns, this.deps.theme, this.deps.screenStyler);
|
|
181
190
|
const row = toScreenRow(belowEditorStartRow + index);
|
|
@@ -190,25 +199,7 @@ export class AppRenderController {
|
|
|
190
199
|
appendFrameOutput("inputStatus", row, this.renderFrameRow(row, rendered.output(row)));
|
|
191
200
|
}
|
|
192
201
|
const statusLayout = this.deps.statusLineRenderer.layout(columns);
|
|
193
|
-
|
|
194
|
-
const inputBorderWidgetsLayout = statusLineRenderer.inputBorderWidgetsLayout?.(columns);
|
|
195
|
-
if (inputBottomSeparatorRow > 1) {
|
|
196
|
-
const separatorText = inputFrameLine(columns, "bottom");
|
|
197
|
-
const row = toScreenRow(inputBottomSeparatorRow);
|
|
198
|
-
if (row < statusRow) {
|
|
199
|
-
const text = inputBorderWidgetsLayout
|
|
200
|
-
? overlayText(separatorText, inputBorderWidgetsLayout.inputBorderWidgetStartColumn ?? 1, inputBorderWidgetsLayout.text)
|
|
201
|
-
: separatorText;
|
|
202
|
-
this.deps.mouseController.renderedRowTexts.set(row, text);
|
|
203
|
-
const output = inputBorderWidgetsLayout && statusLineRenderer.renderInputBorderWidgets
|
|
204
|
-
? statusLineRenderer.renderInputBorderWidgets(row, inputBorderWidgetsLayout, separatorText, columns)
|
|
205
|
-
: this.deps.screenStyler.styleLine(row, separatorText, columns, {
|
|
206
|
-
foreground: this.deps.theme.colors.inputBorder,
|
|
207
|
-
});
|
|
208
|
-
appendFrameOutput("inputStatus", row, this.renderFrameRow(row, output));
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
this.updateStatusMouseState(statusLayout, statusRow, inputBorderWidgetsLayout, toScreenRow(inputBottomSeparatorRow));
|
|
202
|
+
this.updateStatusMouseState(statusLayout, statusRow);
|
|
212
203
|
appendFrameOutput("inputStatus", statusRow, this.renderFrameRow(statusRow, this.deps.statusLineRenderer.render(statusRow, statusLayout, columns)));
|
|
213
204
|
const voiceProgressOverlay = this.renderVoiceProgressOverlay(this.deps.voiceProgressOverlayText(), columns, statusRow);
|
|
214
205
|
if (voiceProgressOverlay) {
|
|
@@ -286,9 +277,9 @@ export class AppRenderController {
|
|
|
286
277
|
const text = fixedCellText(flash.text, width);
|
|
287
278
|
return `\x1b[${flash.y};${flash.startColumn}H\x1b[7m${text}${ANSI_RESET}`;
|
|
288
279
|
}
|
|
289
|
-
updateStatusMouseState(statusLayout, statusRow
|
|
290
|
-
const widgetLayout =
|
|
291
|
-
const widgetRow =
|
|
280
|
+
updateStatusMouseState(statusLayout, statusRow) {
|
|
281
|
+
const widgetLayout = statusLayout;
|
|
282
|
+
const widgetRow = statusRow;
|
|
292
283
|
this.deps.mouseController.statusModelTarget = this.deps.statusLineRenderer.modelTarget(statusLayout.text, statusRow);
|
|
293
284
|
this.deps.mouseController.statusThinkingTarget = this.deps.statusLineRenderer.thinkingTarget(statusLayout.text, statusRow);
|
|
294
285
|
this.deps.mouseController.statusContextTarget = this.deps.statusLineRenderer.contextTarget(statusLayout.text, statusRow, statusLayout);
|
|
@@ -327,40 +318,40 @@ export class AppRenderController {
|
|
|
327
318
|
].join("");
|
|
328
319
|
return { row: Math.min(2, rows - 1), text, output };
|
|
329
320
|
}
|
|
321
|
+
renderConversationLoadingOverlay(message, width, topReservedRows, bodyHeight) {
|
|
322
|
+
if (!message || width <= 0 || bodyHeight <= 0)
|
|
323
|
+
return undefined;
|
|
324
|
+
const overlayWidth = Math.min(stringDisplayWidth(message), width);
|
|
325
|
+
const leftWidth = Math.max(0, Math.floor((width - overlayWidth) / 2));
|
|
326
|
+
const rightWidth = Math.max(0, width - leftWidth - overlayWidth);
|
|
327
|
+
const text = `${" ".repeat(leftWidth)}${padOrTrimPlain(message, overlayWidth)}${" ".repeat(rightWidth)}`;
|
|
328
|
+
const row = topReservedRows + Math.floor((bodyHeight + 1) / 2);
|
|
329
|
+
const output = this.deps.screenStyler.styleLine(row, text, width, {
|
|
330
|
+
foreground: this.deps.theme.colors.muted,
|
|
331
|
+
});
|
|
332
|
+
return { row, text, output };
|
|
333
|
+
}
|
|
330
334
|
}
|
|
331
335
|
function visibleToastStates(toastController) {
|
|
332
336
|
const candidate = toastController;
|
|
333
337
|
return typeof candidate.visibleStates === "function" ? candidate.visibleStates() : candidate.toast?.visibleStates ?? [];
|
|
334
338
|
}
|
|
335
339
|
function inputFrameLine(width, edge) {
|
|
340
|
+
void edge;
|
|
336
341
|
if (width <= 0)
|
|
337
342
|
return "";
|
|
338
|
-
|
|
339
|
-
return edge === "top" ? INPUT_FRAME.topLeft : INPUT_FRAME.bottomLeft;
|
|
340
|
-
const left = edge === "top" ? INPUT_FRAME.topLeft : INPUT_FRAME.bottomLeft;
|
|
341
|
-
const right = edge === "top" ? INPUT_FRAME.topRight : INPUT_FRAME.bottomRight;
|
|
342
|
-
return `${left}${INPUT_FRAME.horizontal.repeat(Math.max(0, width - 2))}${right}`;
|
|
343
|
+
return INPUT_FRAME.horizontal.repeat(width);
|
|
343
344
|
}
|
|
344
345
|
function frameRenderedLine(line, width, theme, screenStyler) {
|
|
345
346
|
if (width <= 0)
|
|
346
347
|
return { line, text: "", output: () => "" };
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
});
|
|
351
|
-
return { line, text: "│", output: () => border };
|
|
352
|
-
}
|
|
353
|
-
const innerWidth = Math.max(0, width - 2);
|
|
354
|
-
const innerText = padOrTrimPlain(line?.text ?? "", innerWidth);
|
|
355
|
-
const innerLine = line ? frameInnerRenderedLine(line, innerText, innerWidth) : undefined;
|
|
356
|
-
const leftBorder = colorize("│", {
|
|
357
|
-
foreground: theme.colors.inputBorder,
|
|
358
|
-
});
|
|
359
|
-
const rightBorder = leftBorder;
|
|
348
|
+
void theme;
|
|
349
|
+
const text = padOrTrimPlain(line?.text ?? "", width);
|
|
350
|
+
const outputLine = line ? frameInnerRenderedLine(line, text, width) : undefined;
|
|
360
351
|
return {
|
|
361
352
|
line,
|
|
362
|
-
text
|
|
363
|
-
output: (row) =>
|
|
353
|
+
text,
|
|
354
|
+
output: (row) => screenStyler.styleBaseLine(row, outputLine, width),
|
|
364
355
|
};
|
|
365
356
|
}
|
|
366
357
|
function frameInnerRenderedLine(line, text, width) {
|
|
@@ -98,8 +98,8 @@ export function padOrTrimPlain(text, width) {
|
|
|
98
98
|
}
|
|
99
99
|
export function horizontalPaddingLayout(width) {
|
|
100
100
|
const safeWidth = Math.max(1, width);
|
|
101
|
-
const left =
|
|
102
|
-
const right =
|
|
101
|
+
const left = 0;
|
|
102
|
+
const right = 0;
|
|
103
103
|
return { left, right, contentWidth: Math.max(1, safeWidth - left - right) };
|
|
104
104
|
}
|
|
105
105
|
export function padHorizontalText(text, width) {
|
|
@@ -53,6 +53,8 @@ export declare class StatusLineRenderer {
|
|
|
53
53
|
terminalBellSoundTarget(layout: StatusLineLayout, row: number): StatusTerminalBellSoundTarget | undefined;
|
|
54
54
|
sessionTarget(statusText: string, row: number, label: string, workspaceLabel: string): StatusSessionTarget | undefined;
|
|
55
55
|
private segments;
|
|
56
|
+
private fitWorkspaceLabel;
|
|
57
|
+
private pushStatusWidgetSegments;
|
|
56
58
|
private pushPromptEnhancerWidgetSegment;
|
|
57
59
|
private pushUserJumpWidgetSegment;
|
|
58
60
|
private pushDraftQueueWidgetSegment;
|
|
@@ -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.inputBorder;
|
|
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),
|
|
@@ -50,7 +50,7 @@ export class TabLineRenderer {
|
|
|
50
50
|
segments.push({
|
|
51
51
|
start: separatorOffset + 1,
|
|
52
52
|
end: separatorOffset + 2,
|
|
53
|
-
foreground: this.host.theme.colors.
|
|
53
|
+
foreground: this.host.theme.colors.tabBorder,
|
|
54
54
|
});
|
|
55
55
|
displayColumn += separatorWidth;
|
|
56
56
|
}
|
|
@@ -89,7 +89,7 @@ export class TabLineRenderer {
|
|
|
89
89
|
segments.push({
|
|
90
90
|
start: newTabDividerOffset,
|
|
91
91
|
end: newTabDividerOffset + 1,
|
|
92
|
-
foreground: this.host.theme.colors.
|
|
92
|
+
foreground: this.host.theme.colors.tabBorder,
|
|
93
93
|
});
|
|
94
94
|
segments.push({
|
|
95
95
|
start: lineText.length - APP_ICONS.plus.length,
|
|
@@ -119,7 +119,7 @@ export class TabLineRenderer {
|
|
|
119
119
|
}
|
|
120
120
|
renderBottom(row, layout, width) {
|
|
121
121
|
return this.host.screenStyler.styleLine(row, this.bottomText(layout, width), width, {
|
|
122
|
-
foreground: this.host.theme.colors.
|
|
122
|
+
foreground: this.host.theme.colors.tabBorder,
|
|
123
123
|
});
|
|
124
124
|
}
|
|
125
125
|
bottomText(layout, width) {
|
package/dist/app/runtime.js
CHANGED
|
@@ -133,6 +133,32 @@ function isBundledQuestionConflict(error, bundledExtensionPaths) {
|
|
|
133
133
|
}
|
|
134
134
|
return false;
|
|
135
135
|
}
|
|
136
|
+
const bundledSkillsInstallPromises = new Map();
|
|
137
|
+
const piToolsSuiteInstallPromises = new Map();
|
|
138
|
+
async function ensureBundledSkillsInstalledOnce(options = {}) {
|
|
139
|
+
const targetPath = resolve(options.targetPath ?? bundledSkillsInstallPath(options.homeDir));
|
|
140
|
+
const existing = bundledSkillsInstallPromises.get(targetPath);
|
|
141
|
+
if (existing)
|
|
142
|
+
return await existing;
|
|
143
|
+
const pending = ensureBundledSkillsInstalled(options).catch((error) => {
|
|
144
|
+
bundledSkillsInstallPromises.delete(targetPath);
|
|
145
|
+
throw error;
|
|
146
|
+
});
|
|
147
|
+
bundledSkillsInstallPromises.set(targetPath, pending);
|
|
148
|
+
return await pending;
|
|
149
|
+
}
|
|
150
|
+
async function ensurePiToolsSuiteExtensionInstalledOnce(options = {}) {
|
|
151
|
+
const targetPath = resolve(options.targetPath ?? piToolsSuiteExtensionInstallPath(options.agentDir));
|
|
152
|
+
const existing = piToolsSuiteInstallPromises.get(targetPath);
|
|
153
|
+
if (existing)
|
|
154
|
+
return await existing;
|
|
155
|
+
const pending = ensurePiToolsSuiteExtensionInstalled(options).catch((error) => {
|
|
156
|
+
piToolsSuiteInstallPromises.delete(targetPath);
|
|
157
|
+
throw error;
|
|
158
|
+
});
|
|
159
|
+
piToolsSuiteInstallPromises.set(targetPath, pending);
|
|
160
|
+
return await pending;
|
|
161
|
+
}
|
|
136
162
|
export function resolvePixRuntimeModelRef(options, sessionManager, config = loadPixConfig()) {
|
|
137
163
|
if (options.modelRef)
|
|
138
164
|
return options.modelRef;
|
|
@@ -176,8 +202,8 @@ export async function createPixRuntime(options, runtimeOptions = {}) {
|
|
|
176
202
|
const effectiveModelRef = resolvePixRuntimeModelRef(options, sessionManager, config);
|
|
177
203
|
const parsedModel = effectiveModelRef ? parseModelRef(effectiveModelRef) : undefined;
|
|
178
204
|
const initialThinkingLevel = resolvePixRuntimeInitialThinkingLevel(options, sessionManager, config);
|
|
179
|
-
await
|
|
180
|
-
await
|
|
205
|
+
await ensureBundledSkillsInstalledOnce();
|
|
206
|
+
await ensurePiToolsSuiteExtensionInstalledOnce({ agentDir });
|
|
181
207
|
const bundledExtensionPaths = getBundledExtensionPaths();
|
|
182
208
|
const services = await createAgentSessionServices({
|
|
183
209
|
cwd,
|
|
@@ -232,7 +258,7 @@ export async function createPixRuntime(options, runtimeOptions = {}) {
|
|
|
232
258
|
sessionManager: options.noSession
|
|
233
259
|
? SessionManager.inMemory(options.cwd)
|
|
234
260
|
: options.sessionPath
|
|
235
|
-
? openLazySessionManager(options.sessionPath, { cwdOverride: options.cwd })
|
|
261
|
+
? await openLazySessionManager(options.sessionPath, { cwdOverride: options.cwd })
|
|
236
262
|
: SessionManager.create(options.cwd),
|
|
237
263
|
});
|
|
238
264
|
}
|
|
@@ -3,6 +3,8 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
import type { RenderedLink } from "./file-links.js";
|
|
4
4
|
type FileLinkOpenerDeps = {
|
|
5
5
|
existsSync: typeof existsSync;
|
|
6
|
+
env: NodeJS.ProcessEnv;
|
|
7
|
+
platform: NodeJS.Platform;
|
|
6
8
|
spawn: typeof spawn;
|
|
7
9
|
};
|
|
8
10
|
export declare function setFileLinkOpenerTestDeps(overrides: Partial<FileLinkOpenerDeps>): () => void;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import {
|
|
3
|
+
import { isAbsolute, posix, win32 } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
let deps = { existsSync, spawn };
|
|
5
|
+
let deps = { existsSync, env: process.env, platform: process.platform, spawn };
|
|
6
6
|
export function setFileLinkOpenerTestDeps(overrides) {
|
|
7
7
|
const previous = deps;
|
|
8
8
|
deps = { ...deps, ...overrides };
|
|
@@ -14,13 +14,10 @@ export function openFileLink(link) {
|
|
|
14
14
|
const filePath = link.filePath ?? filePathFromUrl(link.url);
|
|
15
15
|
if (!filePath)
|
|
16
16
|
return false;
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
if (trySpawnCandidates(candidates, [target]))
|
|
17
|
+
const editorLaunch = preferredEditorLaunch(filePath, link.line, link.column);
|
|
18
|
+
if (editorLaunch && trySpawnCandidates(editorLaunch.candidates, editorLaunch.args))
|
|
20
19
|
return true;
|
|
21
|
-
|
|
22
|
-
return spawnDetached("open", ["-a", "Zed", filePath]);
|
|
23
|
-
return false;
|
|
20
|
+
return openPathWithSystemViewer(filePath);
|
|
24
21
|
}
|
|
25
22
|
function filePathFromUrl(url) {
|
|
26
23
|
if (!url.startsWith("file://"))
|
|
@@ -37,29 +34,99 @@ function zedTarget(filePath, line, column) {
|
|
|
37
34
|
return filePath;
|
|
38
35
|
return column === undefined ? `${filePath}:${line}` : `${filePath}:${line}:${column}`;
|
|
39
36
|
}
|
|
37
|
+
function gotoTarget(filePath, line, column) {
|
|
38
|
+
if (line === undefined)
|
|
39
|
+
return filePath;
|
|
40
|
+
return column === undefined ? `${filePath}:${line}` : `${filePath}:${line}:${column}`;
|
|
41
|
+
}
|
|
42
|
+
function preferredEditorLaunch(filePath, line, column) {
|
|
43
|
+
switch (detectEditor(deps.env)) {
|
|
44
|
+
case "cursor":
|
|
45
|
+
return { args: ["--goto", gotoTarget(filePath, line, column)], candidates: commandCandidates(deps.env.CURSOR_CLI, "cursor") };
|
|
46
|
+
case "jetbrains":
|
|
47
|
+
return {
|
|
48
|
+
args: jetbrainsTargetArgs(filePath, line),
|
|
49
|
+
candidates: commandCandidates(deps.env.JETBRAINS_IDE_CLI, "idea", "idea64", "webstorm", "webstorm64", "pycharm", "pycharm64", "goland", "goland64", "clion", "clion64", "phpstorm", "phpstorm64", "rubymine", "rubymine64", "rider", "rider64"),
|
|
50
|
+
};
|
|
51
|
+
case "vscode":
|
|
52
|
+
return { args: ["--goto", gotoTarget(filePath, line, column)], candidates: commandCandidates(deps.env.VSCODE_CLI, "code", "code-insiders") };
|
|
53
|
+
case "windsurf":
|
|
54
|
+
return { args: ["--goto", gotoTarget(filePath, line, column)], candidates: commandCandidates(deps.env.WINDSURF_CLI, "windsurf") };
|
|
55
|
+
case "zed":
|
|
56
|
+
return { args: [zedTarget(filePath, line, column)], candidates: zedCommandCandidates() };
|
|
57
|
+
default:
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function detectEditor(env) {
|
|
62
|
+
const termProgram = env.TERM_PROGRAM?.trim().toLowerCase();
|
|
63
|
+
const terminalEmulator = env.TERMINAL_EMULATOR?.trim().toLowerCase();
|
|
64
|
+
const terminalProvider = env.TERMINAL_PROVIDER?.trim().toLowerCase();
|
|
65
|
+
if (termProgram === "cursor" || env.CURSOR_TRACE_ID || env.CURSOR_TRACE)
|
|
66
|
+
return "cursor";
|
|
67
|
+
if (termProgram === "windsurf")
|
|
68
|
+
return "windsurf";
|
|
69
|
+
if (termProgram === "zed" || env.ZED_CLI)
|
|
70
|
+
return "zed";
|
|
71
|
+
if (termProgram === "vscode" || env.VSCODE_IPC_HOOK_CLI || env.VSCODE_GIT_IPC_HANDLE)
|
|
72
|
+
return "vscode";
|
|
73
|
+
if (terminalEmulator?.includes("jetbrains") || terminalProvider === "jetbrains")
|
|
74
|
+
return "jetbrains";
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
40
77
|
function zedCommandCandidates() {
|
|
41
|
-
const candidates = [
|
|
42
|
-
if (
|
|
78
|
+
const candidates = [deps.env.ZED_CLI, "zed", "zeditor"];
|
|
79
|
+
if (deps.platform === "darwin")
|
|
43
80
|
candidates.push("/opt/homebrew/bin/zed", "/usr/local/bin/zed");
|
|
44
81
|
return candidates.filter((candidate) => Boolean(candidate));
|
|
45
82
|
}
|
|
83
|
+
function commandCandidates(primary, ...rest) {
|
|
84
|
+
return [primary, ...rest].filter((candidate) => Boolean(candidate));
|
|
85
|
+
}
|
|
86
|
+
function jetbrainsTargetArgs(filePath, line) {
|
|
87
|
+
if (line === undefined)
|
|
88
|
+
return [filePath];
|
|
89
|
+
return ["--line", `${line}`, filePath];
|
|
90
|
+
}
|
|
46
91
|
function trySpawnCandidates(candidates, args) {
|
|
47
92
|
for (const command of candidates) {
|
|
48
|
-
if (
|
|
49
|
-
continue;
|
|
50
|
-
if (!command.includes("/") && !commandOnPath(command))
|
|
93
|
+
if (!canRunCommand(command))
|
|
51
94
|
continue;
|
|
52
95
|
if (spawnDetached(command, args))
|
|
53
96
|
return true;
|
|
54
97
|
}
|
|
55
98
|
return false;
|
|
56
99
|
}
|
|
100
|
+
function canRunCommand(command) {
|
|
101
|
+
if (hasPathSeparator(command) || isAbsolute(command))
|
|
102
|
+
return deps.existsSync(command);
|
|
103
|
+
return commandOnPath(command);
|
|
104
|
+
}
|
|
105
|
+
function hasPathSeparator(command) {
|
|
106
|
+
return command.includes("/") || command.includes("\\");
|
|
107
|
+
}
|
|
57
108
|
function commandOnPath(command) {
|
|
58
|
-
const pathEntries =
|
|
59
|
-
const extensions =
|
|
60
|
-
? (
|
|
109
|
+
const pathEntries = deps.env.PATH?.split(pathDelimiter()) ?? [];
|
|
110
|
+
const extensions = deps.platform === "win32"
|
|
111
|
+
? (deps.env.PATHEXT?.split(";") ?? [".EXE", ".CMD", ".BAT", ".COM"])
|
|
61
112
|
: [""];
|
|
62
|
-
return pathEntries.some((entry) => extensions.some((
|
|
113
|
+
return pathEntries.some((entry) => pathCommandCandidates(entry, command, extensions).some((candidate) => deps.existsSync(candidate)));
|
|
114
|
+
}
|
|
115
|
+
function pathDelimiter() {
|
|
116
|
+
return deps.platform === "win32" ? ";" : ":";
|
|
117
|
+
}
|
|
118
|
+
function pathCommandCandidates(entry, command, extensions) {
|
|
119
|
+
const pathApi = deps.platform === "win32" ? win32 : posix;
|
|
120
|
+
if (deps.platform !== "win32" || pathApi.extname(command))
|
|
121
|
+
return [pathApi.join(entry, command)];
|
|
122
|
+
return [pathApi.join(entry, command), ...extensions.map((extension) => pathApi.join(entry, `${command}${extension}`))];
|
|
123
|
+
}
|
|
124
|
+
function openPathWithSystemViewer(filePath) {
|
|
125
|
+
if (deps.platform === "darwin")
|
|
126
|
+
return spawnDetached("open", [filePath]);
|
|
127
|
+
if (deps.platform === "win32")
|
|
128
|
+
return spawnDetached("cmd", ["/c", "start", "", filePath]);
|
|
129
|
+
return spawnDetached("xdg-open", [filePath]);
|
|
63
130
|
}
|
|
64
131
|
function spawnDetached(command, args) {
|
|
65
132
|
try {
|
|
@@ -20,9 +20,7 @@ export type InputFrameCopyRows = {
|
|
|
20
20
|
inputStartRow: number;
|
|
21
21
|
inputEndRow: number;
|
|
22
22
|
inputSeparatorRow: number;
|
|
23
|
-
inputBottomSeparatorRow
|
|
24
|
-
contentStartColumn: number;
|
|
25
|
-
contentEndColumn: number;
|
|
23
|
+
inputBottomSeparatorRow?: number;
|
|
26
24
|
};
|
|
27
25
|
export type AppMouseControllerHost = {
|
|
28
26
|
terminalColumns(): number;
|