pi-cc-extensions 0.9.4 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +4 -4
- package/README.md +4 -4
- package/extensions/config/config.ts +2 -3
- package/extensions/config/panel.ts +2 -3
- package/extensions/feature/compact-thinking.ts +1 -1
- package/extensions/feature/reference/index.ts +23 -36
- package/extensions/feature/reference/session.ts +95 -1
- package/extensions/feature/reference/subagent.ts +60 -21
- package/extensions/renderer/compact-mode.ts +174 -62
- package/extensions/renderer/default-mode.ts +36 -39
- package/extensions/renderer/index.ts +5 -3
- package/extensions/renderer/mouse/interaction.ts +251 -80
- package/extensions/renderer/mouse/layout.ts +14 -5
- package/extensions/renderer/tool/diff/diff-component.ts +19 -2
- package/extensions/renderer/tool/diff/diff-edit-render.ts +18 -9
- package/extensions/renderer/tool/diff/diff-limits.ts +22 -21
- package/extensions/renderer/tool/diff/diff-write-render.ts +28 -18
- package/extensions/renderer/tool/grouping.ts +21 -5
- package/extensions/renderer/tool/mcp-title.ts +39 -0
- package/extensions/renderer/tool/names.ts +103 -1
- package/extensions/renderer/tool/result.ts +6 -1
- package/extensions/renderer/tool/show-more-hint.ts +10 -0
- package/package.json +2 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Compact mode:每条含 toolCall 的 assistant message 折叠为一条逐步累加的摘要行
|
|
3
3
|
* (`Ran for 8s, bash×2, read×2`),edit/write 独立标题行(`✓ write <path> (+25 -0)`),
|
|
4
|
-
* 普通工具折叠时不显示独立行;展开(Ctrl+O / fullscreen
|
|
5
|
-
* compact-thinking/Pi 原生或专用
|
|
4
|
+
* 普通工具折叠时不显示独立行;展开(Ctrl+O / fullscreen 点击)时助手文本按原生渲染,
|
|
5
|
+
* thinking 与工具卡装进 userMessageBg 面板(卡内恢复 compact-thinking/Pi 原生或专用
|
|
6
|
+
* renderer)。edit/write 复用 mode=on 的 rich diff
|
|
6
7
|
* 与 `editDiffCollapsedLines` / `writeDiffCollapsedLines` / `expandedPreviewMaxLines`。
|
|
7
8
|
* Agent/Task 族:调用只进摘要计数,tool 卡始终折叠(避免 pending→完成高度闪动)。
|
|
8
9
|
* 底部 Agents/Tasks 面板由 pi-subagents/pi-tasks 独立 widget 负责,不经 tool 卡外置。
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
import { renderRichToolResult } from "./tool/diff/index.ts";
|
|
36
37
|
import type { WriteExecutionMetadataStore } from "./tool/diff/write-execution.ts";
|
|
37
38
|
import { isToolCallHovered } from "./mouse/hover.ts";
|
|
39
|
+
import { getToolMouseTui } from "./mouse/scroll.ts";
|
|
38
40
|
import { insetComponent, renderExpandedToolResult, scheduleAnimation } from "./tool/result.ts";
|
|
39
41
|
import { paddedBackgroundRow } from "./tool/grouping.ts";
|
|
40
42
|
import { formatDisplayPath } from "./tool/names.ts";
|
|
@@ -50,6 +52,11 @@ import {
|
|
|
50
52
|
PROTOTYPE_ORIGINAL_KEY,
|
|
51
53
|
} from "../utils/patch-keys.ts";
|
|
52
54
|
|
|
55
|
+
/** Pi 的 native 组件与扩展可能各持一份 pi-tui,Spacer 不能只靠 instanceof 判。 */
|
|
56
|
+
function isSpacerComponent(value: any): boolean {
|
|
57
|
+
return value instanceof Spacer || value?.constructor?.name === "Spacer";
|
|
58
|
+
}
|
|
59
|
+
|
|
53
60
|
/** compact 渲染层对 compact-thinking 的只读查询面(不建第二套计时器)。 */
|
|
54
61
|
export type CompactThinkingQuery = {
|
|
55
62
|
getMessageThinkingDurationMs(messageTimestamp: number): number | undefined;
|
|
@@ -320,14 +327,18 @@ function editWriteExpandedCard(theme: any): any {
|
|
|
320
327
|
);
|
|
321
328
|
}
|
|
322
329
|
|
|
323
|
-
/** compact
|
|
324
|
-
*
|
|
325
|
-
*
|
|
330
|
+
/** compact 展开面板:外卡片保持 userMessageBg;展开的 thinking 另包一层更深的
|
|
331
|
+
* 内卡(自身 Box 背景与外卡同色,不区分会糊在一起),工具卡直接铺在外卡背景上。
|
|
332
|
+
* 每个子卡前面补 1 行分隔(上一条已经是空行时不再补),子卡自带的首尾空行会被裁掉,
|
|
333
|
+
* 避免与外卡/内卡 padding 叠出双空行。
|
|
334
|
+
* hits:子卡的行区间与命中目标(thinking 命中自身,工具命中 tool 组件),
|
|
335
|
+
* 供展开后点击 hint。未开启 toolHits 时不映射工具,点击仍归外层面板。 */
|
|
326
336
|
function layoutExpandedToolCard(
|
|
327
337
|
theme: any,
|
|
328
338
|
children: any[],
|
|
329
339
|
width: number,
|
|
330
340
|
paints?: string[][],
|
|
341
|
+
toolHits = false,
|
|
331
342
|
): { lines: string[]; hits: Array<{ child: any; start: number; end: number }> } {
|
|
332
343
|
const slot = "userMessageBg";
|
|
333
344
|
const toolBgAnsi = darkenBgAnsi(theme, slot);
|
|
@@ -335,31 +346,18 @@ function layoutExpandedToolCard(
|
|
|
335
346
|
const lines: string[] = [];
|
|
336
347
|
const hits: Array<{ child: any; start: number; end: number }> = [];
|
|
337
348
|
const isThinkingPreview = (child: any) => typeof child?.setHintHovered === "function";
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
349
|
+
let lastBlank = false;
|
|
350
|
+
const pushBlank = () => {
|
|
351
|
+
if (lastBlank) return;
|
|
352
|
+
lines.push(paddedBackgroundRow(theme, slot, "", width));
|
|
353
|
+
lastBlank = true;
|
|
354
|
+
};
|
|
341
355
|
for (let childIndex = 0; childIndex < children.length; childIndex++) {
|
|
342
356
|
const child = children[childIndex];
|
|
343
357
|
const painted = paints?.[childIndex];
|
|
344
358
|
const childLines = Array.isArray(painted) ? painted : child.render(innerWidth);
|
|
345
|
-
// 展开的 thinking
|
|
346
|
-
const
|
|
347
|
-
if (!nest) {
|
|
348
|
-
let start = 0;
|
|
349
|
-
if (skipLeadingBlank) {
|
|
350
|
-
while (start < childLines.length && !hasVisibleText(childLines[start])) start++;
|
|
351
|
-
skipLeadingBlank = false;
|
|
352
|
-
}
|
|
353
|
-
const rangeStart = lines.length;
|
|
354
|
-
for (let i = start; i < childLines.length; i++) {
|
|
355
|
-
lines.push(paddedBackgroundRow(theme, slot, childLines[i], width));
|
|
356
|
-
}
|
|
357
|
-
if (lines.length > rangeStart) hits.push({ child, start: rangeStart, end: lines.length });
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
// 工具卡:前导 1 空行 + 首尾文本行之间的内容区(深色);不保留尾随,
|
|
361
|
-
// 相邻工具卡之间正好 1 空行,底部由卡片 padding 收尾。
|
|
362
|
-
skipLeadingBlank = false;
|
|
359
|
+
// 展开的 thinking 带深色内卡(自带上下 padding,不再补外卡分隔行)。
|
|
360
|
+
const innerCard = isThinkingPreview(child) && child.expanded === true;
|
|
363
361
|
let first = -1;
|
|
364
362
|
let last = -1;
|
|
365
363
|
for (let i = 0; i < childLines.length; i++) {
|
|
@@ -371,26 +369,36 @@ function layoutExpandedToolCard(
|
|
|
371
369
|
if (first < 0) {
|
|
372
370
|
for (const line of childLines) {
|
|
373
371
|
lines.push(paddedBackgroundRow(theme, slot, line, width));
|
|
372
|
+
lastBlank = !hasVisibleText(line);
|
|
374
373
|
}
|
|
375
374
|
continue;
|
|
376
375
|
}
|
|
377
|
-
|
|
376
|
+
if (!innerCard) pushBlank();
|
|
378
377
|
const rangeStart = lines.length;
|
|
379
|
-
//
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
lines.push(
|
|
383
|
-
|
|
384
|
-
|
|
378
|
+
// 展开的 thinking 走深色内卡(上下各 1 行 padding),工具卡直接铺在外卡上。
|
|
379
|
+
const paintRow = innerCard
|
|
380
|
+
? (line: string) => lines.push(toolCardBgRow(theme, slot, toolBgAnsi, line, width))
|
|
381
|
+
: (line: string) => lines.push(paddedBackgroundRow(theme, slot, line, width));
|
|
382
|
+
if (innerCard) paintRow("");
|
|
383
|
+
for (let i = first; i <= last; i++) paintRow(childLines[i]);
|
|
384
|
+
if (innerCard) paintRow("");
|
|
385
|
+
// 内卡的下 padding 不能当分隔:后面的子卡要再留 1 行外卡空行。
|
|
386
|
+
lastBlank = false;
|
|
387
|
+
// thinking 命中自身;工具在展开卡内需要能二次展开时才映射回 tool 组件。
|
|
385
388
|
if (isThinkingPreview(child)) hits.push({ child, start: rangeStart, end: lines.length });
|
|
389
|
+
else if (toolHits && child.__ccTool) {
|
|
390
|
+
hits.push({ child: child.__ccTool, start: rangeStart, end: lines.length });
|
|
391
|
+
}
|
|
386
392
|
}
|
|
387
|
-
|
|
393
|
+
pushBlank();
|
|
388
394
|
return { lines, hits };
|
|
389
395
|
}
|
|
390
396
|
|
|
391
397
|
function compactRoundCard(
|
|
392
398
|
cardItems: Array<{ child?: any; tool?: any }>,
|
|
393
399
|
toolRender: (tool: any, width: number) => string[],
|
|
400
|
+
/** true 时卡内工具行归 tool 组件,可点击二次展开(整回合展开卡)。 */
|
|
401
|
+
toolHits = false,
|
|
394
402
|
): any {
|
|
395
403
|
const children: any[] = [];
|
|
396
404
|
for (const item of cardItems) {
|
|
@@ -399,6 +407,7 @@ function compactRoundCard(
|
|
|
399
407
|
const tool = item.tool;
|
|
400
408
|
children.push({
|
|
401
409
|
__ccToolCard: true,
|
|
410
|
+
__ccTool: tool,
|
|
402
411
|
render: (innerWidth: number) => toolRender(tool, innerWidth),
|
|
403
412
|
invalidate: () => tool.invalidate?.(),
|
|
404
413
|
});
|
|
@@ -429,12 +438,12 @@ function compactRoundCard(
|
|
|
429
438
|
) {
|
|
430
439
|
return paint;
|
|
431
440
|
}
|
|
432
|
-
const laid = layoutExpandedToolCard(theme, children, width, paints);
|
|
441
|
+
const laid = layoutExpandedToolCard(theme, children, width, paints, toolHits);
|
|
433
442
|
paint = {
|
|
434
443
|
width,
|
|
435
444
|
theme,
|
|
436
445
|
paints,
|
|
437
|
-
lines:
|
|
446
|
+
lines: laid.lines,
|
|
438
447
|
hits: laid.hits,
|
|
439
448
|
};
|
|
440
449
|
return paint;
|
|
@@ -445,10 +454,8 @@ function compactRoundCard(
|
|
|
445
454
|
return layout(width).lines;
|
|
446
455
|
},
|
|
447
456
|
childAtRow(localRow: number, width: number) {
|
|
448
|
-
if (localRow < 1) return null;
|
|
449
|
-
const row = localRow - 1;
|
|
450
457
|
for (const hit of layout(width).hits) {
|
|
451
|
-
if (
|
|
458
|
+
if (localRow >= hit.start && localRow < hit.end) return hit.child;
|
|
452
459
|
}
|
|
453
460
|
return null;
|
|
454
461
|
},
|
|
@@ -531,6 +538,43 @@ export function setHoveredCompactAssistant(component: any): boolean {
|
|
|
531
538
|
return true;
|
|
532
539
|
}
|
|
533
540
|
|
|
541
|
+
/**
|
|
542
|
+
* 整回合展开卡内的工具被点击展开时调用:让 compact 的强制折叠放行这一个,
|
|
543
|
+
* 并把同一回合里其他展开的工具收回去(保持单开)。非 round 内工具是空操作。
|
|
544
|
+
*/
|
|
545
|
+
let roundToolExpansion: { markUserExpanded(tool: any): void } | undefined;
|
|
546
|
+
|
|
547
|
+
export function markCompactRoundToolExpanded(tool: any): void {
|
|
548
|
+
roundToolExpansion?.markUserExpanded(tool);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/** 面板展开前的视口位置:收起时还原,避免面板撑高后收起导致视口错位。 */
|
|
552
|
+
const panelViewportBeforeExpand = new WeakMap<
|
|
553
|
+
object,
|
|
554
|
+
{ scrollTop: number; followingEnd: boolean }
|
|
555
|
+
>();
|
|
556
|
+
|
|
557
|
+
function capturePanelViewport(card: any): void {
|
|
558
|
+
if (!card || panelViewportBeforeExpand.has(card)) return;
|
|
559
|
+
const view = getToolMouseTui()?.getPrimaryScrollView?.();
|
|
560
|
+
if (!view || typeof view.scrollTop !== "number" || typeof view.scrollTo !== "function") return;
|
|
561
|
+
panelViewportBeforeExpand.set(card, {
|
|
562
|
+
scrollTop: view.scrollTop,
|
|
563
|
+
followingEnd: view.isFollowingEnd !== false,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function restorePanelViewport(card: any): void {
|
|
568
|
+
const snapshot = panelViewportBeforeExpand.get(card);
|
|
569
|
+
if (!snapshot) return;
|
|
570
|
+
panelViewportBeforeExpand.delete(card);
|
|
571
|
+
const view = getToolMouseTui()?.getPrimaryScrollView?.();
|
|
572
|
+
if (!view) return;
|
|
573
|
+
// 展开前就在底部:交回官方 follow;否则回到展开前的滚动偏移。
|
|
574
|
+
if (snapshot.followingEnd && typeof view.scrollToEnd === "function") view.scrollToEnd();
|
|
575
|
+
else if (typeof view.scrollTo === "function") view.scrollTo(snapshot.scrollTop);
|
|
576
|
+
}
|
|
577
|
+
|
|
534
578
|
function compactEditWriteLine(
|
|
535
579
|
component: any,
|
|
536
580
|
width: number,
|
|
@@ -968,6 +1012,8 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
968
1012
|
let activeRound: CompactRound | undefined;
|
|
969
1013
|
let roundByComponent = new WeakMap<object, CompactRound>();
|
|
970
1014
|
const expandedRoundToolIds = new Set<string>();
|
|
1015
|
+
/** 用户显式点开的 round 内工具;强制折叠不压回这些 id。 */
|
|
1016
|
+
const explicitRoundToolIds = new Set<string>();
|
|
971
1017
|
let uiRef: { requestRender?: (force?: boolean) => void } | undefined;
|
|
972
1018
|
let roundTickTimer: ReturnType<typeof setInterval> | undefined;
|
|
973
1019
|
|
|
@@ -1037,6 +1083,23 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1037
1083
|
return result;
|
|
1038
1084
|
};
|
|
1039
1085
|
|
|
1086
|
+
const markRoundToolUserExpanded = (tool: any): void => {
|
|
1087
|
+
const id = String(tool?.toolCallId ?? "");
|
|
1088
|
+
if (!id || !expandedRoundToolIds.has(id)) return;
|
|
1089
|
+
for (const other of trackedToolComponents) {
|
|
1090
|
+
const otherId = String(other?.toolCallId ?? "");
|
|
1091
|
+
if (other === tool || !expandedRoundToolIds.has(otherId) || other.expanded !== true) continue;
|
|
1092
|
+
explicitRoundToolIds.delete(otherId);
|
|
1093
|
+
if (typeof other.setExpanded === "function") other.setExpanded(false);
|
|
1094
|
+
else {
|
|
1095
|
+
other.expanded = false;
|
|
1096
|
+
other.updateDisplay?.();
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
explicitRoundToolIds.add(id);
|
|
1100
|
+
};
|
|
1101
|
+
roundToolExpansion = { markUserExpanded: markRoundToolUserExpanded };
|
|
1102
|
+
|
|
1040
1103
|
const roundMessages = (round: CompactRound): any[] => [
|
|
1041
1104
|
...round.messages.values(),
|
|
1042
1105
|
...round.detachedMessages,
|
|
@@ -1061,17 +1124,24 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1061
1124
|
const summary = getSummary();
|
|
1062
1125
|
for (const id of round.suppressedToolIds) expandedRoundToolIds.delete(id);
|
|
1063
1126
|
round.suppressedToolIds.clear();
|
|
1127
|
+
// 回合收起(或本回合不在展开态)时忘掉用户的单开选择。
|
|
1128
|
+
if (round.anchor.expanded !== true) {
|
|
1129
|
+
for (const id of roundToolCallIds(round)) explicitRoundToolIds.delete(id);
|
|
1130
|
+
}
|
|
1064
1131
|
|
|
1065
1132
|
for (const [component, message] of round.messages) {
|
|
1066
1133
|
component.lastMessage = message;
|
|
1067
1134
|
ensureAssistantSetExpanded(component);
|
|
1068
1135
|
component[ASSISTANT_TOGGLE_ROUND_KEY] = (expanded: boolean) => {
|
|
1136
|
+
const wasExpanded = round.anchor.expanded === true;
|
|
1137
|
+
if (expanded && !wasExpanded) capturePanelViewport(round.anchor);
|
|
1069
1138
|
for (const member of round.messages.keys()) member.expanded = expanded;
|
|
1070
1139
|
if (!expanded) {
|
|
1071
1140
|
for (const id of round.suppressedToolIds) expandedRoundToolIds.delete(id);
|
|
1072
1141
|
round.suppressedToolIds.clear();
|
|
1073
1142
|
}
|
|
1074
1143
|
renderRound(round);
|
|
1144
|
+
if (!expanded && wasExpanded) restorePanelViewport(round.anchor);
|
|
1075
1145
|
};
|
|
1076
1146
|
}
|
|
1077
1147
|
|
|
@@ -1080,14 +1150,39 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1080
1150
|
for (const tool of trackedToolComponents) {
|
|
1081
1151
|
if (typeof tool?.toolCallId === "string") toolsById.set(tool.toolCallId, tool);
|
|
1082
1152
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1153
|
+
// 助手文本按原生渲染、不进面板(面板只留 thinking 与工具卡)。
|
|
1154
|
+
// thinking/工具卡按连续段各包一张面板;先出文本再出面板,
|
|
1155
|
+
// 否则消息内容里的 thinking([thinking, text, toolCall])会盖到文本上面。
|
|
1156
|
+
const anchorChildren: any[] = [];
|
|
1157
|
+
let cardItems: Array<{ child?: any; tool?: any }> = [];
|
|
1158
|
+
const flushPanel = () => {
|
|
1159
|
+
if (cardItems.length === 0) return;
|
|
1160
|
+
if (cardItems.every((item) => !item.tool && isSpacerComponent(item.child))) {
|
|
1161
|
+
for (const item of cardItems) anchorChildren.push(item.child);
|
|
1162
|
+
} else {
|
|
1163
|
+
// 面板上沿留 1 行卡外空行:面板底色与 userMessage 同色,
|
|
1164
|
+
// 贴在一起会看成一个块;卡内顶部再加 1 行 padding 保持上下对称。
|
|
1165
|
+
anchorChildren.push(new Spacer(1));
|
|
1166
|
+
anchorChildren.push(
|
|
1167
|
+
compactRoundCard(
|
|
1168
|
+
cardItems,
|
|
1169
|
+
(tool, innerWidth) => patch.toolOriginalRender.call(tool, innerWidth),
|
|
1170
|
+
// 整回合展开卡:卡内工具行归 tool 组件,可单击二次展开。
|
|
1171
|
+
true,
|
|
1172
|
+
),
|
|
1173
|
+
);
|
|
1088
1174
|
}
|
|
1089
|
-
|
|
1175
|
+
cardItems = [];
|
|
1090
1176
|
};
|
|
1177
|
+
const takeChild = (child: any, outside: boolean) => {
|
|
1178
|
+
if (!outside) {
|
|
1179
|
+
cardItems.push({ child });
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
flushPanel();
|
|
1183
|
+
anchorChildren.push(child);
|
|
1184
|
+
};
|
|
1185
|
+
const placedToolIds = new Set<string>();
|
|
1091
1186
|
const placeTool = (id: string, fallbackName?: string) => {
|
|
1092
1187
|
const tool = toolsById.get(id);
|
|
1093
1188
|
if (!tool || EDIT_WRITE_TOOLS.has(String(tool.toolName ?? fallbackName ?? ""))) return;
|
|
@@ -1095,6 +1190,7 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1095
1190
|
placedToolIds.add(id);
|
|
1096
1191
|
cardItems.push({ tool });
|
|
1097
1192
|
};
|
|
1193
|
+
type LocalEntry = { outside: boolean; child?: any; toolId?: string; toolName?: string };
|
|
1098
1194
|
for (const [component, message] of round.messages) {
|
|
1099
1195
|
passThroughAssistant(component, message);
|
|
1100
1196
|
const kids = Array.isArray(component.contentContainer?.children)
|
|
@@ -1103,25 +1199,39 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1103
1199
|
component.contentContainer?.clear?.();
|
|
1104
1200
|
const cursor = { i: 0 };
|
|
1105
1201
|
let inThinkingRun = false;
|
|
1202
|
+
// 本消息产出先收集再发射:文本(含其前导 Spacer)统一排在面板前面。
|
|
1203
|
+
const local: LocalEntry[] = [];
|
|
1204
|
+
const takeRun = (outside: boolean) => {
|
|
1205
|
+
while (cursor.i < kids.length && isSpacerComponent(kids[cursor.i])) {
|
|
1206
|
+
local.push({ outside, child: kids[cursor.i++] });
|
|
1207
|
+
}
|
|
1208
|
+
if (cursor.i < kids.length) local.push({ outside, child: kids[cursor.i++] });
|
|
1209
|
+
};
|
|
1106
1210
|
for (const item of Array.isArray(message?.content) ? message.content : []) {
|
|
1107
1211
|
if (item?.type === "thinking") {
|
|
1108
1212
|
if (!String(item.thinking ?? "").trim()) continue;
|
|
1109
1213
|
if (!inThinkingRun) {
|
|
1110
|
-
|
|
1214
|
+
takeRun(false);
|
|
1111
1215
|
inThinkingRun = true;
|
|
1112
1216
|
}
|
|
1113
1217
|
continue;
|
|
1114
1218
|
}
|
|
1115
1219
|
inThinkingRun = false;
|
|
1116
1220
|
if (item?.type === "text" && String(item.text ?? "").trim()) {
|
|
1117
|
-
|
|
1221
|
+
takeRun(true);
|
|
1118
1222
|
continue;
|
|
1119
1223
|
}
|
|
1120
1224
|
if (item?.type === "toolCall" && typeof item.id === "string") {
|
|
1121
|
-
|
|
1225
|
+
local.push({ outside: false, toolId: item.id, toolName: item.name });
|
|
1122
1226
|
}
|
|
1123
1227
|
}
|
|
1124
|
-
while (cursor.i < kids.length)
|
|
1228
|
+
while (cursor.i < kids.length) local.push({ outside: false, child: kids[cursor.i++] });
|
|
1229
|
+
for (const entry of local) if (entry.outside) takeChild(entry.child, true);
|
|
1230
|
+
for (const entry of local) {
|
|
1231
|
+
if (entry.outside) continue;
|
|
1232
|
+
if (entry.toolId) placeTool(entry.toolId, entry.toolName);
|
|
1233
|
+
else takeChild(entry.child, false);
|
|
1234
|
+
}
|
|
1125
1235
|
}
|
|
1126
1236
|
for (const message of round.detachedMessages) {
|
|
1127
1237
|
for (const item of Array.isArray(message?.content) ? message.content : []) {
|
|
@@ -1136,12 +1246,14 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1136
1246
|
expandedRoundToolIds.add(id);
|
|
1137
1247
|
if (!placedToolIds.has(id)) placeTool(id);
|
|
1138
1248
|
}
|
|
1139
|
-
// Round
|
|
1249
|
+
// Round 展开只打开外层卡片。普通工具保持折叠,避免长输出递归撑满屏幕;
|
|
1250
|
+
// 用户单独点开的(explicitRoundToolIds)不压回去。
|
|
1140
1251
|
for (const tool of trackedToolComponents) {
|
|
1141
1252
|
if (
|
|
1142
1253
|
!ids.has(tool.toolCallId) ||
|
|
1143
1254
|
EDIT_WRITE_TOOLS.has(String(tool.toolName ?? "")) ||
|
|
1144
|
-
tool.expanded !== true
|
|
1255
|
+
tool.expanded !== true ||
|
|
1256
|
+
explicitRoundToolIds.has(String(tool.toolCallId ?? ""))
|
|
1145
1257
|
) {
|
|
1146
1258
|
continue;
|
|
1147
1259
|
}
|
|
@@ -1151,11 +1263,8 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1151
1263
|
tool.updateDisplay?.();
|
|
1152
1264
|
}
|
|
1153
1265
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
patch.toolOriginalRender.call(tool, innerWidth),
|
|
1157
|
-
),
|
|
1158
|
-
);
|
|
1266
|
+
flushPanel();
|
|
1267
|
+
for (const child of anchorChildren) round.anchor.contentContainer.addChild(child);
|
|
1159
1268
|
// 展开卡内工具会显示 error,外层仍挂 abort/length,避免只藏在折叠工具里。
|
|
1160
1269
|
appendStopStatus(round.anchor, stopStatus);
|
|
1161
1270
|
return;
|
|
@@ -1189,6 +1298,7 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1189
1298
|
activeRound = undefined;
|
|
1190
1299
|
roundByComponent = new WeakMap();
|
|
1191
1300
|
expandedRoundToolIds.clear();
|
|
1301
|
+
explicitRoundToolIds.clear();
|
|
1192
1302
|
deps.query?.setCompactSummaryActive?.(false);
|
|
1193
1303
|
stopRoundTick();
|
|
1194
1304
|
};
|
|
@@ -1322,12 +1432,11 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1322
1432
|
};
|
|
1323
1433
|
|
|
1324
1434
|
patch.toolInstalledUpdateDisplay = function (this: any) {
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
this.expanded = false;
|
|
1435
|
+
const id = String(this.toolCallId ?? "");
|
|
1436
|
+
if (patch.active && config.mode === "compact" && expandedRoundToolIds.has(id)) {
|
|
1437
|
+
// 用户点开的保留展开;其余(含全局展开被收回的)继续强制折叠。
|
|
1438
|
+
if (this.expanded !== true) explicitRoundToolIds.delete(id);
|
|
1439
|
+
else if (!explicitRoundToolIds.has(id)) this.expanded = false;
|
|
1331
1440
|
}
|
|
1332
1441
|
const result = patch.toolOriginalUpdateDisplay.call(this);
|
|
1333
1442
|
if (!patch.active) return result;
|
|
@@ -1355,6 +1464,9 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1355
1464
|
patch.dispose = () => {
|
|
1356
1465
|
if (!patch.active) return;
|
|
1357
1466
|
patch.active = false;
|
|
1467
|
+
if (roundToolExpansion?.markUserExpanded === markRoundToolUserExpanded) {
|
|
1468
|
+
roundToolExpansion = undefined;
|
|
1469
|
+
}
|
|
1358
1470
|
if (assistantPrototype.updateContent === patch.assistantInstalled) {
|
|
1359
1471
|
assistantPrototype.updateContent = patch.assistantOriginal;
|
|
1360
1472
|
}
|
|
@@ -36,7 +36,13 @@ import { showMoreHintText } from "./tool/show-more-hint.ts";
|
|
|
36
36
|
import { countWriteDiffStats } from "./tool/diff/diff-renderer.ts";
|
|
37
37
|
import { renderRichToolResult, type WriteExecutionMetadataStore } from "./tool/diff/index.ts";
|
|
38
38
|
import { getMessageDisplayTheme } from "./tool/message-display.ts";
|
|
39
|
-
import {
|
|
39
|
+
import { mcpToolTitle } from "./tool/mcp-title.ts";
|
|
40
|
+
import {
|
|
41
|
+
fitToolCallSummary,
|
|
42
|
+
humanizeToolLabel,
|
|
43
|
+
renderToolSummary,
|
|
44
|
+
toolCallSummary,
|
|
45
|
+
} from "./tool/names.ts";
|
|
40
46
|
|
|
41
47
|
// 成功勾:亮绿 truecolor(与 message-display 一致)
|
|
42
48
|
const BRIGHT_GREEN = "\x1b[38;2;80;220;100m";
|
|
@@ -102,25 +108,6 @@ export function shouldRenderRichDiff(
|
|
|
102
108
|
return mode === "on" && !isError && (toolName === "edit" || toolName === "write");
|
|
103
109
|
}
|
|
104
110
|
|
|
105
|
-
export function isMcpToolDefinition(definition: any, toolName: string): boolean {
|
|
106
|
-
const label = typeof definition?.label === "string" ? definition.label.trim() : "";
|
|
107
|
-
if (/^MCP(?::|$)/i.test(label)) return true;
|
|
108
|
-
if (toolName === "mcp" || /^mcp[_:-]|[_:-]mcp[_:-]/i.test(toolName)) return true;
|
|
109
|
-
if (label) return false;
|
|
110
|
-
const description = typeof definition?.description === "string" ? definition.description : "";
|
|
111
|
-
return /\bModel Context Protocol\b/i.test(description);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function humanizeMcpToolName(toolName: string): string {
|
|
115
|
-
const words = toolName
|
|
116
|
-
.replace(/^mcp(?:[_:-]+)+/i, "")
|
|
117
|
-
.split(/[_:-]+/)
|
|
118
|
-
.filter(Boolean);
|
|
119
|
-
return words.length
|
|
120
|
-
? words.map((word) => word[0]!.toUpperCase() + word.slice(1)).join(" ")
|
|
121
|
-
: "MCP";
|
|
122
|
-
}
|
|
123
|
-
|
|
124
111
|
/** 排除名单内且自带 renderer 的工具保留原渲染。 */
|
|
125
112
|
export function preservesOriginalRenderer(
|
|
126
113
|
extensionDefinition: any,
|
|
@@ -225,15 +212,16 @@ function createCcstyleTool(
|
|
|
225
212
|
writeExecutionMetadata: WriteExecutionMetadataStore,
|
|
226
213
|
): any {
|
|
227
214
|
const toolName = originalTool.name;
|
|
228
|
-
const label =
|
|
229
|
-
|
|
230
|
-
|
|
215
|
+
const label = originalTool.label || toolName;
|
|
216
|
+
const defaultTitle = label === toolName ? humanizeToolLabel(label) : label;
|
|
217
|
+
// MCP 工具直接用 adapter 暴露的真实工具名,不做人性化
|
|
218
|
+
const title = mcpToolTitle({ toolName, definition: originalTool }) ?? defaultTitle;
|
|
231
219
|
|
|
232
220
|
return {
|
|
233
221
|
...originalTool,
|
|
234
222
|
renderShell: "self",
|
|
235
223
|
renderCall(args: any, theme: any, context: any) {
|
|
236
|
-
if (config.mode
|
|
224
|
+
if (config.mode === "off") {
|
|
237
225
|
return renderDefault(originalTool, "renderCall", [args, theme, context], String(toolName));
|
|
238
226
|
}
|
|
239
227
|
|
|
@@ -252,7 +240,7 @@ function createCcstyleTool(
|
|
|
252
240
|
// 不再每次 tick 走 updateDisplay 把整张卡重建一遍。
|
|
253
241
|
const pendingIconStyled = () => theme.fg(toolIconColor(context), pendingIcon(toolName));
|
|
254
242
|
const summary = toolCallSummary(toolName, args, {
|
|
255
|
-
title
|
|
243
|
+
title,
|
|
256
244
|
variant: "default",
|
|
257
245
|
cwd: context?.cwd,
|
|
258
246
|
});
|
|
@@ -295,7 +283,7 @@ function createCcstyleTool(
|
|
|
295
283
|
cachedWidth = width;
|
|
296
284
|
cachedIcon = icon;
|
|
297
285
|
// 路径按最终可用宽度中间截断,避免整行二次截断隐藏文件名。
|
|
298
|
-
cachedLine = `${lead}${icon} ${
|
|
286
|
+
cachedLine = `${lead}${icon} ${renderToolSummary(summary, mainWidth, theme.fg.bind(theme))}${extraStyled}`;
|
|
299
287
|
return [truncateToWidth(cachedLine, viewportWidth, "")];
|
|
300
288
|
},
|
|
301
289
|
invalidate() {
|
|
@@ -305,7 +293,7 @@ function createCcstyleTool(
|
|
|
305
293
|
};
|
|
306
294
|
},
|
|
307
295
|
renderResult(result: any, options: any, theme: any, context: any) {
|
|
308
|
-
if (config.mode
|
|
296
|
+
if (config.mode === "off") {
|
|
309
297
|
return renderDefault(
|
|
310
298
|
originalTool,
|
|
311
299
|
"renderResult",
|
|
@@ -428,8 +416,9 @@ function shouldGloballyStyleTool(component: any, patch: GlobalToolRenderPatch):
|
|
|
428
416
|
const builtInDefinition = component.builtInToolDefinition;
|
|
429
417
|
const definition = extensionDefinition ?? builtInDefinition;
|
|
430
418
|
const toolName = String(component.toolName || definition?.name || "");
|
|
419
|
+
// compact 也复用同一套 ccstyle call/result,避免折叠态工具卡回落到 Pi 原生样式。
|
|
431
420
|
const useCcstyle =
|
|
432
|
-
patch.mode()
|
|
421
|
+
patch.mode() !== "off" &&
|
|
433
422
|
!DEDICATED_RENDERER_TOOLS.has(toolName) &&
|
|
434
423
|
!preservesOriginalRenderer(extensionDefinition, toolName, builtInDefinition);
|
|
435
424
|
component[COMPONENT_TOOL_RENDER_MODE] = useCcstyle;
|
|
@@ -597,19 +586,27 @@ function installGlobalToolRendering(
|
|
|
597
586
|
return originalRender.call(this, width);
|
|
598
587
|
}
|
|
599
588
|
const cache = patch.paintCache;
|
|
600
|
-
|
|
589
|
+
// A running partial tool owns a time-varying spinner. Light animation ticks
|
|
590
|
+
// request a repaint without invalidating the component, so settled-content
|
|
591
|
+
// caching must not intercept those paints.
|
|
592
|
+
const cacheable = !(this.executionStarted && this.isPartial);
|
|
593
|
+
const hit = cacheable ? cache?.get(this) : undefined;
|
|
601
594
|
if (hit && toolPaintMatches(hit, this, width)) return hit.lines;
|
|
602
595
|
const lines = originalRender.call(this, width);
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
596
|
+
if (cacheable) {
|
|
597
|
+
cache?.set(this, {
|
|
598
|
+
width,
|
|
599
|
+
expanded: Boolean(this.expanded),
|
|
600
|
+
isPartial: Boolean(this.isPartial),
|
|
601
|
+
result: this.result,
|
|
602
|
+
args: this.args,
|
|
603
|
+
callHover: isToolCallHovered(this.toolCallId),
|
|
604
|
+
ioHover: ioHoverOf(this),
|
|
605
|
+
lines,
|
|
606
|
+
});
|
|
607
|
+
} else {
|
|
608
|
+
cache?.delete(this);
|
|
609
|
+
}
|
|
613
610
|
return lines;
|
|
614
611
|
};
|
|
615
612
|
patch.invalidatePaint = function (this: any): void {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { ToolExecutionComponent, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { CompactThinkingController } from "../feature/compact-thinking.ts";
|
|
3
3
|
import { installToolGrouping, type ToolGroupingHooks } from "./tool/grouping.ts";
|
|
4
4
|
import {
|
|
@@ -266,11 +266,13 @@ export default function (
|
|
|
266
266
|
// ---- 对外导出:入口/测试实际消费的符号 ----
|
|
267
267
|
export { getCompactThinkingConfig } from "../config/config.ts";
|
|
268
268
|
export {
|
|
269
|
-
humanizeMcpToolName,
|
|
270
|
-
isMcpToolDefinition,
|
|
271
269
|
preservesOriginalRenderer,
|
|
272
270
|
shouldRenderRichDiff,
|
|
273
271
|
} from "./default-mode.ts";
|
|
272
|
+
export {
|
|
273
|
+
isMcpToolDefinition,
|
|
274
|
+
mcpToolTitle,
|
|
275
|
+
} from "./tool/mcp-title.ts";
|
|
274
276
|
export {
|
|
275
277
|
ExpandedToolIoView,
|
|
276
278
|
ExpandedToolResultText,
|