pi-ui-extend 0.1.25 → 0.1.27
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/icons.d.ts +3 -0
- package/dist/app/icons.js +6 -0
- package/dist/app/rendering/conversation-tool-renderer.js +3 -3
- package/dist/app/rendering/tool-block-renderer.d.ts +1 -0
- package/dist/app/rendering/tool-block-renderer.js +76 -21
- package/dist/app/screen/mouse-controller.d.ts +2 -0
- package/dist/app/screen/mouse-controller.js +37 -1
- package/dist/app/session/session-event-controller.js +6 -0
- package/dist/app/session/tabs-controller.d.ts +5 -0
- package/dist/app/session/tabs-controller.js +47 -0
- package/external/pi-tools-suite/src/glm-coding-discipline/index.ts +21 -1
- package/package.json +1 -1
package/dist/app/icons.d.ts
CHANGED
|
@@ -25,6 +25,9 @@ declare const NERD_FONT_ICONS: {
|
|
|
25
25
|
readonly thinkingExpanded: "";
|
|
26
26
|
readonly stopCircle: "";
|
|
27
27
|
readonly timerSand: "";
|
|
28
|
+
readonly toolBodyEnd: "└";
|
|
29
|
+
readonly toolBodyGutter: "│";
|
|
30
|
+
readonly toolPreviewTruncated: "⊞";
|
|
28
31
|
readonly down: "";
|
|
29
32
|
};
|
|
30
33
|
export type AppIconName = keyof typeof NERD_FONT_ICONS;
|
package/dist/app/icons.js
CHANGED
|
@@ -31,6 +31,9 @@ const NERD_FONT_ICONS = {
|
|
|
31
31
|
thinkingExpanded: "\u{f0335}",
|
|
32
32
|
stopCircle: "\u{f0665}",
|
|
33
33
|
timerSand: "\u{f051f}",
|
|
34
|
+
toolBodyEnd: "└",
|
|
35
|
+
toolBodyGutter: "│",
|
|
36
|
+
toolPreviewTruncated: "⊞",
|
|
34
37
|
down: "\u{f0045}",
|
|
35
38
|
};
|
|
36
39
|
const FALLBACK_ICONS = {
|
|
@@ -58,6 +61,9 @@ const FALLBACK_ICONS = {
|
|
|
58
61
|
thinkingExpanded: ">",
|
|
59
62
|
stopCircle: "■",
|
|
60
63
|
timerSand: "⏳",
|
|
64
|
+
toolBodyEnd: "`",
|
|
65
|
+
toolBodyGutter: "|",
|
|
66
|
+
toolPreviewTruncated: "+",
|
|
61
67
|
down: "v",
|
|
62
68
|
};
|
|
63
69
|
export const APP_ICON_THEMES = {
|
|
@@ -42,7 +42,7 @@ export function renderConversationToolEntry(entry, width, options) {
|
|
|
42
42
|
collapsedBody: display.collapsedBody,
|
|
43
43
|
expandedText: display.expandedText,
|
|
44
44
|
syntaxHighlight: display.syntaxHighlight,
|
|
45
|
-
}, rule, width, options.colors, { superCompact: Boolean(options.superCompactTools) });
|
|
45
|
+
}, rule, width, options.colors, { superCompact: Boolean(options.superCompactTools), showGutter: true });
|
|
46
46
|
return attachImageClickTargets(lines, entry.id, entry.images, { foreground: options.colors.info, underline: true });
|
|
47
47
|
}
|
|
48
48
|
export function renderThinkingEntry(entry, width, options) {
|
|
@@ -63,7 +63,7 @@ export function renderThinkingEntry(entry, width, options) {
|
|
|
63
63
|
expandedText: compactExpandedText || "(empty)",
|
|
64
64
|
bodyWrap: "word",
|
|
65
65
|
syntaxHighlight: compactExpandedText ? markdownSyntaxHighlightsForText(compactExpandedText) : undefined,
|
|
66
|
-
}, rule, width, options.colors, { superCompact: Boolean(options.superCompactTools && !forceExpanded), backgroundOverride: options.colors.thinkingMessageBackground, skipHeaderBackground: true });
|
|
66
|
+
}, rule, width, options.colors, { superCompact: Boolean(options.superCompactTools && !forceExpanded), backgroundOverride: options.colors.thinkingMessageBackground, skipHeaderBackground: true, showGutter: false });
|
|
67
67
|
}
|
|
68
68
|
function trimTrailingBlankLines(text) {
|
|
69
69
|
return text.replace(/(?:\r?\n[ \t]*)+$/u, "");
|
|
@@ -88,7 +88,7 @@ function renderTodoToolEntry(entry, width, options) {
|
|
|
88
88
|
output: body,
|
|
89
89
|
collapsedBody: body,
|
|
90
90
|
expandedText: body,
|
|
91
|
-
}, rule, width, options.colors, { superCompact: Boolean(options.superCompactTools) });
|
|
91
|
+
}, rule, width, options.colors, { superCompact: Boolean(options.superCompactTools), showGutter: true });
|
|
92
92
|
}
|
|
93
93
|
function todoDetailsText(details) {
|
|
94
94
|
const lines = [];
|
|
@@ -25,5 +25,6 @@ export type ToolBlockRenderOptions = {
|
|
|
25
25
|
superCompact?: boolean;
|
|
26
26
|
backgroundOverride?: string;
|
|
27
27
|
skipHeaderBackground?: boolean;
|
|
28
|
+
showGutter?: boolean;
|
|
28
29
|
};
|
|
29
30
|
export declare function renderToolBlock(entry: ToolBlockEntry, rule: ResolvedToolRule, width: number, colors: Theme["colors"], options?: ToolBlockRenderOptions): RenderedLine[];
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { resolveColor } from "../../config.js";
|
|
2
2
|
import { expandTabs, sliceByDisplayWidth, stringDisplayWidth, wrapDisplayLineByWords } from "../../terminal-width.js";
|
|
3
3
|
import { alertIconPrefixLength, hasToolLspDiagnosticsAfterMutation, lspDiagnosticSeverityForLine, sanitizeText, toolStatusIcon, toolStatusIconColor, wrapLine } from "./render-text.js";
|
|
4
|
-
|
|
4
|
+
import { APP_ICONS } from "../icons.js";
|
|
5
|
+
const TOOL_BODY_PREFIX = " ";
|
|
6
|
+
function truncatedPreviewMarker() {
|
|
7
|
+
return `${APP_ICONS.toolPreviewTruncated} `;
|
|
8
|
+
}
|
|
9
|
+
function toolBodyGutterPrefix() {
|
|
10
|
+
return `${APP_ICONS.toolBodyGutter} `;
|
|
11
|
+
}
|
|
12
|
+
function toolBodyEndPrefix() {
|
|
13
|
+
return `${APP_ICONS.toolBodyEnd} `;
|
|
14
|
+
}
|
|
5
15
|
export function renderToolBlock(entry, rule, width, colors, options = {}) {
|
|
6
16
|
if (rule.hidden)
|
|
7
17
|
return [];
|
|
@@ -19,6 +29,7 @@ export function renderToolBlock(entry, rule, width, colors, options = {}) {
|
|
|
19
29
|
const headerArgsWidth = width - stringDisplayWidth(headerPrefix) - 1;
|
|
20
30
|
const clippedHeaderArgs = headerArgsWidth > 0 ? sliceByDisplayWidth(headerArgs, headerArgsWidth) : "";
|
|
21
31
|
const target = { kind: "tool", id: entry.id };
|
|
32
|
+
const showGutter = options.showGutter ?? true;
|
|
22
33
|
const header = clippedHeaderArgs ? `${headerPrefix} ${clippedHeaderArgs}` : headerPrefix;
|
|
23
34
|
const headerArgsStart = clippedHeaderArgs ? headerPrefix.length + 1 : header.length;
|
|
24
35
|
const headerLine = {
|
|
@@ -34,7 +45,7 @@ export function renderToolBlock(entry, rule, width, colors, options = {}) {
|
|
|
34
45
|
};
|
|
35
46
|
const headerLines = [headerLine];
|
|
36
47
|
if (expanded) {
|
|
37
|
-
headerLines.push(...renderToolBodyLines(entry.expandedText, width, target, toolOutputColor, entry.bodyStyle, colors, entry.syntaxHighlight, entry.bodyWrap, hasLspDiagnostics, entry.bodyLineStyles, entry.preserveAnsi));
|
|
48
|
+
headerLines.push(...renderToolBodyLines(entry.expandedText, width, target, toolOutputColor, entry.bodyStyle, colors, entry.syntaxHighlight, entry.bodyWrap, hasLspDiagnostics, entry.bodyLineStyles, entry.preserveAnsi, showGutter));
|
|
38
49
|
if (options.skipHeaderBackground && headerLines.length > 1) {
|
|
39
50
|
applyBackground(headerLines.slice(1));
|
|
40
51
|
}
|
|
@@ -49,7 +60,7 @@ export function renderToolBlock(entry, rule, width, colors, options = {}) {
|
|
|
49
60
|
if (!body || rule.previewLines === 0)
|
|
50
61
|
return headerLines;
|
|
51
62
|
if (!options.superCompact) {
|
|
52
|
-
headerLines.push(...renderCollapsedPreviewLines(entry, body, rule, width, target, toolOutputColor, colors, hasLspDiagnostics));
|
|
63
|
+
headerLines.push(...renderCollapsedPreviewLines(entry, body, rule, width, target, toolOutputColor, colors, hasLspDiagnostics, showGutter));
|
|
53
64
|
applyBackground(headerLines);
|
|
54
65
|
return headerLines;
|
|
55
66
|
}
|
|
@@ -60,13 +71,14 @@ export function renderToolBlock(entry, rule, width, colors, options = {}) {
|
|
|
60
71
|
const availablePreviewWidth = width - stringDisplayWidth(header) - stringDisplayWidth(separator);
|
|
61
72
|
if (availablePreviewWidth <= 0)
|
|
62
73
|
return headerLines;
|
|
63
|
-
const
|
|
74
|
+
const markerPrefix = truncatedPreviewMarker();
|
|
75
|
+
const previewText = preview.overflow ? `${markerPrefix}${preview.text}` : preview.text;
|
|
64
76
|
const clippedPreview = sliceByDisplayWidth(previewText, availablePreviewWidth);
|
|
65
77
|
if (!clippedPreview)
|
|
66
78
|
return headerLines;
|
|
67
79
|
headerLine.text = `${header}${separator}${clippedPreview}`;
|
|
68
80
|
const previewStart = header.length + separator.length;
|
|
69
|
-
const previewTextStart = previewStart + (preview.overflow ?
|
|
81
|
+
const previewTextStart = previewStart + (preview.overflow ? markerPrefix.length : 0);
|
|
70
82
|
headerLine.segments = [
|
|
71
83
|
...(headerLine.segments ?? []),
|
|
72
84
|
...(preview.overflow ? [{ start: previewStart, end: previewStart + 1, foreground: colors.statusDotBase }] : []),
|
|
@@ -74,8 +86,8 @@ export function renderToolBlock(entry, rule, width, colors, options = {}) {
|
|
|
74
86
|
];
|
|
75
87
|
return headerLines;
|
|
76
88
|
}
|
|
77
|
-
function renderCollapsedPreviewLines(entry, body, rule, width, target, color, colors, hasLspDiagnostics) {
|
|
78
|
-
const allLines = renderToolBodyLines(body, width, target, color, entry.bodyStyle, colors, undefined, entry.bodyWrap, hasLspDiagnostics, entry.bodyLineStyles, entry.preserveAnsi);
|
|
89
|
+
function renderCollapsedPreviewLines(entry, body, rule, width, target, color, colors, hasLspDiagnostics, showGutter) {
|
|
90
|
+
const allLines = renderToolBodyLines(body, width, target, color, entry.bodyStyle, colors, undefined, entry.bodyWrap, hasLspDiagnostics, entry.bodyLineStyles, entry.preserveAnsi, showGutter);
|
|
79
91
|
if (rule.previewLines >= allLines.length)
|
|
80
92
|
return allLines;
|
|
81
93
|
const previewLines = rule.direction === "tail" ? allLines.slice(-rule.previewLines) : allLines.slice(0, rule.previewLines);
|
|
@@ -92,20 +104,33 @@ function markTruncatedPreviewLine(lines, direction, markerColor) {
|
|
|
92
104
|
if (lines.length === 0)
|
|
93
105
|
return lines;
|
|
94
106
|
const markerIndex = direction === "tail" ? 0 : lines.length - 1;
|
|
107
|
+
const gutterPrefix = toolBodyGutterPrefix();
|
|
108
|
+
const markerPrefix = truncatedPreviewMarker();
|
|
95
109
|
return lines.map((line, index) => {
|
|
96
110
|
if (index !== markerIndex)
|
|
97
111
|
return line;
|
|
112
|
+
const text = line.text.startsWith(gutterPrefix)
|
|
113
|
+
? `${markerPrefix}${line.text.slice(gutterPrefix.length)}`
|
|
114
|
+
: line.text.startsWith(TOOL_BODY_PREFIX)
|
|
115
|
+
? `${markerPrefix}${line.text.slice(TOOL_BODY_PREFIX.length)}`
|
|
116
|
+
: `${markerPrefix}${line.text}`;
|
|
117
|
+
const existingSegments = (line.segments ?? []).filter((segment) => !(segment.start === 0 && segment.end === 1 && segment.foreground === markerColor));
|
|
98
118
|
return {
|
|
99
119
|
...line,
|
|
100
|
-
text
|
|
101
|
-
segments: [{ start: 0, end: 1, foreground: markerColor }, ...
|
|
120
|
+
text,
|
|
121
|
+
segments: [{ start: 0, end: 1, foreground: markerColor }, ...existingSegments],
|
|
102
122
|
};
|
|
103
123
|
});
|
|
104
124
|
}
|
|
105
|
-
function renderToolBodyLines(text, width, target, color, style, colors, syntaxHighlight, bodyWrap = "char", hasLspDiagnostics = false, bodyLineStyles, preserveAnsi = false) {
|
|
106
|
-
const
|
|
125
|
+
function renderToolBodyLines(text, width, target, color, style, colors, syntaxHighlight, bodyWrap = "char", hasLspDiagnostics = false, bodyLineStyles, preserveAnsi = false, showGutter = false) {
|
|
126
|
+
const displayPrefix = showGutter ? toolBodyGutterPrefix() : TOOL_BODY_PREFIX;
|
|
127
|
+
const prefixWidth = stringDisplayWidth(displayPrefix);
|
|
128
|
+
const bodyWidth = Math.max(1, width - prefixWidth);
|
|
107
129
|
const lines = [];
|
|
108
130
|
const wrapBodyLine = bodyWrap === "word" ? wrapDisplayLineByWords : wrapLine;
|
|
131
|
+
const gutterSegment = showGutter
|
|
132
|
+
? { start: 0, end: 1, foreground: colors.statusDotBase }
|
|
133
|
+
: undefined;
|
|
109
134
|
for (const [rawLineIndex, rawLine] of sanitizeToolBodyText(text, preserveAnsi).split("\n").entries()) {
|
|
110
135
|
const ansiLine = preserveAnsi ? ansiStyledLine(rawLine) : undefined;
|
|
111
136
|
const displayLine = ansiLine?.text ?? rawLine;
|
|
@@ -118,37 +143,67 @@ function renderToolBodyLines(text, width, target, color, style, colors, syntaxHi
|
|
|
118
143
|
: wrapBodyLine(displayLine, bodyWidth).map((wrapped) => ({ text: wrapped, segments: [] }));
|
|
119
144
|
for (const [wrapIndex, wrapped] of wrappedLines.entries()) {
|
|
120
145
|
const line = {
|
|
121
|
-
text:
|
|
122
|
-
copyText:
|
|
146
|
+
text: `${displayPrefix}${wrapped.text}`,
|
|
147
|
+
copyText: `${TOOL_BODY_PREFIX}${wrapped.text}`,
|
|
123
148
|
...(wrapIndex < wrappedLines.length - 1 ? { continuesOnNextLine: true } : {}),
|
|
124
149
|
target,
|
|
125
150
|
colorOverride: color,
|
|
126
151
|
};
|
|
127
152
|
if (diffStyle) {
|
|
128
|
-
const segment = { start:
|
|
153
|
+
const segment = { start: displayPrefix.length, end: line.text.length, foreground: diffStyle.foreground };
|
|
129
154
|
if (diffStyle.bold != null)
|
|
130
155
|
segment.bold = diffStyle.bold;
|
|
131
|
-
line.segments = [segment];
|
|
156
|
+
line.segments = gutterSegment ? [gutterSegment, segment] : [segment];
|
|
132
157
|
}
|
|
133
158
|
else if (lspDiagnosticStyle?.kind === "alert" && wrapIndex === 0) {
|
|
134
|
-
line.segments = [
|
|
159
|
+
line.segments = [
|
|
160
|
+
...(gutterSegment ? [gutterSegment] : []),
|
|
161
|
+
{ start: displayPrefix.length, end: displayPrefix.length + lspDiagnosticStyle.length, foreground: colors.warning, bold: true },
|
|
162
|
+
];
|
|
135
163
|
}
|
|
136
164
|
else if (lspDiagnosticStyle?.kind === "severity") {
|
|
137
|
-
line.segments = [
|
|
165
|
+
line.segments = [
|
|
166
|
+
...(gutterSegment ? [gutterSegment] : []),
|
|
167
|
+
{ start: displayPrefix.length, end: line.text.length, foreground: lspDiagnosticStyle.foreground },
|
|
168
|
+
];
|
|
138
169
|
}
|
|
139
|
-
else if (bodyLineStyle && line.text.length >
|
|
140
|
-
line.segments = [
|
|
170
|
+
else if (bodyLineStyle && line.text.length > displayPrefix.length) {
|
|
171
|
+
line.segments = [
|
|
172
|
+
...(gutterSegment ? [gutterSegment] : []),
|
|
173
|
+
{ start: displayPrefix.length, end: line.text.length, ...bodyLineStyle },
|
|
174
|
+
];
|
|
141
175
|
}
|
|
142
176
|
else if (lineSyntaxHighlight) {
|
|
143
177
|
const rawStart = wrapIndex === 0 ? lineSyntaxHighlight.startColumn ?? 0 : 0;
|
|
144
|
-
line.syntaxHighlight = { language: lineSyntaxHighlight.language, start: Math.min(line.text.length,
|
|
178
|
+
line.syntaxHighlight = { language: lineSyntaxHighlight.language, start: Math.min(line.text.length, displayPrefix.length + rawStart) };
|
|
179
|
+
if (gutterSegment)
|
|
180
|
+
line.segments = [gutterSegment];
|
|
145
181
|
}
|
|
146
182
|
else if (wrapped.segments.length > 0) {
|
|
147
|
-
line.segments =
|
|
183
|
+
line.segments = [
|
|
184
|
+
...(gutterSegment ? [gutterSegment] : []),
|
|
185
|
+
...wrapped.segments.map((segment) => ({
|
|
186
|
+
...segment,
|
|
187
|
+
start: segment.start + displayPrefix.length,
|
|
188
|
+
end: segment.end + displayPrefix.length,
|
|
189
|
+
})),
|
|
190
|
+
];
|
|
191
|
+
}
|
|
192
|
+
else if (gutterSegment) {
|
|
193
|
+
line.segments = [gutterSegment];
|
|
148
194
|
}
|
|
149
195
|
lines.push(line);
|
|
150
196
|
}
|
|
151
197
|
}
|
|
198
|
+
if (showGutter && lines.length > 0) {
|
|
199
|
+
const lastLine = lines.at(-1);
|
|
200
|
+
if (!lastLine)
|
|
201
|
+
return lines;
|
|
202
|
+
const gutterPrefix = toolBodyGutterPrefix();
|
|
203
|
+
if (lastLine.text.startsWith(gutterPrefix)) {
|
|
204
|
+
lastLine.text = `${toolBodyEndPrefix()}${lastLine.text.slice(gutterPrefix.length)}`;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
152
207
|
return lines;
|
|
153
208
|
}
|
|
154
209
|
const ANSI_STANDARD_COLORS = ["#000000", "#cd3131", "#0dbc79", "#e5e510", "#2472c8", "#bc3fbc", "#11a8cd", "#e5e5e5"];
|
|
@@ -124,6 +124,7 @@ export declare class AppMouseController {
|
|
|
124
124
|
private renderedConversationFrame;
|
|
125
125
|
constructor(host: AppMouseControllerHost, popupMenus: AppPopupMenuController, popupActions: AppPopupActionController, scrollController: AppScrollController, commandController: AppCommandController);
|
|
126
126
|
handleMouse(event: MouseEvent): void;
|
|
127
|
+
private toolTargetContainsEvent;
|
|
127
128
|
activeClickFlash(): ClickFlash | undefined;
|
|
128
129
|
consumeClickFlashDirty(): boolean;
|
|
129
130
|
private withClickFlash;
|
|
@@ -133,6 +134,7 @@ export declare class AppMouseController {
|
|
|
133
134
|
private clickFlashForEvent;
|
|
134
135
|
private clickFlashForRegion;
|
|
135
136
|
private clickFlashRegionForEvent;
|
|
137
|
+
private toolClickFlashRegionForEvent;
|
|
136
138
|
private normalizedClickFlashRegion;
|
|
137
139
|
private inputClickFlashRegionForEvent;
|
|
138
140
|
private imageTargetAt;
|
|
@@ -7,6 +7,7 @@ import { sliceByDisplayColumns, stringDisplayWidth } from "../../terminal-width.
|
|
|
7
7
|
import { formatDcpStatsToast } from "../rendering/dcp-stats.js";
|
|
8
8
|
import { detectFileLinks } from "./file-links.js";
|
|
9
9
|
import { openFileLink as openDetectedFileLink } from "./file-link-opener.js";
|
|
10
|
+
import { APP_ICONS } from "../icons.js";
|
|
10
11
|
const CLICK_FLASH_MS = 100;
|
|
11
12
|
const LOST_MOUSE_RELEASE_SETTLE_MS = 180;
|
|
12
13
|
export class AppMouseController {
|
|
@@ -151,6 +152,8 @@ export class AppMouseController {
|
|
|
151
152
|
return;
|
|
152
153
|
}
|
|
153
154
|
if (target?.kind === "tool") {
|
|
155
|
+
if (!this.toolTargetContainsEvent(event))
|
|
156
|
+
return;
|
|
154
157
|
const entry = this.host.findEntry(target.id);
|
|
155
158
|
if (entry?.kind === "tool" || entry?.kind === "thinking" || entry?.kind === "shell") {
|
|
156
159
|
entry.expanded = !entry.expanded;
|
|
@@ -159,6 +162,16 @@ export class AppMouseController {
|
|
|
159
162
|
}
|
|
160
163
|
}
|
|
161
164
|
}
|
|
165
|
+
toolTargetContainsEvent(event) {
|
|
166
|
+
const text = this.renderedRowTexts.get(event.y) ?? "";
|
|
167
|
+
const gutter = toolGutterGlyphForLine(text);
|
|
168
|
+
if (gutter) {
|
|
169
|
+
const gutterWidth = Math.max(1, stringDisplayWidth(gutter));
|
|
170
|
+
return event.x >= 1 && event.x < 1 + gutterWidth;
|
|
171
|
+
}
|
|
172
|
+
const bounds = nonBlankLineBounds(text, event.x);
|
|
173
|
+
return event.x >= bounds.startColumn && event.x < bounds.endColumn;
|
|
174
|
+
}
|
|
162
175
|
activeClickFlash() {
|
|
163
176
|
return this.clickFlash;
|
|
164
177
|
}
|
|
@@ -230,7 +243,10 @@ export class AppMouseController {
|
|
|
230
243
|
const statusTarget = this.statusTargetAt(event);
|
|
231
244
|
if (statusTarget)
|
|
232
245
|
return statusTarget;
|
|
233
|
-
const
|
|
246
|
+
const target = this.renderedTargets.get(event.y);
|
|
247
|
+
if (target?.kind === "tool")
|
|
248
|
+
return this.toolClickFlashRegionForEvent(event);
|
|
249
|
+
const toastTarget = target;
|
|
234
250
|
if (toastTarget?.kind === "toast" && toastTargetContainsEvent(toastTarget, event)) {
|
|
235
251
|
return {
|
|
236
252
|
y: event.y,
|
|
@@ -244,6 +260,19 @@ export class AppMouseController {
|
|
|
244
260
|
}
|
|
245
261
|
return undefined;
|
|
246
262
|
}
|
|
263
|
+
toolClickFlashRegionForEvent(event) {
|
|
264
|
+
const text = this.renderedRowTexts.get(event.y) ?? "";
|
|
265
|
+
const gutter = toolGutterGlyphForLine(text);
|
|
266
|
+
if (gutter) {
|
|
267
|
+
const gutterWidth = Math.max(1, stringDisplayWidth(gutter));
|
|
268
|
+
const region = { y: event.y, startColumn: 1, endColumn: 1 + gutterWidth };
|
|
269
|
+
return event.x >= region.startColumn && event.x < region.endColumn ? region : undefined;
|
|
270
|
+
}
|
|
271
|
+
const bounds = nonBlankLineBounds(text, event.x);
|
|
272
|
+
return event.x >= bounds.startColumn && event.x < bounds.endColumn
|
|
273
|
+
? { y: event.y, startColumn: bounds.startColumn, endColumn: bounds.endColumn }
|
|
274
|
+
: undefined;
|
|
275
|
+
}
|
|
247
276
|
normalizedClickFlashRegion(region) {
|
|
248
277
|
const columns = Math.max(1, this.host.terminalColumns());
|
|
249
278
|
const y = Math.max(1, region.y);
|
|
@@ -995,6 +1024,13 @@ function nonBlankLineBounds(text, fallbackColumn) {
|
|
|
995
1024
|
? { startColumn: fallbackColumn, endColumn: fallbackColumn + 1 }
|
|
996
1025
|
: { startColumn, endColumn };
|
|
997
1026
|
}
|
|
1027
|
+
function toolGutterGlyphForLine(text) {
|
|
1028
|
+
for (const glyph of [APP_ICONS.toolBodyGutter, APP_ICONS.toolBodyEnd, APP_ICONS.toolPreviewTruncated]) {
|
|
1029
|
+
if (text.startsWith(`${glyph} `))
|
|
1030
|
+
return glyph;
|
|
1031
|
+
}
|
|
1032
|
+
return undefined;
|
|
1033
|
+
}
|
|
998
1034
|
function displayCellAtColumn(text, column) {
|
|
999
1035
|
if (column < 1)
|
|
1000
1036
|
return " ";
|
|
@@ -363,6 +363,9 @@ export class AppSessionEventController {
|
|
|
363
363
|
}
|
|
364
364
|
if (!this.assistantTextBuffer)
|
|
365
365
|
return visibleText;
|
|
366
|
+
if (!final && shouldHoldAssistantStreamWhitespaceTail(this.assistantTextBuffer, this.hasVisibleAssistantText(visibleText))) {
|
|
367
|
+
return visibleText;
|
|
368
|
+
}
|
|
366
369
|
if (shouldHoldAssistantStreamTail(this.assistantTextBuffer, this.hasVisibleAssistantText(visibleText))) {
|
|
367
370
|
if (final)
|
|
368
371
|
this.assistantTextBuffer = "";
|
|
@@ -445,6 +448,9 @@ function shouldHoldAssistantStreamTail(text, hasVisibleText) {
|
|
|
445
448
|
return !hasVisibleText;
|
|
446
449
|
return isPotentialDcpMetadataLine(text);
|
|
447
450
|
}
|
|
451
|
+
function shouldHoldAssistantStreamWhitespaceTail(text, hasVisibleText) {
|
|
452
|
+
return hasVisibleText && text.trim().length === 0;
|
|
453
|
+
}
|
|
448
454
|
function isHiddenMarkdownMetadataLine(line) {
|
|
449
455
|
return isMarkdownReferenceDefinition(line) || isPotentialDcpMetadataLine(line);
|
|
450
456
|
}
|
|
@@ -41,8 +41,10 @@ export declare class AppTabsController {
|
|
|
41
41
|
private readonly runtimeLoadsByTabId;
|
|
42
42
|
private readonly runtimeSubscriptionsByTabId;
|
|
43
43
|
private readonly runtimeRefreshTimersByTabId;
|
|
44
|
+
private readonly historyReloadTimersByTabId;
|
|
44
45
|
private readonly inputStatesByTabId;
|
|
45
46
|
private readonly deferredUserMessagesByTabId;
|
|
47
|
+
private readonly tabIdsNeedingHistoryReload;
|
|
46
48
|
private activeTabId;
|
|
47
49
|
private pendingActiveTabId;
|
|
48
50
|
private historyLoadGeneration;
|
|
@@ -89,6 +91,9 @@ export declare class AppTabsController {
|
|
|
89
91
|
private shouldScheduleDelayedSyncForRuntimeEvent;
|
|
90
92
|
private scheduleDelayedRuntimeSync;
|
|
91
93
|
private clearRuntimeRefreshTimers;
|
|
94
|
+
private clearHistoryReloadTimers;
|
|
95
|
+
private scheduleDelayedHistoryReload;
|
|
96
|
+
private reloadActiveTabHistoryIfNeeded;
|
|
92
97
|
private syncTabFromObservedRuntime;
|
|
93
98
|
private storeActiveInputState;
|
|
94
99
|
private storeActiveDeferredUserMessages;
|
|
@@ -20,8 +20,10 @@ export class AppTabsController {
|
|
|
20
20
|
runtimeLoadsByTabId = new Map();
|
|
21
21
|
runtimeSubscriptionsByTabId = new Map();
|
|
22
22
|
runtimeRefreshTimersByTabId = new Map();
|
|
23
|
+
historyReloadTimersByTabId = new Map();
|
|
23
24
|
inputStatesByTabId = new Map();
|
|
24
25
|
deferredUserMessagesByTabId = new Map();
|
|
26
|
+
tabIdsNeedingHistoryReload = new Set();
|
|
25
27
|
activeTabId;
|
|
26
28
|
pendingActiveTabId;
|
|
27
29
|
historyLoadGeneration = 0;
|
|
@@ -632,6 +634,7 @@ export class AppTabsController {
|
|
|
632
634
|
void this.saveTabs();
|
|
633
635
|
this.scheduleTabPrewarm();
|
|
634
636
|
await this.loadActiveSessionHistory(targetRuntime);
|
|
637
|
+
this.scheduleDelayedHistoryReload(target.id, targetRuntime);
|
|
635
638
|
}
|
|
636
639
|
async closeTab(tabId) {
|
|
637
640
|
if (this.pendingActiveTabId) {
|
|
@@ -814,6 +817,8 @@ export class AppTabsController {
|
|
|
814
817
|
this.runtimesByTabId.delete(tabId);
|
|
815
818
|
this.runtimeLoadsByTabId.delete(tabId);
|
|
816
819
|
this.clearRuntimeRefreshTimers(tabId);
|
|
820
|
+
this.clearHistoryReloadTimers(tabId);
|
|
821
|
+
this.tabIdsNeedingHistoryReload.delete(tabId);
|
|
817
822
|
const subscription = this.runtimeSubscriptionsByTabId.get(tabId);
|
|
818
823
|
subscription?.unsubscribe();
|
|
819
824
|
this.runtimeSubscriptionsByTabId.delete(tabId);
|
|
@@ -822,6 +827,9 @@ export class AppTabsController {
|
|
|
822
827
|
for (const tabId of this.runtimeRefreshTimersByTabId.keys()) {
|
|
823
828
|
this.clearRuntimeRefreshTimers(tabId);
|
|
824
829
|
}
|
|
830
|
+
for (const tabId of this.historyReloadTimersByTabId.keys()) {
|
|
831
|
+
this.clearHistoryReloadTimers(tabId);
|
|
832
|
+
}
|
|
825
833
|
for (const subscription of this.runtimeSubscriptionsByTabId.values()) {
|
|
826
834
|
subscription.unsubscribe();
|
|
827
835
|
}
|
|
@@ -835,6 +843,7 @@ export class AppTabsController {
|
|
|
835
843
|
const unsubscribe = runtime.session.subscribe((event) => {
|
|
836
844
|
if (this.shouldScheduleDelayedSyncForRuntimeEvent(event)) {
|
|
837
845
|
this.scheduleDelayedRuntimeSync(tabId, runtime);
|
|
846
|
+
this.tabIdsNeedingHistoryReload.add(tabId);
|
|
838
847
|
}
|
|
839
848
|
if (!this.shouldSyncTabFromRuntimeEvent(event))
|
|
840
849
|
return;
|
|
@@ -878,6 +887,44 @@ export class AppTabsController {
|
|
|
878
887
|
clearTimeout(timer);
|
|
879
888
|
this.runtimeRefreshTimersByTabId.delete(tabId);
|
|
880
889
|
}
|
|
890
|
+
clearHistoryReloadTimers(tabId) {
|
|
891
|
+
const timers = this.historyReloadTimersByTabId.get(tabId);
|
|
892
|
+
if (!timers)
|
|
893
|
+
return;
|
|
894
|
+
for (const timer of timers)
|
|
895
|
+
clearTimeout(timer);
|
|
896
|
+
this.historyReloadTimersByTabId.delete(tabId);
|
|
897
|
+
}
|
|
898
|
+
scheduleDelayedHistoryReload(tabId, runtime) {
|
|
899
|
+
if (!this.tabIdsNeedingHistoryReload.has(tabId))
|
|
900
|
+
return;
|
|
901
|
+
if (tabId !== this.activeTabId || this.pendingActiveTabId !== undefined)
|
|
902
|
+
return;
|
|
903
|
+
this.clearHistoryReloadTimers(tabId);
|
|
904
|
+
for (const delayMs of [150, 1000, 3000]) {
|
|
905
|
+
const timer = setTimeout(() => {
|
|
906
|
+
this.historyReloadTimersByTabId.get(tabId)?.delete(timer);
|
|
907
|
+
void this.reloadActiveTabHistoryIfNeeded(tabId, runtime, delayMs === 3000);
|
|
908
|
+
}, delayMs);
|
|
909
|
+
timer.unref?.();
|
|
910
|
+
let timers = this.historyReloadTimersByTabId.get(tabId);
|
|
911
|
+
if (!timers) {
|
|
912
|
+
timers = new Set();
|
|
913
|
+
this.historyReloadTimersByTabId.set(tabId, timers);
|
|
914
|
+
}
|
|
915
|
+
timers.add(timer);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
async reloadActiveTabHistoryIfNeeded(tabId, runtime, finalAttempt) {
|
|
919
|
+
if (tabId !== this.activeTabId || this.pendingActiveTabId !== undefined || this.host.runtime() !== runtime)
|
|
920
|
+
return;
|
|
921
|
+
if (!this.tabIdsNeedingHistoryReload.has(tabId))
|
|
922
|
+
return;
|
|
923
|
+
await this.loadActiveSessionHistory(runtime);
|
|
924
|
+
if (finalAttempt && tabId === this.activeTabId && this.host.runtime() === runtime) {
|
|
925
|
+
this.tabIdsNeedingHistoryReload.delete(tabId);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
881
928
|
syncTabFromObservedRuntime(tabId, runtime) {
|
|
882
929
|
const tab = this.tabItems.find((item) => item.id === tabId);
|
|
883
930
|
if (!tab) {
|
|
@@ -40,6 +40,7 @@ const DEFAULT_LOOKUP_TIMEOUT_MS = 120_000;
|
|
|
40
40
|
const MAX_IMAGE_BYTES = 16 * 1024 * 1024;
|
|
41
41
|
const SILENCE_REMINDER_MIN_VIOLATION_GAP = 3;
|
|
42
42
|
const SILENCE_REMINDER_MIN_MESSAGE_GAP = 12;
|
|
43
|
+
const LOOKUP_TOOL_NAME = "lookup";
|
|
43
44
|
|
|
44
45
|
const LOOKUP_TOOL_PARAMS = Type.Object(
|
|
45
46
|
{
|
|
@@ -187,16 +188,35 @@ export default function glmCodingDiscipline(pi: ExtensionAPI) {
|
|
|
187
188
|
pi.registerTool(createLookupTool());
|
|
188
189
|
}
|
|
189
190
|
|
|
191
|
+
function syncLookupToolAvailability(modelRef: string | undefined, cwd?: string): void {
|
|
192
|
+
const activeTools = typeof pi.getActiveTools === "function" ? pi.getActiveTools() : undefined;
|
|
193
|
+
if (!Array.isArray(activeTools)) return;
|
|
194
|
+
|
|
195
|
+
const lookupEnabled = Boolean(lookupModelFromConfig(cwd));
|
|
196
|
+
const shouldExposeLookup = lookupEnabled && isGlmModel(modelRef);
|
|
197
|
+
const hasLookup = activeTools.includes(LOOKUP_TOOL_NAME);
|
|
198
|
+
|
|
199
|
+
if (shouldExposeLookup === hasLookup) return;
|
|
200
|
+
if (typeof pi.setActiveTools !== "function") return;
|
|
201
|
+
|
|
202
|
+
const nextTools = shouldExposeLookup
|
|
203
|
+
? [...activeTools, LOOKUP_TOOL_NAME]
|
|
204
|
+
: activeTools.filter((tool: unknown) => tool !== LOOKUP_TOOL_NAME);
|
|
205
|
+
pi.setActiveTools([...new Set(nextTools)]);
|
|
206
|
+
}
|
|
207
|
+
|
|
190
208
|
maybeRegisterLookupTool(process.cwd());
|
|
191
209
|
|
|
192
210
|
pi.on("session_start", async (_event: unknown, ctx: unknown) => {
|
|
193
211
|
selectedModelRef = modelRefFromContext(ctx);
|
|
194
212
|
maybeRegisterLookupTool(contextCwd(ctx));
|
|
213
|
+
syncLookupToolAvailability(selectedModelRef, contextCwd(ctx));
|
|
195
214
|
});
|
|
196
215
|
|
|
197
216
|
pi.on("model_select", async (event: { model?: unknown }, ctx: unknown) => {
|
|
198
217
|
selectedModelRef = modelRefFromModel(event.model) ?? modelRefFromContext(ctx);
|
|
199
218
|
maybeRegisterLookupTool(contextCwd(ctx));
|
|
219
|
+
syncLookupToolAvailability(selectedModelRef, contextCwd(ctx));
|
|
200
220
|
});
|
|
201
221
|
|
|
202
222
|
pi.on("before_provider_request", async (event: { payload?: unknown }, ctx: unknown) => {
|
|
@@ -273,7 +293,7 @@ export function injectCodingDisciplineIntoPayload(payload: unknown, options: { l
|
|
|
273
293
|
|
|
274
294
|
function createLookupTool() {
|
|
275
295
|
return {
|
|
276
|
-
name:
|
|
296
|
+
name: LOOKUP_TOOL_NAME,
|
|
277
297
|
label: "Lookup",
|
|
278
298
|
description: [
|
|
279
299
|
"Ask the configured vision-capable lookup model to inspect recent image/screenshot context and answer a focused visual question.",
|