pi-cc-extensions 0.8.63 → 0.8.64
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/extensions/feature/compact-thinking.ts +56 -11
- package/extensions/index.ts +0 -2
- package/extensions/renderer/mouse/hover.ts +13 -0
- package/extensions/renderer/mouse/interaction.ts +16 -5
- package/extensions/renderer/tool/message-display.ts +18 -1
- package/package.json +1 -1
- package/extensions/feature/shell/model-status.ts +0 -19
|
@@ -200,12 +200,58 @@ function cachePreview(key: string, entry: PreviewCacheEntry): void {
|
|
|
200
200
|
|
|
201
201
|
export function clearThinkingPreviewCache(): void {
|
|
202
202
|
previewCache.clear();
|
|
203
|
+
expandedWrapCache.clear();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
type ExpandedWrapEntry = { text: string; lines: string[] };
|
|
207
|
+
const expandedWrapCache = new Map<string, ExpandedWrapEntry>();
|
|
208
|
+
export const THINKING_EXPANDED_WRAP_CACHE_MAX = 64;
|
|
209
|
+
|
|
210
|
+
function expandedWrapKey(messageTimestamp: number, runStartIndex: number, width: number): string {
|
|
211
|
+
return `${messageTimestamp}:${runStartIndex}:${width}`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function wrapExpandedThinking(
|
|
215
|
+
messageTimestamp: number,
|
|
216
|
+
runStartIndex: number,
|
|
217
|
+
text: string,
|
|
218
|
+
width: number,
|
|
219
|
+
): string[] {
|
|
220
|
+
const key = expandedWrapKey(messageTimestamp, runStartIndex, width);
|
|
221
|
+
const hit = expandedWrapCache.get(key);
|
|
222
|
+
if (hit && hit.text === text) {
|
|
223
|
+
expandedWrapCache.delete(key);
|
|
224
|
+
expandedWrapCache.set(key, hit);
|
|
225
|
+
return hit.lines;
|
|
226
|
+
}
|
|
227
|
+
const lines = wrapTextWithAnsi(text.replace(/\t/g, " "), Math.max(1, width));
|
|
228
|
+
expandedWrapCache.delete(key);
|
|
229
|
+
while (expandedWrapCache.size >= THINKING_EXPANDED_WRAP_CACHE_MAX) {
|
|
230
|
+
const oldest = expandedWrapCache.keys().next().value;
|
|
231
|
+
if (oldest === undefined) break;
|
|
232
|
+
expandedWrapCache.delete(oldest);
|
|
233
|
+
}
|
|
234
|
+
expandedWrapCache.set(key, { text, lines });
|
|
235
|
+
return lines;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function evictExpandedWraps(messageTimestamp: number, runStartIndex: number): void {
|
|
239
|
+
for (const key of expandedWrapCache.keys()) {
|
|
240
|
+
const [ts, run] = key.split(":");
|
|
241
|
+
if (Number(ts) === messageTimestamp && Number(run) === runStartIndex) {
|
|
242
|
+
expandedWrapCache.delete(key);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
203
245
|
}
|
|
204
246
|
|
|
205
247
|
export function thinkingPreviewCacheSize(): number {
|
|
206
248
|
return previewCache.size;
|
|
207
249
|
}
|
|
208
250
|
|
|
251
|
+
export function thinkingExpandedWrapCacheSize(): number {
|
|
252
|
+
return expandedWrapCache.size;
|
|
253
|
+
}
|
|
254
|
+
|
|
209
255
|
/** 按可见宽度估算折行数,不走 Text 全量 wrap。无换行长段也能计到隐藏行。 */
|
|
210
256
|
function countWrappedLines(text: string, contentWidth: number): number {
|
|
211
257
|
if (!text) return 0;
|
|
@@ -285,12 +331,12 @@ export class ThinkingPreviewBlock implements Component {
|
|
|
285
331
|
private heading: string;
|
|
286
332
|
private text: string;
|
|
287
333
|
private padding: number;
|
|
288
|
-
|
|
334
|
+
readonly messageTimestamp: number;
|
|
335
|
+
readonly runStartIndex: number;
|
|
289
336
|
private style: (text: string) => string;
|
|
290
337
|
private theme: Theme | undefined;
|
|
291
338
|
private _expanded: boolean;
|
|
292
339
|
private hintHovered = false;
|
|
293
|
-
private expandedBody: { width: number; lines: string[] } | undefined;
|
|
294
340
|
private collapsedMemo:
|
|
295
341
|
| {
|
|
296
342
|
width: number;
|
|
@@ -308,11 +354,13 @@ export class ThinkingPreviewBlock implements Component {
|
|
|
308
354
|
messageTimestamp: number,
|
|
309
355
|
style: (text: string) => string,
|
|
310
356
|
theme?: Theme,
|
|
357
|
+
runStartIndex = 0,
|
|
311
358
|
) {
|
|
312
359
|
this.heading = heading;
|
|
313
360
|
this.text = text;
|
|
314
361
|
this.padding = padding;
|
|
315
362
|
this.messageTimestamp = messageTimestamp;
|
|
363
|
+
this.runStartIndex = runStartIndex;
|
|
316
364
|
this.style = style;
|
|
317
365
|
this.theme = theme;
|
|
318
366
|
this._expanded = expandedThinking.has(messageTimestamp);
|
|
@@ -334,7 +382,7 @@ export class ThinkingPreviewBlock implements Component {
|
|
|
334
382
|
if (expanded) expandedThinking.add(this.messageTimestamp);
|
|
335
383
|
else {
|
|
336
384
|
expandedThinking.delete(this.messageTimestamp);
|
|
337
|
-
this.
|
|
385
|
+
evictExpandedWraps(this.messageTimestamp, this.runStartIndex);
|
|
338
386
|
}
|
|
339
387
|
}
|
|
340
388
|
|
|
@@ -368,13 +416,10 @@ export class ThinkingPreviewBlock implements Component {
|
|
|
368
416
|
return { lines: [], hiddenLines: 0 };
|
|
369
417
|
}
|
|
370
418
|
if (this._expanded) {
|
|
371
|
-
|
|
372
|
-
this.
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
return { lines: this.expandedBody.lines, hiddenLines: 0 };
|
|
419
|
+
return {
|
|
420
|
+
lines: wrapExpandedThinking(this.messageTimestamp, this.runStartIndex, this.text, width),
|
|
421
|
+
hiddenLines: 0,
|
|
422
|
+
};
|
|
378
423
|
}
|
|
379
424
|
const preview = layoutThinkingPreview(this.text, width, padding);
|
|
380
425
|
return { lines: preview.visible, hiddenLines: preview.hiddenLines };
|
|
@@ -430,7 +475,6 @@ export class ThinkingPreviewBlock implements Component {
|
|
|
430
475
|
|
|
431
476
|
invalidate() {
|
|
432
477
|
this.collapsedMemo = undefined;
|
|
433
|
-
this.expandedBody = undefined;
|
|
434
478
|
}
|
|
435
479
|
}
|
|
436
480
|
|
|
@@ -715,6 +759,7 @@ function compactThinking(pi: ExtensionAPI) {
|
|
|
715
759
|
message.timestamp,
|
|
716
760
|
thinkingStyle,
|
|
717
761
|
activeTheme,
|
|
762
|
+
runStartIndex,
|
|
718
763
|
),
|
|
719
764
|
);
|
|
720
765
|
|
package/extensions/index.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { config } from "./config/config.ts";
|
|
|
5
5
|
import piAliases from "./feature/shell/aliases.ts";
|
|
6
6
|
import piStartupHeader from "./feature/shell/startup-header.ts";
|
|
7
7
|
import workingMessage from "./feature/shell/working-message.ts";
|
|
8
|
-
import modelStatus from "./feature/shell/model-status.ts";
|
|
9
8
|
|
|
10
9
|
// feature
|
|
11
10
|
import agentAutocomplete from "./feature/reference/subagent.ts";
|
|
@@ -23,7 +22,6 @@ export default function (pi: ExtensionAPI): void {
|
|
|
23
22
|
if (config.enableAliases) piAliases(pi);
|
|
24
23
|
piStartupHeader(pi);
|
|
25
24
|
if (config.enableWorkingMessage) workingMessage(pi);
|
|
26
|
-
modelStatus(pi);
|
|
27
25
|
|
|
28
26
|
// render stack:thinking controller 直接交给 style 作 query
|
|
29
27
|
markdownEnhance(pi);
|
|
@@ -54,6 +54,16 @@ export function setHoveredThinking(block: ThinkingPreviewBlock | null): boolean
|
|
|
54
54
|
return true;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
let hoveredMessageDisplay: any = null;
|
|
58
|
+
|
|
59
|
+
export function setHoveredMessageDisplay(component: any): boolean {
|
|
60
|
+
if (component === hoveredMessageDisplay) return false;
|
|
61
|
+
hoveredMessageDisplay?.setHintHovered?.(false);
|
|
62
|
+
hoveredMessageDisplay = component;
|
|
63
|
+
component?.setHintHovered?.(true);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
57
67
|
let hoveredToolIoView: ExpandedToolIoView | null = null;
|
|
58
68
|
let hoveredToolIoSection: ToolIoSection | null = null;
|
|
59
69
|
|
|
@@ -110,6 +120,7 @@ export type FullscreenHoverTarget =
|
|
|
110
120
|
| { kind: "button" }
|
|
111
121
|
| { kind: "group"; component: ToolGroupComponent }
|
|
112
122
|
| { kind: "thinking"; component: ThinkingPreviewBlock }
|
|
123
|
+
| { kind: "message"; component: any }
|
|
113
124
|
| { kind: "assistant"; component: any }
|
|
114
125
|
| {
|
|
115
126
|
kind: "tool";
|
|
@@ -133,6 +144,8 @@ export function applyFullscreenHover(tui: any, target: FullscreenHoverTarget | n
|
|
|
133
144
|
if (setHoveredToolGroup(nextGroup)) changed = true;
|
|
134
145
|
const nextThinking = target?.kind === "thinking" ? target.component : null;
|
|
135
146
|
if (setHoveredThinking(nextThinking)) changed = true;
|
|
147
|
+
const nextMessage = target?.kind === "message" ? target.component : null;
|
|
148
|
+
if (setHoveredMessageDisplay(nextMessage)) changed = true;
|
|
136
149
|
const nextAssistant = target?.kind === "assistant" ? target.component : null;
|
|
137
150
|
if (setHoveredCompactAssistant(nextAssistant)) changed = true;
|
|
138
151
|
const nextView = target?.kind === "tool" ? target.view : null;
|
|
@@ -59,6 +59,7 @@ import {
|
|
|
59
59
|
setHoveredToolCallId,
|
|
60
60
|
setHoveredToolGroup,
|
|
61
61
|
setHoveredThinking,
|
|
62
|
+
setHoveredMessageDisplay,
|
|
62
63
|
setHoveredToolIo,
|
|
63
64
|
type FullscreenHoverTarget,
|
|
64
65
|
} from "./hover.ts";
|
|
@@ -155,13 +156,20 @@ function updateToolSummaryHover(tui: any, packet: SgrMousePacket): void {
|
|
|
155
156
|
}
|
|
156
157
|
|
|
157
158
|
const EXPAND_PANEL_DOUBLE_CLICK_MS = 400;
|
|
158
|
-
let lastExpandPanelClick: {
|
|
159
|
+
let lastExpandPanelClick: { id: unknown; at: number } | null = null;
|
|
160
|
+
|
|
161
|
+
function expandPanelIdentity(card: any): unknown {
|
|
162
|
+
return card instanceof ThinkingPreviewBlock
|
|
163
|
+
? `${card.messageTimestamp}:${card.runStartIndex}`
|
|
164
|
+
: card;
|
|
165
|
+
}
|
|
159
166
|
|
|
160
167
|
function isExpandPanelDoubleClick(card: any): boolean {
|
|
161
168
|
const now = Date.now();
|
|
169
|
+
const id = expandPanelIdentity(card);
|
|
162
170
|
const prev = lastExpandPanelClick;
|
|
163
|
-
lastExpandPanelClick = {
|
|
164
|
-
return Boolean(prev && prev.
|
|
171
|
+
lastExpandPanelClick = { id, at: now };
|
|
172
|
+
return Boolean(prev && prev.id === id && now - prev.at <= EXPAND_PANEL_DOUBLE_CLICK_MS);
|
|
165
173
|
}
|
|
166
174
|
|
|
167
175
|
function clearExpandPanelDoubleClick(): void {
|
|
@@ -174,6 +182,7 @@ function collapseExpandedCard(tui: any, card: any): boolean {
|
|
|
174
182
|
setHoveredToolCallId(null);
|
|
175
183
|
setHoveredToolGroup(null);
|
|
176
184
|
setHoveredThinking(null);
|
|
185
|
+
setHoveredMessageDisplay(null);
|
|
177
186
|
setHoveredToolIo(null, null);
|
|
178
187
|
setHoveredCompactAssistant(null);
|
|
179
188
|
card.invalidate?.();
|
|
@@ -345,6 +354,7 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
|
|
|
345
354
|
setHoveredToolCallId(null);
|
|
346
355
|
setHoveredToolGroup(null);
|
|
347
356
|
setHoveredThinking(null);
|
|
357
|
+
setHoveredMessageDisplay(null);
|
|
348
358
|
setHoveredToolIo(null, null);
|
|
349
359
|
setHoveredCompactAssistant(null);
|
|
350
360
|
card.invalidate?.();
|
|
@@ -416,8 +426,7 @@ function handleFullscreenToolHover(tui: any, packet: SgrMousePacket): void {
|
|
|
416
426
|
// compact 摘要行:折叠时仅提示文字高亮,展开卡整体高亮。
|
|
417
427
|
if (component.expanded || overHint) target = { kind: "assistant", component };
|
|
418
428
|
} else if (isMessageDisplayComponent(component)) {
|
|
419
|
-
|
|
420
|
-
if (component.expanded || overHint) target = { kind: "assistant", component };
|
|
429
|
+
if (overHint) target = { kind: "message", component };
|
|
421
430
|
}
|
|
422
431
|
}
|
|
423
432
|
}
|
|
@@ -814,6 +823,7 @@ export function teardownToolMouseInteraction(
|
|
|
814
823
|
setHoveredToolCallId(null);
|
|
815
824
|
setHoveredToolGroup(null);
|
|
816
825
|
setHoveredThinking(null);
|
|
826
|
+
setHoveredMessageDisplay(null);
|
|
817
827
|
setHoveredToolIo(null, null);
|
|
818
828
|
setHoveredCompactAssistant(null);
|
|
819
829
|
clearExpandPanelDoubleClick();
|
|
@@ -841,6 +851,7 @@ export function teardownToolMouseInteraction(
|
|
|
841
851
|
export function resetToolHoverState(): void {
|
|
842
852
|
setHoveredToolCallId(null);
|
|
843
853
|
setHoveredThinking(null);
|
|
854
|
+
setHoveredMessageDisplay(null);
|
|
844
855
|
setHoveredCompactAssistant(null);
|
|
845
856
|
setScrollButtonVisible(false);
|
|
846
857
|
setScrollButtonHovered(false);
|
|
@@ -55,6 +55,15 @@ const BRANCH_KIND: DisplayKind = {
|
|
|
55
55
|
body: (component) => String(component.message?.summary ?? ""),
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
+
function ensureHintHover(component: any): void {
|
|
59
|
+
if (typeof component.setHintHovered === "function") return;
|
|
60
|
+
component.setHintHovered = function (this: any, hovered: boolean) {
|
|
61
|
+
if (this.hintHovered === hovered) return;
|
|
62
|
+
this.hintHovered = hovered;
|
|
63
|
+
this.invalidate?.();
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
58
67
|
function renderCcstyle(component: any, kind: DisplayKind): void {
|
|
59
68
|
const theme = displayTheme;
|
|
60
69
|
if (!theme) return; // 主题未就绪时保留原生渲染
|
|
@@ -64,13 +73,21 @@ function renderCcstyle(component: any, kind: DisplayKind): void {
|
|
|
64
73
|
component.paddingY = 0; // 工具组件 paddingY=0;原生 Box 默认 1,会上下各留一个空行
|
|
65
74
|
}
|
|
66
75
|
component.clear();
|
|
76
|
+
ensureHintHover(component);
|
|
67
77
|
const icon = `${BRIGHT_GREEN}✓${ANSI_FG_RESET}`; // 已完成消息,等同工具成功态
|
|
68
78
|
const title = theme.fg("toolTitle", kind.title(component));
|
|
69
79
|
if (!component.expanded) {
|
|
70
|
-
const
|
|
80
|
+
const hovered = component.hintHovered === true;
|
|
81
|
+
const hint = `${theme.fg("dim", " • ")}${theme.fg(hovered ? "text" : "dim", showMoreHintText())}`;
|
|
71
82
|
component.addChild(new Text(`${icon} ${title}${hint}`, 0, 0));
|
|
72
83
|
return;
|
|
73
84
|
}
|
|
85
|
+
// 展开卡与 tool 一致:userMessageBg + 上下左右 1 格
|
|
86
|
+
component.paddingX = 1;
|
|
87
|
+
component.paddingY = 1;
|
|
88
|
+
if (typeof theme.bg === "function" && typeof component.setBgFn === "function") {
|
|
89
|
+
component.setBgFn((text: string) => theme.bg("userMessageBg", text));
|
|
90
|
+
}
|
|
74
91
|
component.addChild(new Text(`${icon} ${title}`, 0, 0));
|
|
75
92
|
component.addChild(new Spacer(1));
|
|
76
93
|
component.addChild(
|
package/package.json
CHANGED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
|
|
3
|
-
function modelLabel(model: any): string | undefined {
|
|
4
|
-
if (!model) return undefined;
|
|
5
|
-
const name = model.name || model.id;
|
|
6
|
-
if (!name) return undefined;
|
|
7
|
-
return model.provider ? `${model.provider} · ${name}` : name;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function apply(ctx: any): void {
|
|
11
|
-
if (!ctx?.hasUI) return;
|
|
12
|
-
ctx.ui?.setStatus?.("model", modelLabel(ctx.model));
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** 用 setStatus 挂模型名,不 setFooter,不接管原生 footer。 */
|
|
16
|
-
export default function (pi: ExtensionAPI): void {
|
|
17
|
-
pi.on("session_start", async (_event, ctx) => apply(ctx));
|
|
18
|
-
pi.on("model_select", async (_event, ctx) => apply(ctx));
|
|
19
|
-
}
|