pi-cc-extensions 0.9.12 → 1.0.0
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 +2 -1
- package/README.md +2 -1
- package/extensions/config/config.ts +5 -0
- package/extensions/config/panel.ts +21 -6
- package/extensions/renderer/compact-mode.ts +108 -25
- package/extensions/renderer/default-mode.ts +152 -24
- package/extensions/renderer/index.ts +30 -1
- package/extensions/renderer/mouse/interaction.ts +22 -4
- package/extensions/renderer/mouse/sticky-prompt.ts +398 -0
- package/extensions/renderer/tool/ask-user-question.ts +173 -0
- package/extensions/renderer/tool/codemode.ts +4 -8
- package/extensions/renderer/tool/diff/diff-edit-render.ts +6 -6
- package/extensions/renderer/tool/diff/diff-presentation.ts +41 -0
- package/extensions/renderer/tool/diff/diff-renderer.ts +5 -1
- package/extensions/renderer/tool/diff/diff-write-render.ts +35 -9
- package/extensions/renderer/tool/grouping.ts +115 -24
- package/extensions/renderer/tool/names.ts +1 -1
- package/extensions/renderer/tool/result.ts +106 -15
- package/extensions/utils/patch-keys.ts +5 -0
- package/package.json +1 -4
- package/themes/cc-dark.json +1 -1
- package/themes/cc-light.json +1 -1
package/README.en.md
CHANGED
|
@@ -63,6 +63,7 @@ Renderer snapshots for `on` / `compact` modes: [default](./docs/tool-render-exam
|
|
|
63
63
|
"expandedCardBackground": "userMessageBg", // expanded card background slot: userMessageBg (same as user prompts) / toolPendingBg (neutral) etc.
|
|
64
64
|
"toolLabelClip": 0, // tool summary path/command clip length; 0 = fit width
|
|
65
65
|
"showStartupHeader": true, // startup header (logo + tips) toggle
|
|
66
|
+
"stickyUserMessage": true, // fullscreen: pin a user message's first line once it scrolls off the top edge, until the next one takes over
|
|
66
67
|
|
|
67
68
|
// diff
|
|
68
69
|
"diffViewMode": "auto", // layout: auto / split / unified
|
|
@@ -92,7 +93,7 @@ Renderer snapshots for `on` / `compact` modes: [default](./docs/tool-render-exam
|
|
|
92
93
|
```
|
|
93
94
|
|
|
94
95
|
> [!TIP]
|
|
95
|
-
> **Fullscreen**: click `click to show more` to expand tool cards, thinking, Skill, and compact summaries. An expanded diff always shows every line; when expanded Input/Output exceeds the line cap, the footer `… +N more lines · click to show more` opens a full preview. Click to collapse
|
|
96
|
+
> **Fullscreen**: click `click to show more` to expand tool cards, thinking, Skill, and compact summaries. An expanded diff always shows every line; when expanded Input/Output exceeds the line cap, the footer `… +N more lines · click to show more` opens a full preview. Click to collapse.
|
|
96
97
|
|
|
97
98
|
> [!NOTE]
|
|
98
99
|
> **Mermaid rendering**: set `markdown.mermaid` to `final` via `~/.pi/agent/settings.json` or the Mermaid diagrams option in `/settings`. Default `streaming` redraws per frame; `final` renders once at completion.
|
package/README.md
CHANGED
|
@@ -63,6 +63,7 @@ pi install git:github.com/minuque/pi-cc-extensions
|
|
|
63
63
|
"expandedCardBackground": "userMessageBg", // 展开卡背景槽位;userMessageBg 与用户消息同色,toolPendingBg 为中性灰
|
|
64
64
|
"toolLabelClip": 0, // 工具摘要 path/command 折叠字符数;0 = 按可用宽度
|
|
65
65
|
"showStartupHeader": true, // 启动头(logo + tips)开关
|
|
66
|
+
"stickyUserMessage": true, // fullscreen:userMessage 正文首行滚出视口上沿后钉住,直到下一条指令接手
|
|
66
67
|
|
|
67
68
|
// diff
|
|
68
69
|
"diffViewMode": "auto", // 布局:auto / split / unified
|
|
@@ -92,7 +93,7 @@ pi install git:github.com/minuque/pi-cc-extensions
|
|
|
92
93
|
```
|
|
93
94
|
|
|
94
95
|
> [!TIP]
|
|
95
|
-
> **全屏模式**:单击 `click to show more` 展开工具卡、思考、Skill 和 compact 摘要;工具卡展开态 diff 全量显示,Input/Output 超行时末行 `… +N more lines · click to show more`
|
|
96
|
+
> **全屏模式**:单击 `click to show more` 展开工具卡、思考、Skill 和 compact 摘要;工具卡展开态 diff 全量显示,Input/Output 超行时末行 `… +N more lines · click to show more` 打开全量预览,单击收起。
|
|
96
97
|
|
|
97
98
|
> [!NOTE]
|
|
98
99
|
> **Mermaid 渲染**:建议把 `markdown.mermaid` 设为 `final`(`~/.pi/agent/settings.json` 或 `/settings` 面板的 Mermaid diagrams 选项)。默认 `streaming` 逐帧重绘,`final` 渲染最终版更稳定。
|
|
@@ -65,6 +65,8 @@ export type Config = {
|
|
|
65
65
|
animationIntervalMs: number;
|
|
66
66
|
dimThinkingText: boolean;
|
|
67
67
|
showStartupHeader: boolean;
|
|
68
|
+
/** fullscreen:userMessage 正文首行滚出视口上沿后钉住,直到下一条指令接手。 */
|
|
69
|
+
stickyUserMessage: boolean;
|
|
68
70
|
enableSessionReference: boolean;
|
|
69
71
|
enableSubagentAutocomplete: boolean;
|
|
70
72
|
enableContextCommand: boolean;
|
|
@@ -153,6 +155,7 @@ export const DEFAULT_CONFIG: Config = {
|
|
|
153
155
|
animationIntervalMs: 90,
|
|
154
156
|
dimThinkingText: false,
|
|
155
157
|
showStartupHeader: true,
|
|
158
|
+
stickyUserMessage: true,
|
|
156
159
|
enableSessionReference: true,
|
|
157
160
|
enableSubagentAutocomplete: true,
|
|
158
161
|
enableContextCommand: true,
|
|
@@ -282,6 +285,7 @@ export function normalizeConfig(input: unknown): Config {
|
|
|
282
285
|
),
|
|
283
286
|
dimThinkingText: source.dimThinkingText === true,
|
|
284
287
|
showStartupHeader: source.showStartupHeader !== false,
|
|
288
|
+
stickyUserMessage: source.stickyUserMessage !== false,
|
|
285
289
|
enableSessionReference: source.enableSessionReference !== false,
|
|
286
290
|
enableSubagentAutocomplete: source.enableSubagentAutocomplete !== false,
|
|
287
291
|
enableContextCommand: source.enableContextCommand !== false,
|
|
@@ -344,6 +348,7 @@ export function formatConfigStatus(source: Config = config): string {
|
|
|
344
348
|
`thinkingAnimation=${source.animationIntervalMs}ms`,
|
|
345
349
|
`thinkingDim=${source.dimThinkingText ? "on" : "off"}`,
|
|
346
350
|
`startupHeader=${source.showStartupHeader ? "on" : "off"}`,
|
|
351
|
+
`stickyUserMessage=${source.stickyUserMessage ? "on" : "off"}`,
|
|
347
352
|
`sessionRef=${source.enableSessionReference ? "on" : "off"}`,
|
|
348
353
|
`subagentAuto=${source.enableSubagentAutocomplete ? "on" : "off"}`,
|
|
349
354
|
`context=${source.enableContextCommand ? "on" : "off"}`,
|
|
@@ -68,7 +68,7 @@ export type CcstylePanelHooks = {
|
|
|
68
68
|
|
|
69
69
|
function modeSettingDescription(mode: CompactStyleMode): string {
|
|
70
70
|
if (mode === "compact") {
|
|
71
|
-
return "
|
|
71
|
+
return "One summary line per assistant round; edit/write reuse the same Diff preview settings as on.";
|
|
72
72
|
}
|
|
73
73
|
if (mode === "off") {
|
|
74
74
|
return "Pi native tool rendering. Diff options below still apply independently.";
|
|
@@ -422,8 +422,8 @@ export async function showCcstylePanel(
|
|
|
422
422
|
id: "mode",
|
|
423
423
|
label: "Mode",
|
|
424
424
|
description: modeSettingDescription(config.mode),
|
|
425
|
-
currentValue: config.mode
|
|
426
|
-
values: ["on", "compact
|
|
425
|
+
currentValue: config.mode,
|
|
426
|
+
values: ["on", "compact", "off"],
|
|
427
427
|
};
|
|
428
428
|
// Tracks whether a nested submenu is open so Tab switches sections
|
|
429
429
|
// only at the top level (mirrors Zentui settings: Tab = switch sections).
|
|
@@ -595,6 +595,15 @@ export async function showCcstylePanel(
|
|
|
595
595
|
currentValue: config.showStartupHeader ? "on" : "off",
|
|
596
596
|
values: ["on", "off"],
|
|
597
597
|
};
|
|
598
|
+
const stickyUserMessageSetting = {
|
|
599
|
+
id: "stickyUserMessage",
|
|
600
|
+
label: "Sticky user message",
|
|
601
|
+
description: config.stickyUserMessage
|
|
602
|
+
? "Fullscreen: pin a user message's first line once it scrolls off the top edge, until the next one takes over."
|
|
603
|
+
: "User messages scroll normally.",
|
|
604
|
+
currentValue: config.stickyUserMessage ? "on" : "off",
|
|
605
|
+
values: ["on", "off"],
|
|
606
|
+
};
|
|
598
607
|
|
|
599
608
|
// 额外功能开关:注册于扩展加载期,切换后需重启(/reload)生效。
|
|
600
609
|
const sessionReferenceToggle = featureToggleSetting(
|
|
@@ -819,9 +828,7 @@ export async function showCcstylePanel(
|
|
|
819
828
|
toolLabelClipSetting.currentValue = String(config.toolLabelClip);
|
|
820
829
|
break;
|
|
821
830
|
case "mode": {
|
|
822
|
-
|
|
823
|
-
const mode: CompactStyleMode =
|
|
824
|
-
value === "compact (Experimental)" ? "compact" : (value as CompactStyleMode);
|
|
831
|
+
const mode = value as CompactStyleMode;
|
|
825
832
|
modeSetting.description = modeSettingDescription(mode);
|
|
826
833
|
hooks.applyStyleMode(mode, ctx, toolGrouping);
|
|
827
834
|
return;
|
|
@@ -930,6 +937,13 @@ export async function showCcstylePanel(
|
|
|
930
937
|
? "Thinking text uses the theme's dim color."
|
|
931
938
|
: "Keep the default thinking text color.";
|
|
932
939
|
break;
|
|
940
|
+
case "stickyUserMessage": {
|
|
941
|
+
updateConfig({ stickyUserMessage: value === "on" });
|
|
942
|
+
stickyUserMessageSetting.description = config.stickyUserMessage
|
|
943
|
+
? "Fullscreen: pin a user message's first line once it scrolls off the top edge, until the next one takes over."
|
|
944
|
+
: "User messages scroll normally.";
|
|
945
|
+
break;
|
|
946
|
+
}
|
|
933
947
|
case "showStartupHeader": {
|
|
934
948
|
const enabled = value === "on";
|
|
935
949
|
updateConfig({ showStartupHeader: enabled });
|
|
@@ -975,6 +989,7 @@ export async function showCcstylePanel(
|
|
|
975
989
|
expandedMaxSetting,
|
|
976
990
|
expandedCardBgSetting,
|
|
977
991
|
toolLabelClipSetting,
|
|
992
|
+
stickyUserMessageSetting,
|
|
978
993
|
startupHeaderSetting,
|
|
979
994
|
],
|
|
980
995
|
},
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
* renderer)。edit/write 复用 mode=on 的 rich diff
|
|
7
7
|
* 与 `editDiffCollapsedLines` / `writeDiffCollapsedLines` / `expandedPreviewMaxLines`。
|
|
8
8
|
* Agent/Task 族:调用只进摘要计数,tool 卡始终折叠(避免 pending→完成高度闪动)。
|
|
9
|
+
* edit/write 与 ask_user_question 留在外层:不进摘要计数,折叠态也单独成行。
|
|
10
|
+
* 用户消息是回合的硬边界:steer 消息插进回合中间、或上一回合没正文就收尾时,回合
|
|
11
|
+
* 就地收口、后续消息另起一回合,区间里的用户消息不会被当成外来行挪走(摘要行、
|
|
12
|
+
* 展开面板因此留在自己回合的位置上)。
|
|
9
13
|
* 底部 Agents/Tasks 面板由 pi-subagents/pi-tasks 独立 widget 负责,不经 tool 卡外置。
|
|
10
14
|
*
|
|
11
15
|
* live 围观态(回合进行中 / 收尾静默期):正文跟折叠态同形;摘要行与单一槽位卡
|
|
@@ -26,11 +30,14 @@
|
|
|
26
30
|
*/
|
|
27
31
|
import {
|
|
28
32
|
AssistantMessageComponent,
|
|
33
|
+
SkillInvocationMessageComponent,
|
|
29
34
|
ToolExecutionComponent,
|
|
35
|
+
UserMessageComponent,
|
|
30
36
|
type Theme,
|
|
31
37
|
} from "@earendil-works/pi-coding-agent";
|
|
32
38
|
import { truncateToWidth, visibleWidth, Box, Spacer } from "@earendil-works/pi-tui";
|
|
33
39
|
import { config, getToolDisplayConfig } from "../config/config.ts";
|
|
40
|
+
import { ASK_USER_QUESTION_TOOL_NAME } from "./tool/ask-user-question.ts";
|
|
34
41
|
import { toolLoadingIcon } from "../utils/tool-loading-icon.ts";
|
|
35
42
|
import { sanitizeToolResultText } from "../utils/tool-result-sanitize.ts";
|
|
36
43
|
import { refreshTranscriptComponent } from "./transcript-refresh.ts";
|
|
@@ -46,7 +53,12 @@ import { displayConfigCacheKey } from "./tool/diff/diff-component.ts";
|
|
|
46
53
|
import type { WriteExecutionMetadataStore } from "./tool/diff/write-execution.ts";
|
|
47
54
|
import { isToolCallHovered } from "./mouse/hover.ts";
|
|
48
55
|
import { getToolMouseTui } from "./mouse/scroll.ts";
|
|
49
|
-
import {
|
|
56
|
+
import {
|
|
57
|
+
insetComponent,
|
|
58
|
+
isLiveToolCall,
|
|
59
|
+
renderExpandedToolResult,
|
|
60
|
+
scheduleAnimation,
|
|
61
|
+
} from "./tool/result.ts";
|
|
50
62
|
import { paddedBackgroundRow } from "./tool/grouping.ts";
|
|
51
63
|
import { formatDisplayPath } from "./tool/names.ts";
|
|
52
64
|
import {
|
|
@@ -82,6 +94,13 @@ export type CompactThinkingQuery = {
|
|
|
82
94
|
|
|
83
95
|
const EDIT_WRITE_TOOLS = new Set(["edit", "write"]);
|
|
84
96
|
|
|
97
|
+
/**
|
|
98
|
+
* 留在外层的工具:不进摘要计数,折叠态也单独成行。
|
|
99
|
+
* edit/write 走 compact 自己的 rich diff;ask_user_question 走 default 模式的专用渲染
|
|
100
|
+
*(标题 (+N) 加折叠问答树)。
|
|
101
|
+
*/
|
|
102
|
+
const OUTER_TOOLS = new Set([...EDIT_WRITE_TOOLS, ASK_USER_QUESTION_TOOL_NAME]);
|
|
103
|
+
|
|
85
104
|
/**
|
|
86
105
|
* 回合收尾后延迟收拢的静默期(ms)。正文 token 往往先于 toolCall 到达,
|
|
87
106
|
* 立刻收拢会得到「先收再开」两次转场;静默期内被新回合接替则一并收拢。
|
|
@@ -206,7 +225,7 @@ function buildMessagesSummary(
|
|
|
206
225
|
for (const item of content) {
|
|
207
226
|
if (item?.type !== "toolCall") continue;
|
|
208
227
|
const rawName = typeof item.name === "string" ? item.name : "tool";
|
|
209
|
-
if (
|
|
228
|
+
if (OUTER_TOOLS.has(rawName)) continue;
|
|
210
229
|
const name = sanitizeToolResultText(rawName);
|
|
211
230
|
if (rawName.split(".").pop() === "read") {
|
|
212
231
|
const args = item.arguments ?? item.args ?? {};
|
|
@@ -675,7 +694,7 @@ function compactEditWriteLine(
|
|
|
675
694
|
component: any,
|
|
676
695
|
width: number,
|
|
677
696
|
writeMetadata?: WriteExecutionMetadataStore,
|
|
678
|
-
options: { hint?: boolean; flushLeft?: boolean } = {},
|
|
697
|
+
options: { hint?: boolean; flushLeft?: boolean; showStats?: boolean } = {},
|
|
679
698
|
): string[] {
|
|
680
699
|
const theme = themeOf();
|
|
681
700
|
const name = String(component.toolName ?? "tool");
|
|
@@ -692,7 +711,8 @@ function compactEditWriteLine(
|
|
|
692
711
|
const iconColor = isError ? "error" : isPending ? "accent" : "success";
|
|
693
712
|
let statsText = "";
|
|
694
713
|
let statsStyled = "";
|
|
695
|
-
|
|
714
|
+
// 统计只显示一处:正文自带 +A -D 时标题不再重复(showStats=false)。
|
|
715
|
+
if (!isError && !isPending && options.showStats !== false) {
|
|
696
716
|
const stats =
|
|
697
717
|
name === "edit"
|
|
698
718
|
? countEditDiffStats(component.result?.details)
|
|
@@ -730,6 +750,12 @@ function compactEditWriteLine(
|
|
|
730
750
|
return ["", truncateToWidth(line, width, "")];
|
|
731
751
|
}
|
|
732
752
|
|
|
753
|
+
/** 正文自带 +A -D 的声明:diff 组件自报,折叠行数/宽度这些细节由它自己判断。 */
|
|
754
|
+
function richDiffBodyShowsStats(component: unknown): boolean {
|
|
755
|
+
const probe = (component as { bodyShowsStats?: () => boolean } | undefined)?.bodyShowsStats;
|
|
756
|
+
return typeof probe === "function" && probe.call(component) === true;
|
|
757
|
+
}
|
|
758
|
+
|
|
733
759
|
/** expanded 写进 rich diff 闭包,折叠/展开分槽缓存,避免每帧 parseDiff。 */
|
|
734
760
|
const compactRichDiffCache = new WeakMap<
|
|
735
761
|
object,
|
|
@@ -853,7 +879,12 @@ function paintCompactEditWrite(
|
|
|
853
879
|
component.resultRendererComponent = candidate;
|
|
854
880
|
}
|
|
855
881
|
|
|
856
|
-
|
|
882
|
+
// 统计只显示一处:正文里已经有 +A -D(diff 头行/0 行摘要行)时标题不再重复。
|
|
883
|
+
const showStats = !(hasRich && richDiffBodyShowsStats(candidate));
|
|
884
|
+
const title = compactEditWriteLine(component, width, writeMetadata, {
|
|
885
|
+
hint: !hasRich,
|
|
886
|
+
showStats,
|
|
887
|
+
});
|
|
857
888
|
if (!hasRich && !expanded) {
|
|
858
889
|
return title;
|
|
859
890
|
}
|
|
@@ -893,6 +924,7 @@ function paintCompactEditWrite(
|
|
|
893
924
|
return compactEditWriteLine(component, innerWidth, writeMetadata, {
|
|
894
925
|
hint: false,
|
|
895
926
|
flushLeft: true,
|
|
927
|
+
showStats,
|
|
896
928
|
}).slice(1);
|
|
897
929
|
},
|
|
898
930
|
invalidate() {},
|
|
@@ -1006,7 +1038,7 @@ function compactStopStatusLine(status: string, pad = 0): any {
|
|
|
1006
1038
|
|
|
1007
1039
|
/**
|
|
1008
1040
|
* 槽位卡内的展开入口:卡里是只读围观,展开走摘要行自己的 hint,卡内不再重复提示。
|
|
1009
|
-
* 提示有两处来源——我们的 showMoreHintText(`…
|
|
1041
|
+
* 提示有两处来源——我们的 showMoreHintText(`… N more lines · click to show more`)与
|
|
1010
1042
|
* pi 原生工具卡(`… (N earlier lines, ctrl+o to expand)`),所以按渲染结果收尾裁剪。
|
|
1011
1043
|
* 只去掉动作词,`(N more lines)` 这类计数保留。
|
|
1012
1044
|
*/
|
|
@@ -1367,6 +1399,54 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1367
1399
|
return found;
|
|
1368
1400
|
};
|
|
1369
1401
|
|
|
1402
|
+
/** 回合成员判定:本回合的 assistant 组件,或归属本回合的工具(含分组容器)。 */
|
|
1403
|
+
const roundMemberMatcher = (round: CompactRound): ((value: any) => boolean) => {
|
|
1404
|
+
const ids = roundToolCallIds(round);
|
|
1405
|
+
const matchesTool = (value: any): boolean =>
|
|
1406
|
+
typeof value?.toolCallId === "string" && ids.has(value.toolCallId);
|
|
1407
|
+
return (value: any): boolean =>
|
|
1408
|
+
round.messages.has(value) ||
|
|
1409
|
+
matchesTool(value) ||
|
|
1410
|
+
(Array.isArray(value?.children) && value.children.some(matchesTool));
|
|
1411
|
+
};
|
|
1412
|
+
|
|
1413
|
+
/** 用户消息(含 skill 调用块)是回合的硬边界。 */
|
|
1414
|
+
const isUserMessageComponent = (value: any): boolean =>
|
|
1415
|
+
value instanceof UserMessageComponent || value instanceof SkillInvocationMessageComponent;
|
|
1416
|
+
|
|
1417
|
+
/**
|
|
1418
|
+
* 新组件与回合已有成员之间是否隔着一条用户消息。隔着就说明这是下一回合的内容:
|
|
1419
|
+
* steer 消息插进了回合中间,或上一回合没有正文就收尾、用户又问了新问题。
|
|
1420
|
+
* 组件还没挂进容器时判断不了,按不隔开处理(会话重放的 refresh 会再求值一次)。
|
|
1421
|
+
*/
|
|
1422
|
+
const crossesUserMessage = (round: CompactRound, component: any): boolean => {
|
|
1423
|
+
const container = tailContainerOf(round.anchor);
|
|
1424
|
+
const children = container?.children;
|
|
1425
|
+
if (!Array.isArray(children)) return false;
|
|
1426
|
+
const index = children.indexOf(component);
|
|
1427
|
+
if (index < 0) return false;
|
|
1428
|
+
const isMember = roundMemberMatcher(round);
|
|
1429
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
1430
|
+
if (isMember(children[i])) return false;
|
|
1431
|
+
if (isUserMessageComponent(children[i])) return true;
|
|
1432
|
+
}
|
|
1433
|
+
return false;
|
|
1434
|
+
};
|
|
1435
|
+
|
|
1436
|
+
/**
|
|
1437
|
+
* 可以并入的活动回合。被用户消息隔开时返回 undefined,调用方收掉旧回合另起一个,
|
|
1438
|
+
* 保证回合不跨用户消息——否则区间里的用户消息会被当成外来行挪到回合末尾之后。
|
|
1439
|
+
*/
|
|
1440
|
+
const joinableActiveRound = (component: any): CompactRound | undefined =>
|
|
1441
|
+
activeRound && !crossesUserMessage(activeRound, component) ? activeRound : undefined;
|
|
1442
|
+
|
|
1443
|
+
/** 取并入目标;旧回合被用户消息隔开时先收尾(静默期一转场,新回合立即接手)。 */
|
|
1444
|
+
const joinOrCreateRound = (component: any, message: any): CompactRound => {
|
|
1445
|
+
const joinable = joinableActiveRound(component);
|
|
1446
|
+
if (!joinable && activeRound) finishRound(activeRound);
|
|
1447
|
+
return joinable ?? createRound(component, message);
|
|
1448
|
+
};
|
|
1449
|
+
|
|
1370
1450
|
/** 尾行实际所在容器;缓存的 container 过期时按树找回来。 */
|
|
1371
1451
|
const tailHostContainer = (tail: MountedRoundTail): any =>
|
|
1372
1452
|
containerOfHost(tail.host) ??
|
|
@@ -1449,13 +1529,7 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1449
1529
|
const children = container.children as any[];
|
|
1450
1530
|
const current = children.indexOf(tail.host);
|
|
1451
1531
|
if (current >= 0) children.splice(current, 1);
|
|
1452
|
-
const
|
|
1453
|
-
const matchesTool = (value: any): boolean =>
|
|
1454
|
-
typeof value?.toolCallId === "string" && ids.has(value.toolCallId);
|
|
1455
|
-
const isMember = (value: any): boolean =>
|
|
1456
|
-
round.messages.has(value) ||
|
|
1457
|
-
matchesTool(value) ||
|
|
1458
|
-
(Array.isArray(value?.children) && value.children.some(matchesTool));
|
|
1532
|
+
const isMember = roundMemberMatcher(round);
|
|
1459
1533
|
let first = -1;
|
|
1460
1534
|
let last = -1;
|
|
1461
1535
|
for (let i = 0; i < children.length; i++) {
|
|
@@ -1466,7 +1540,9 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1466
1540
|
const lastMember = last >= 0 ? children[last] : undefined;
|
|
1467
1541
|
const intruders: any[] = [];
|
|
1468
1542
|
for (let i = last - 1; i > first; i--) {
|
|
1469
|
-
|
|
1543
|
+
// 用户消息永远留在原地:挪走会把别人的消息挪到用户消息下面去。
|
|
1544
|
+
if (isMember(children[i]) || isUserMessageComponent(children[i])) continue;
|
|
1545
|
+
intruders.unshift(children.splice(i, 1)[0]);
|
|
1470
1546
|
}
|
|
1471
1547
|
if (lastMember) last = children.indexOf(lastMember);
|
|
1472
1548
|
const insertAt = last < 0 ? children.length : last + 1;
|
|
@@ -1648,7 +1724,7 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1648
1724
|
if (item?.type !== "toolCall" || typeof item.id !== "string") continue;
|
|
1649
1725
|
const tool = toolsById.get(item.id);
|
|
1650
1726
|
// edit/write 保留自己的 compact 行,不进槽位。
|
|
1651
|
-
if (
|
|
1727
|
+
if (OUTER_TOOLS.has(String(tool?.toolName ?? item.name ?? ""))) continue;
|
|
1652
1728
|
round.liveSlotToolIds.add(item.id);
|
|
1653
1729
|
liveSlotToolIds.add(item.id);
|
|
1654
1730
|
if (!tool) continue;
|
|
@@ -1705,8 +1781,7 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1705
1781
|
for (const id of ids) explicitRoundToolIds.delete(id);
|
|
1706
1782
|
collapseTools(
|
|
1707
1783
|
(tool) =>
|
|
1708
|
-
ids.has(String(tool.toolCallId ?? "")) &&
|
|
1709
|
-
!EDIT_WRITE_TOOLS.has(String(tool.toolName ?? "")),
|
|
1784
|
+
ids.has(String(tool.toolCallId ?? "")) && !OUTER_TOOLS.has(String(tool.toolName ?? "")),
|
|
1710
1785
|
);
|
|
1711
1786
|
}
|
|
1712
1787
|
for (const id of round.liveSlotToolIds) liveSlotToolIds.delete(id);
|
|
@@ -1776,7 +1851,7 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1776
1851
|
const placedToolIds = new Set<string>();
|
|
1777
1852
|
const placeTool = (id: string, fallbackName?: string) => {
|
|
1778
1853
|
const tool = toolsById.get(id);
|
|
1779
|
-
if (!tool ||
|
|
1854
|
+
if (!tool || OUTER_TOOLS.has(String(tool.toolName ?? fallbackName ?? ""))) return;
|
|
1780
1855
|
if (placedToolIds.has(id)) return;
|
|
1781
1856
|
placedToolIds.add(id);
|
|
1782
1857
|
cardItems.push({ tool });
|
|
@@ -1842,7 +1917,7 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1842
1917
|
collapseTools(
|
|
1843
1918
|
(tool) =>
|
|
1844
1919
|
ids.has(String(tool.toolCallId ?? "")) &&
|
|
1845
|
-
!
|
|
1920
|
+
!OUTER_TOOLS.has(String(tool.toolName ?? "")) &&
|
|
1846
1921
|
!explicitRoundToolIds.has(String(tool.toolCallId ?? "")),
|
|
1847
1922
|
);
|
|
1848
1923
|
flushPanel();
|
|
@@ -1978,7 +2053,8 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1978
2053
|
round = createRound(this, message);
|
|
1979
2054
|
roundByComponent.set(this, round);
|
|
1980
2055
|
} else if (!round) {
|
|
1981
|
-
|
|
2056
|
+
// 中间隔着用户消息就另起一回合,不并进上一个回合。
|
|
2057
|
+
round = joinOrCreateRound(this, message);
|
|
1982
2058
|
roundByComponent.set(this, round);
|
|
1983
2059
|
}
|
|
1984
2060
|
round.messages.set(this, message);
|
|
@@ -2014,7 +2090,7 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
2014
2090
|
if (hasThinking) {
|
|
2015
2091
|
let round = roundByComponent.get(this);
|
|
2016
2092
|
if (!round) {
|
|
2017
|
-
round =
|
|
2093
|
+
round = joinOrCreateRound(this, message);
|
|
2018
2094
|
roundByComponent.set(this, round);
|
|
2019
2095
|
}
|
|
2020
2096
|
round.messages.set(this, message);
|
|
@@ -2040,10 +2116,17 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
2040
2116
|
return patch.toolOriginalRender.call(this, width);
|
|
2041
2117
|
}
|
|
2042
2118
|
const name = String(this.toolName ?? "");
|
|
2043
|
-
if (
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2119
|
+
if (OUTER_TOOLS.has(name)) {
|
|
2120
|
+
// edit/write 走 compact 自己的 rich diff;ask_user_question 走 default 模式的专用渲染。
|
|
2121
|
+
// 两者都留在外层,不进摘要、不进展开面板。
|
|
2122
|
+
if (EDIT_WRITE_TOOLS.has(name)) {
|
|
2123
|
+
const pending = !this.result || this.isPartial === true;
|
|
2124
|
+
// 等待窗口(声明流式中或等待派发)也要转,否则这一行冻在第一帧。
|
|
2125
|
+
if (pending && (this.executionStarted || isLiveToolCall(this.toolCallId)))
|
|
2126
|
+
scheduleAnimation(this);
|
|
2127
|
+
return compactEditWriteLines(this, width, deps.writeMetadata);
|
|
2128
|
+
}
|
|
2129
|
+
return patch.toolOriginalRender.call(this, width);
|
|
2047
2130
|
}
|
|
2048
2131
|
// Agent/Task 等同普通工具:折叠不外置(live 面板走独立 widget)。
|
|
2049
2132
|
if (expandedRoundToolIds.has(String(this.toolCallId ?? ""))) return [];
|