pi-cc-extensions 0.8.53 → 0.8.55
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 +12 -17
- package/README.md +18 -17
- package/assets/readme/hero.en.svg +101 -0
- package/extensions/config/config.ts +25 -17
- package/extensions/config/panel.ts +100 -4
- package/extensions/{utils/agent-summary.ts → feature/agent-summary/core.ts} +39 -47
- package/extensions/feature/{agent-summary.ts → agent-summary/index.ts} +2 -6
- package/extensions/feature/compact-thinking.ts +8 -3
- package/extensions/feature/{session-reference.ts → reference/index.ts} +1 -1
- package/extensions/feature/{agent-autocomplete.ts → reference/subagent.ts} +0 -1
- package/extensions/feature/{pi-startup-header.ts → shell/startup-header.ts} +2 -42
- package/extensions/feature/{working-message.ts → shell/working-message.ts} +5 -11
- package/extensions/index.ts +28 -38
- package/extensions/renderer/compact-mode.ts +394 -101
- package/extensions/renderer/default-mode.ts +747 -0
- package/extensions/renderer/index.ts +58 -775
- package/extensions/renderer/{mouse-hover.ts → mouse/hover.ts} +8 -6
- package/extensions/renderer/{mouse-interaction.ts → mouse/interaction.ts} +37 -36
- package/extensions/renderer/{mouse-layout.ts → mouse/layout.ts} +5 -21
- package/extensions/renderer/{mouse-packets.ts → mouse/packets.ts} +1 -1
- package/extensions/renderer/{mouse-scroll.ts → mouse/scroll.ts} +49 -36
- package/extensions/renderer/{tool-diff → tool/diff}/diff-renderer.ts +10 -45
- package/extensions/renderer/{tool-diff → tool/diff}/line-width-safety.ts +1 -1
- package/extensions/renderer/tool/diff/write-display-utils.ts +12 -0
- package/extensions/renderer/{tool-grouping.ts → tool/grouping.ts} +20 -16
- package/extensions/renderer/{tool-result.ts → tool/result.ts} +35 -27
- package/extensions/utils/ansi-color.ts +44 -0
- package/extensions/utils/format.ts +21 -0
- package/extensions/utils/fullscreen-detect.ts +0 -27
- package/package.json +4 -2
- package/extensions/renderer/tool-diff/write-display-utils.ts +0 -29
- /package/extensions/feature/{session-reference-core.ts → reference/session.ts} +0 -0
- /package/extensions/feature/{pi-aliases.ts → shell/aliases.ts} +0 -0
- /package/extensions/{feature → renderer}/markdown-enhance.ts +0 -0
- /package/extensions/renderer/{tool-diff → tool/diff}/ATTRIBUTION.md +0 -0
- /package/extensions/renderer/{tool-diff → tool/diff}/ansi-utils.ts +0 -0
- /package/extensions/renderer/{tool-diff → tool/diff}/diff-presentation.ts +0 -0
- /package/extensions/renderer/{tool-diff → tool/diff}/index.ts +0 -0
- /package/extensions/renderer/{tool-diff → tool/diff}/render-utils.ts +0 -0
- /package/extensions/renderer/{tool-diff → tool/diff}/shiki-highlight.ts +0 -0
- /package/extensions/renderer/{tool-diff → tool/diff}/types.ts +0 -0
- /package/extensions/renderer/{tool-diff → tool/diff}/write-execution.ts +0 -0
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { ToolExecutionComponent } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import { Text, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
4
2
|
import type { CompactThinkingController } from "../feature/compact-thinking.ts";
|
|
5
|
-
import { installToolGrouping, type ToolGroupingHooks } from "./tool
|
|
3
|
+
import { installToolGrouping, type ToolGroupingHooks } from "./tool/grouping.ts";
|
|
6
4
|
import {
|
|
7
5
|
installCompactMode,
|
|
8
6
|
refreshCompactModeComponents,
|
|
9
7
|
type CompactModeHooks,
|
|
8
|
+
type CompactThinkingQuery,
|
|
10
9
|
} from "./compact-mode.ts";
|
|
10
|
+
import {
|
|
11
|
+
installDefaultMode,
|
|
12
|
+
installToolExpandedBackground,
|
|
13
|
+
type DefaultModeHooks,
|
|
14
|
+
} from "./default-mode.ts";
|
|
11
15
|
import { isLazyProxyTui } from "../utils/fullscreen-detect.ts";
|
|
12
16
|
import { showCcstylePanel } from "../config/panel.ts";
|
|
13
17
|
import {
|
|
14
18
|
config,
|
|
15
|
-
DEFAULT_CONFIG,
|
|
16
19
|
formatConfigStatus,
|
|
17
|
-
getCompactThinkingConfig,
|
|
18
|
-
getToolDisplayConfig,
|
|
19
20
|
normalizeConfig,
|
|
20
21
|
saveConfig,
|
|
21
22
|
setConfig,
|
|
@@ -24,73 +25,45 @@ import {
|
|
|
24
25
|
} from "../config/config.ts";
|
|
25
26
|
import {
|
|
26
27
|
installToolMouseInteraction,
|
|
27
|
-
isToolCallHovered,
|
|
28
28
|
resetToolHoverState,
|
|
29
29
|
scheduleSessionRender,
|
|
30
|
-
setHoveredToolGroup,
|
|
31
|
-
setHoveredToolIo,
|
|
32
30
|
teardownToolMouseInteraction,
|
|
33
|
-
toolMouseTui,
|
|
34
31
|
TOOL_MOUSE_DISABLE,
|
|
35
|
-
} from "./mouse
|
|
36
|
-
import {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
headTruncateToWidth,
|
|
41
|
-
insetComponent,
|
|
42
|
-
isToolExpanded,
|
|
43
|
-
middleTruncateToWidth,
|
|
44
|
-
oneLine,
|
|
45
|
-
outputLineCount,
|
|
46
|
-
pendingIcon,
|
|
47
|
-
renderCollapsedToolResultToWidth,
|
|
48
|
-
renderExpandedToolResult,
|
|
49
|
-
resolveToolVisualState,
|
|
50
|
-
scheduleAnimation,
|
|
51
|
-
settledIcon,
|
|
52
|
-
setToolVisualState,
|
|
53
|
-
textFromResult,
|
|
54
|
-
toolIconColor,
|
|
55
|
-
toolViewportWidth,
|
|
56
|
-
} from "./tool-result.ts";
|
|
57
|
-
import { showMoreHintText } from "./show-more-hint.ts";
|
|
58
|
-
import type { CompactThinkingQuery } from "./compact-mode.ts";
|
|
59
|
-
import {
|
|
60
|
-
installWriteOverride,
|
|
61
|
-
renderRichToolResult,
|
|
62
|
-
WriteExecutionMetadataStore,
|
|
63
|
-
} from "./tool-diff/index.ts";
|
|
32
|
+
} from "./mouse/interaction.ts";
|
|
33
|
+
import { getToolMouseTui } from "./mouse/scroll.ts";
|
|
34
|
+
import { setHoveredToolGroup, setHoveredToolIo } from "./mouse/hover.ts";
|
|
35
|
+
import { clearAllAnimations } from "./tool/result.ts";
|
|
36
|
+
import { installWriteOverride, WriteExecutionMetadataStore } from "./tool/diff/index.ts";
|
|
64
37
|
import {
|
|
65
|
-
getMessageDisplayTheme,
|
|
66
38
|
installMessageDisplayRendering,
|
|
67
39
|
refreshMessageDisplays,
|
|
68
40
|
setMessageDisplayTheme,
|
|
69
41
|
} from "./message-display.ts";
|
|
70
42
|
|
|
71
43
|
/**
|
|
72
|
-
* Claude Code Style for pi —
|
|
44
|
+
* Claude Code Style for pi — 装配入口。
|
|
73
45
|
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
46
|
+
* mode=on → default-mode(工具卡样式 + 展开背景)
|
|
47
|
+
* mode=compact → compact-mode(消息折叠摘要)
|
|
48
|
+
* 共用 → tool-grouping / message-display / mouse / write override
|
|
76
49
|
*/
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
50
|
+
|
|
51
|
+
const GLOBAL_COMPACTION_RENDER_PATCH = Symbol.for("pi.ccstyle.compaction-render-patch");
|
|
52
|
+
|
|
53
|
+
let compactModeHooks: CompactModeHooks | undefined;
|
|
80
54
|
|
|
81
55
|
function refreshCurrentTranscript(ctx?: any, toolGrouping?: ToolGroupingHooks): void {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
56
|
+
const tui = getToolMouseTui();
|
|
57
|
+
toolGrouping?.refresh(tui);
|
|
58
|
+
refreshMessageDisplays(tui);
|
|
59
|
+
refreshCompactModeComponents(tui);
|
|
85
60
|
compactModeHooks?.refresh();
|
|
86
|
-
|
|
61
|
+
tui?.requestRender?.(true);
|
|
87
62
|
ctx?.ui?.requestRender?.(true);
|
|
88
63
|
}
|
|
89
64
|
|
|
90
|
-
let compactModeHooks: CompactModeHooks | undefined;
|
|
91
|
-
|
|
92
65
|
function syncCompactMode(ctx: any): void {
|
|
93
|
-
refreshCompactModeComponents(
|
|
66
|
+
refreshCompactModeComponents(getToolMouseTui());
|
|
94
67
|
compactModeHooks?.sync(ctx);
|
|
95
68
|
}
|
|
96
69
|
|
|
@@ -98,708 +71,30 @@ function applyStyleMode(mode: CompactStyleMode, ctx: any, toolGrouping?: ToolGro
|
|
|
98
71
|
config.mode = mode;
|
|
99
72
|
saveConfig();
|
|
100
73
|
if (mode === "off") {
|
|
101
|
-
// Native rendering
|
|
102
|
-
// reporting so the terminal restores its default scrollback wheel scrolling.
|
|
74
|
+
// Native rendering:清 hover/click,关闭鼠标上报以恢复终端默认滚轮。
|
|
103
75
|
resetToolHoverState();
|
|
104
76
|
setHoveredToolGroup(null);
|
|
105
77
|
setHoveredToolIo(null, null);
|
|
106
78
|
// 惰性 Proxy(regular/fullscreen)不持有 reporting:regular 保终端回滚,
|
|
107
79
|
// fullscreen 归官方所有,均不能在此关闭。
|
|
108
|
-
|
|
109
|
-
|
|
80
|
+
const tui = getToolMouseTui();
|
|
81
|
+
if (tui && !isLazyProxyTui(tui)) {
|
|
82
|
+
tui.terminal?.write?.(TOOL_MOUSE_DISABLE);
|
|
110
83
|
}
|
|
111
84
|
} else if (mode === "compact") {
|
|
112
85
|
// 切入 compact:先收集当前 transcript,再同步全局展开状态和补丁所有权。
|
|
113
86
|
syncCompactMode(ctx);
|
|
114
87
|
}
|
|
88
|
+
// 立即重塑一次;再延迟一帧(与 session_start 同款),等面板/custom
|
|
89
|
+
// 卸下、主 transcript 重新挂载后再扫树,避免必须 /reload。
|
|
115
90
|
refreshCurrentTranscript(ctx, toolGrouping);
|
|
91
|
+
scheduleSessionRender(() => {
|
|
92
|
+
if (mode === "compact") syncCompactMode(ctx);
|
|
93
|
+
refreshCurrentTranscript(ctx, toolGrouping);
|
|
94
|
+
});
|
|
116
95
|
ctx.ui.notify(`Claude Code style: ${mode}`, "info");
|
|
117
96
|
}
|
|
118
97
|
|
|
119
|
-
function renderDefault(tool: any, slot: "renderCall" | "renderResult", args: any[], fallback = "") {
|
|
120
|
-
try {
|
|
121
|
-
if (typeof tool?.[slot] === "function") return tool[slot](...args);
|
|
122
|
-
} catch {
|
|
123
|
-
// Fall through to raw fallback.
|
|
124
|
-
}
|
|
125
|
-
return new Text(fallback, 0, 0);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function singleLine(text: string) {
|
|
129
|
-
return {
|
|
130
|
-
render: (width: number) => [truncateToWidth(text, width, "…")],
|
|
131
|
-
invalidate() {},
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function humanizeToolLabel(label: string): string {
|
|
136
|
-
return label
|
|
137
|
-
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
138
|
-
.replace(/[_-]+/g, " ")
|
|
139
|
-
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function singleToolCallSummary(
|
|
143
|
-
toolName: string,
|
|
144
|
-
label: string,
|
|
145
|
-
args: any,
|
|
146
|
-
): { main: string; detail: string } {
|
|
147
|
-
const title = label === toolName ? humanizeToolLabel(label) : label;
|
|
148
|
-
if (!args || typeof args !== "object") return { main: title, detail: "" };
|
|
149
|
-
const name = toolName.toLowerCase();
|
|
150
|
-
const value = (fallback: string, ...keys: string[]) => {
|
|
151
|
-
const found = keys.map((key) => args[key]).find((item) => typeof item === "string" && item);
|
|
152
|
-
// 与多 tool 摘要对齐:从头截断,上限 96 字符(tool-grouping oneLine 默认值)。
|
|
153
|
-
return `${title} ${oneLine(found || fallback, 96)}`;
|
|
154
|
-
};
|
|
155
|
-
if (AGENT_FAMILY_TOOL_NAMES.has(toolName) && args.agent_id) {
|
|
156
|
-
return { main: `${title} ${oneLine(args.agent_id, 96)}`, detail: "" };
|
|
157
|
-
}
|
|
158
|
-
// Agents still uses the ccstyle wrapper; Agent keeps its dedicated renderer.
|
|
159
|
-
if (name === "agents") {
|
|
160
|
-
return {
|
|
161
|
-
main: value("launch agents", "description", "prompt"),
|
|
162
|
-
detail: "",
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
if (name === "skill") return { main: value("run skill", "name"), detail: "" };
|
|
166
|
-
if (name === "enterplanmode" || name === "enter_plan_mode") {
|
|
167
|
-
return { main: `${title} enable read-only planning`, detail: "" };
|
|
168
|
-
}
|
|
169
|
-
if (name === "exitplanmode" || name === "exit_plan_mode") {
|
|
170
|
-
return { main: `${title} present plan`, detail: "" };
|
|
171
|
-
}
|
|
172
|
-
if (name === "taskcreate") return { main: value("create task", "subject"), detail: "" };
|
|
173
|
-
if (name === "tasklist") return { main: `${title} task list`, detail: "" };
|
|
174
|
-
if (name === "taskget" || name === "taskupdate") {
|
|
175
|
-
return { main: value("task", "taskId", "task_id"), detail: "" };
|
|
176
|
-
}
|
|
177
|
-
if (name === "taskoutput" || name === "taskstop") {
|
|
178
|
-
return { main: value("background task", "task_id", "taskId"), detail: "" };
|
|
179
|
-
}
|
|
180
|
-
if (name === "taskexecute") {
|
|
181
|
-
const ids = Array.isArray(args.task_ids)
|
|
182
|
-
? args.task_ids
|
|
183
|
-
: Array.isArray(args.taskIds)
|
|
184
|
-
? args.taskIds
|
|
185
|
-
: [];
|
|
186
|
-
const summary = ids.length
|
|
187
|
-
? `${ids[0]}${ids.length > 1 ? ` (+${ids.length - 1} tasks)` : ""}`
|
|
188
|
-
: "start tasks";
|
|
189
|
-
return { main: `${title} ${summary}`, detail: "" };
|
|
190
|
-
}
|
|
191
|
-
if (toolName === "read") {
|
|
192
|
-
const details = [
|
|
193
|
-
args.offset !== undefined ? `offset=${args.offset}` : "",
|
|
194
|
-
args.limit !== undefined ? `limit=${args.limit}` : "",
|
|
195
|
-
].filter(Boolean);
|
|
196
|
-
return {
|
|
197
|
-
main: `${title}${args.path ? ` ${oneLine(args.path, 96)}` : ""}`,
|
|
198
|
-
detail: details.length ? ` (${details.join(", ")})` : "",
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
const preferred =
|
|
202
|
-
args.path ??
|
|
203
|
-
args.file_path ??
|
|
204
|
-
args.command ??
|
|
205
|
-
args.query ??
|
|
206
|
-
args.question ??
|
|
207
|
-
args.pattern ??
|
|
208
|
-
args.url ??
|
|
209
|
-
args.name ??
|
|
210
|
-
args.tool_use_id ??
|
|
211
|
-
args.toolCallId ??
|
|
212
|
-
args.id ??
|
|
213
|
-
args.message;
|
|
214
|
-
return {
|
|
215
|
-
main:
|
|
216
|
-
preferred !== undefined && preferred !== null && typeof preferred !== "object"
|
|
217
|
-
? `${title} ${oneLine(preferred, 96)}`
|
|
218
|
-
: title,
|
|
219
|
-
detail: "",
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export function shouldRenderRichDiff(
|
|
224
|
-
mode: CompactStyleMode,
|
|
225
|
-
toolName: string,
|
|
226
|
-
isError: boolean,
|
|
227
|
-
): boolean {
|
|
228
|
-
return mode === "on" && !isError && (toolName === "edit" || toolName === "write");
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
type ParsedTask = { id: string; status: string; subject: string };
|
|
232
|
-
|
|
233
|
-
function parseTaskList(text: string): ParsedTask[] {
|
|
234
|
-
return text
|
|
235
|
-
.split("\n")
|
|
236
|
-
.map((line) => line.match(/^#(\d+) \[([^\]]+)] (.+)$/))
|
|
237
|
-
.filter((match): match is RegExpMatchArray => Boolean(match))
|
|
238
|
-
.map((match) => ({ id: match[1]!, status: match[2]!, subject: match[3]! }));
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function taskListSummary(tasks: ParsedTask[]): string {
|
|
242
|
-
const counts = { pending: 0, in_progress: 0, completed: 0 };
|
|
243
|
-
for (const task of tasks) {
|
|
244
|
-
if (task.status in counts) counts[task.status as keyof typeof counts]++;
|
|
245
|
-
}
|
|
246
|
-
return [
|
|
247
|
-
`${tasks.length} tasks`,
|
|
248
|
-
counts.in_progress ? `${counts.in_progress} in progress` : "",
|
|
249
|
-
counts.pending ? `${counts.pending} pending` : "",
|
|
250
|
-
counts.completed ? `${counts.completed} completed` : "",
|
|
251
|
-
]
|
|
252
|
-
.filter(Boolean)
|
|
253
|
-
.join(" • ");
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function renderExpandedTaskResult(
|
|
257
|
-
toolName: string,
|
|
258
|
-
text: string,
|
|
259
|
-
theme: any,
|
|
260
|
-
isError: boolean,
|
|
261
|
-
): any | undefined {
|
|
262
|
-
if (isError) return undefined;
|
|
263
|
-
if (toolName === "TaskList") {
|
|
264
|
-
const tasks = parseTaskList(text);
|
|
265
|
-
if (!tasks.length) return undefined;
|
|
266
|
-
const limit = Math.max(1, config.expandedPreviewMaxLines);
|
|
267
|
-
const rows = tasks.slice(0, limit).map((task) => {
|
|
268
|
-
const color =
|
|
269
|
-
task.status === "completed"
|
|
270
|
-
? "success"
|
|
271
|
-
: task.status === "in_progress"
|
|
272
|
-
? "warning"
|
|
273
|
-
: "muted";
|
|
274
|
-
return ` ${theme.fg("accent", `#${task.id}`)} ${theme.fg(color, task.status)} ${theme.fg("dim", task.subject)}`;
|
|
275
|
-
});
|
|
276
|
-
if (tasks.length > rows.length)
|
|
277
|
-
rows.push(theme.fg("muted", ` … ${tasks.length - rows.length} more tasks`));
|
|
278
|
-
return new Text(` ↳ ${theme.fg("muted", taskListSummary(tasks))}\n${rows.join("\n")}`, 0, 0);
|
|
279
|
-
}
|
|
280
|
-
const line = text.trim();
|
|
281
|
-
if (!line || line.includes("\n")) return undefined;
|
|
282
|
-
let formatted: string | undefined;
|
|
283
|
-
let match: RegExpMatchArray | null;
|
|
284
|
-
if (
|
|
285
|
-
toolName === "TaskCreate" &&
|
|
286
|
-
(match = line.match(/^Task #(\d+) created successfully: (.+)$/))
|
|
287
|
-
) {
|
|
288
|
-
formatted = `${theme.fg("success", "Created task")} ${theme.fg("accent", `#${match[1]}`)} ${theme.fg("muted", match[2])}`;
|
|
289
|
-
} else if (toolName === "TaskUpdate" && (match = line.match(/^Updated task #(\d+) (.+)$/))) {
|
|
290
|
-
formatted = `${theme.fg("success", "Updated task")} ${theme.fg("accent", `#${match[1]}`)} ${theme.fg("muted", match[2])}`;
|
|
291
|
-
} else if (toolName === "TaskExecute") {
|
|
292
|
-
formatted = `${theme.fg("success", "Started")} ${theme.fg("muted", line)}`;
|
|
293
|
-
} else if (toolName === "TaskStop") {
|
|
294
|
-
formatted = `${theme.fg("success", "Stopped")} ${theme.fg("muted", line)}`;
|
|
295
|
-
}
|
|
296
|
-
return formatted ? new Text(` ↳ ${formatted}`, 0, 0) : undefined;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/** Wrap an arbitrary tool definition with ccstyle call/result rendering. */
|
|
300
|
-
function createCcstyleTool(
|
|
301
|
-
originalTool: any,
|
|
302
|
-
writeExecutionMetadata: WriteExecutionMetadataStore,
|
|
303
|
-
): any {
|
|
304
|
-
const toolName = originalTool.name;
|
|
305
|
-
const label = isMcpToolDefinition(originalTool, toolName)
|
|
306
|
-
? humanizeMcpToolName(toolName)
|
|
307
|
-
: originalTool.label || toolName;
|
|
308
|
-
|
|
309
|
-
return {
|
|
310
|
-
...originalTool,
|
|
311
|
-
renderShell: "self",
|
|
312
|
-
renderCall(args: any, theme: any, context: any) {
|
|
313
|
-
if (config.mode !== "on") {
|
|
314
|
-
return renderDefault(originalTool, "renderCall", [args, theme, context], String(toolName));
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
const visualState = resolveToolVisualState(context);
|
|
318
|
-
const isPending =
|
|
319
|
-
visualState === "pending" ||
|
|
320
|
-
(!visualState && (context?.isPartial || context?.executionStarted));
|
|
321
|
-
if (isPending) scheduleAnimation(context);
|
|
322
|
-
const rawIcon = isPending ? pendingIcon(toolName) : settledIcon(toolName, visualState);
|
|
323
|
-
const icon =
|
|
324
|
-
visualState === "success"
|
|
325
|
-
? `${BRIGHT_GREEN}${rawIcon}${ANSI_FG_RESET}`
|
|
326
|
-
: theme.fg(toolIconColor(context), rawIcon);
|
|
327
|
-
const summary = singleToolCallSummary(toolName, label, args);
|
|
328
|
-
let cachedWidth: number | undefined;
|
|
329
|
-
let cachedLine: string | undefined;
|
|
330
|
-
return {
|
|
331
|
-
render(width: number) {
|
|
332
|
-
if (cachedLine !== undefined && cachedWidth === width) return [cachedLine];
|
|
333
|
-
const viewportWidth = toolViewportWidth(width);
|
|
334
|
-
const callWidth = Math.max(0, viewportWidth - visibleWidth(icon) - 2);
|
|
335
|
-
const mainWidth = Math.max(0, callWidth - visibleWidth(summary.detail));
|
|
336
|
-
cachedWidth = width;
|
|
337
|
-
// 纯文本先截断再着色(省略号不带 ANSI,避免 reset 破坏卡片背景);从头截断,与多 tool 一致
|
|
338
|
-
cachedLine = ` ${icon} ${theme.fg("toolTitle", headTruncateToWidth(summary.main, mainWidth))}${theme.fg("dim", summary.detail)}`;
|
|
339
|
-
return [truncateToWidth(cachedLine, viewportWidth, "")];
|
|
340
|
-
},
|
|
341
|
-
invalidate() {},
|
|
342
|
-
};
|
|
343
|
-
},
|
|
344
|
-
renderResult(result: any, options: any, theme: any, context: any) {
|
|
345
|
-
if (config.mode !== "on") {
|
|
346
|
-
return renderDefault(
|
|
347
|
-
originalTool,
|
|
348
|
-
"renderResult",
|
|
349
|
-
[result, options, theme, context],
|
|
350
|
-
textFromResult(result),
|
|
351
|
-
);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
if (options?.isPartial) {
|
|
355
|
-
return new Text(theme.fg("muted", " ↳ Pending…"), 0, 0);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
const isError = options?.isError || context?.isError;
|
|
359
|
-
setToolVisualState(context, isError ? "error" : "success");
|
|
360
|
-
const expanded = isToolExpanded(options, context);
|
|
361
|
-
const toolCallId = context?.toolCallId;
|
|
362
|
-
if (shouldRenderRichDiff(config.mode, toolName, Boolean(isError))) {
|
|
363
|
-
// Pass getter so Diff indicator / wrap / limits update on the next paint
|
|
364
|
-
// without recreating the tool result component.
|
|
365
|
-
const richResult = renderRichToolResult(
|
|
366
|
-
toolName,
|
|
367
|
-
result,
|
|
368
|
-
{
|
|
369
|
-
...options,
|
|
370
|
-
expanded,
|
|
371
|
-
// 全局共享状态避免 /reload 后旧 result renderer 闭包失联。
|
|
372
|
-
isHovered: () => isToolCallHovered(toolCallId),
|
|
373
|
-
},
|
|
374
|
-
theme,
|
|
375
|
-
context,
|
|
376
|
-
writeExecutionMetadata,
|
|
377
|
-
getToolDisplayConfig,
|
|
378
|
-
);
|
|
379
|
-
if (richResult) return insetComponent(richResult);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
const text = textFromResult(result, expanded);
|
|
383
|
-
const args = context?.args;
|
|
384
|
-
if (expanded) {
|
|
385
|
-
const taskResult = renderExpandedTaskResult(toolName, text, theme, Boolean(isError));
|
|
386
|
-
if (taskResult) return taskResult;
|
|
387
|
-
}
|
|
388
|
-
const tasks = !isError && toolName === "TaskList" ? parseTaskList(text) : [];
|
|
389
|
-
const outputLines = outputLineCount(result) || countLines(text);
|
|
390
|
-
const lineWord = outputLines === 1 ? "line" : "lines";
|
|
391
|
-
const action = toolName === "read" ? "loaded" : "returned";
|
|
392
|
-
const rendered = tasks.length
|
|
393
|
-
? taskListSummary(tasks)
|
|
394
|
-
: isError
|
|
395
|
-
? text
|
|
396
|
-
? oneLine(text)
|
|
397
|
-
: "Failed"
|
|
398
|
-
: outputLines
|
|
399
|
-
? `${outputLines} ${lineWord} ${action}`
|
|
400
|
-
: "Done";
|
|
401
|
-
const expandable = !expanded && (tasks.length > 0 || hasExpandableDetail(text, args));
|
|
402
|
-
const hintText = showMoreHintText();
|
|
403
|
-
const hintPrefix = expandable ? theme.fg("dim", " • ") : "";
|
|
404
|
-
const hint = expandable ? hintPrefix + theme.fg("dim", hintText) : "";
|
|
405
|
-
const hoveredHint = expandable ? hintPrefix + theme.fg("text", hintText) : "";
|
|
406
|
-
if (expanded) {
|
|
407
|
-
return renderExpandedToolResult(
|
|
408
|
-
text || "",
|
|
409
|
-
theme,
|
|
410
|
-
Boolean(isError),
|
|
411
|
-
context?.lastComponent,
|
|
412
|
-
args,
|
|
413
|
-
context,
|
|
414
|
-
);
|
|
415
|
-
}
|
|
416
|
-
if (context?.state) context.state.ccstyleIoView = undefined;
|
|
417
|
-
let cachedWidth: number | undefined;
|
|
418
|
-
let cachedLine: string | undefined;
|
|
419
|
-
let cachedHoveredLine: string | undefined;
|
|
420
|
-
return {
|
|
421
|
-
render(width: number) {
|
|
422
|
-
if (cachedLine === undefined || cachedWidth !== width) {
|
|
423
|
-
cachedWidth = width;
|
|
424
|
-
cachedLine = theme.fg(
|
|
425
|
-
isError ? "error" : "muted",
|
|
426
|
-
renderCollapsedToolResultToWidth(rendered, hint, width),
|
|
427
|
-
);
|
|
428
|
-
cachedHoveredLine = theme.fg(
|
|
429
|
-
isError ? "error" : "muted",
|
|
430
|
-
renderCollapsedToolResultToWidth(rendered, hoveredHint, width),
|
|
431
|
-
);
|
|
432
|
-
}
|
|
433
|
-
return [isToolCallHovered(toolCallId) ? cachedHoveredLine! : cachedLine];
|
|
434
|
-
},
|
|
435
|
-
invalidate() {},
|
|
436
|
-
};
|
|
437
|
-
},
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* Apart from the write override used to capture pre-write content, renderers are
|
|
443
|
-
* applied through ToolExecutionComponent. Patch its lookup once so tools use the
|
|
444
|
-
* same ccstyle fallback shell by default. Tools named in excludeRenderers keep
|
|
445
|
-
* their original renderer.
|
|
446
|
-
*/
|
|
447
|
-
const GLOBAL_TOOL_RENDER_PATCH = Symbol.for("pi.ccstyle.global-tool-render-patch");
|
|
448
|
-
const COMPONENT_TOOL_RENDER_MODE = Symbol.for("pi.ccstyle.component-tool-render-mode");
|
|
449
|
-
const COMPONENT_TOOL_SELF_SHELL_MODE = Symbol.for("pi.ccstyle.component-tool-self-shell-mode");
|
|
450
|
-
const AGENT_FAMILY_TOOL_NAMES = new Set([
|
|
451
|
-
"Agent",
|
|
452
|
-
"Agents",
|
|
453
|
-
"get_subagent_result",
|
|
454
|
-
"steer_subagent",
|
|
455
|
-
]);
|
|
456
|
-
// pi-subagents 等扩展为 Agent 提供专用渲染器(displayName/运行统计),ccstyle 必须保留,不能 wrap。
|
|
457
|
-
const DEDICATED_RENDERER_TOOLS = new Set(["Agent"]);
|
|
458
|
-
|
|
459
|
-
type ToolRenderMethods = {
|
|
460
|
-
hasRendererDefinition: (...args: any[]) => boolean;
|
|
461
|
-
getRenderShell: (...args: any[]) => "default" | "self";
|
|
462
|
-
getCallRenderer: (...args: any[]) => any;
|
|
463
|
-
getResultRenderer: (...args: any[]) => any;
|
|
464
|
-
};
|
|
465
|
-
|
|
466
|
-
type GlobalToolRenderPatch = {
|
|
467
|
-
version: 2;
|
|
468
|
-
prototype: any;
|
|
469
|
-
owner: object;
|
|
470
|
-
active: boolean;
|
|
471
|
-
enabled: () => boolean;
|
|
472
|
-
mode: () => CompactStyleMode;
|
|
473
|
-
wrap: (tool: any) => any;
|
|
474
|
-
byDefinition: WeakMap<object, any>;
|
|
475
|
-
byName: Map<string, any>;
|
|
476
|
-
downstream: ToolRenderMethods;
|
|
477
|
-
installed: ToolRenderMethods;
|
|
478
|
-
// Keep the legacy aliases so an older extension build can read this Symbol.
|
|
479
|
-
originalHasRendererDefinition: ToolRenderMethods["hasRendererDefinition"];
|
|
480
|
-
originalGetRenderShell: ToolRenderMethods["getRenderShell"];
|
|
481
|
-
originalGetCallRenderer: ToolRenderMethods["getCallRenderer"];
|
|
482
|
-
originalGetResultRenderer: ToolRenderMethods["getResultRenderer"];
|
|
483
|
-
};
|
|
484
|
-
|
|
485
|
-
export function isMcpToolDefinition(definition: any, toolName: string): boolean {
|
|
486
|
-
const label = typeof definition?.label === "string" ? definition.label.trim() : "";
|
|
487
|
-
if (/^MCP(?::|$)/i.test(label)) return true;
|
|
488
|
-
if (toolName === "mcp" || /^mcp[_:-]|[_:-]mcp[_:-]/i.test(toolName)) return true;
|
|
489
|
-
if (label) return false;
|
|
490
|
-
const description = typeof definition?.description === "string" ? definition.description : "";
|
|
491
|
-
return /\bModel Context Protocol\b/i.test(description);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
export function humanizeMcpToolName(toolName: string): string {
|
|
495
|
-
const words = toolName
|
|
496
|
-
.replace(/^mcp(?:[_:-]+)+/i, "")
|
|
497
|
-
.split(/[_:-]+/)
|
|
498
|
-
.filter(Boolean);
|
|
499
|
-
return words.length
|
|
500
|
-
? words.map((word) => word[0]!.toUpperCase() + word.slice(1)).join(" ")
|
|
501
|
-
: "MCP";
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
/** Return true when this tool must keep its original renderer. */
|
|
505
|
-
export function preservesOriginalRenderer(
|
|
506
|
-
extensionDefinition: any,
|
|
507
|
-
toolName: string,
|
|
508
|
-
builtInToolDefinition?: any,
|
|
509
|
-
excludeRenderers: readonly string[] = config.excludeRenderers,
|
|
510
|
-
): boolean {
|
|
511
|
-
if (!excludeRenderers.includes(toolName)) return false;
|
|
512
|
-
return [extensionDefinition, builtInToolDefinition].some(
|
|
513
|
-
(definition) =>
|
|
514
|
-
definition?.renderShell === "self" ||
|
|
515
|
-
typeof definition?.renderCall === "function" ||
|
|
516
|
-
typeof definition?.renderResult === "function",
|
|
517
|
-
);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
function syncToolShell(component: any, shell: "default" | "self"): void {
|
|
521
|
-
const target = shell === "self" ? component.selfRenderContainer : component.contentBox;
|
|
522
|
-
if (!target || !Array.isArray(component.children)) return;
|
|
523
|
-
const candidates = new Set(
|
|
524
|
-
[component.contentText, component.contentBox, component.selfRenderContainer].filter(Boolean),
|
|
525
|
-
);
|
|
526
|
-
const indexes = component.children
|
|
527
|
-
.map((child: any, index: number) => (candidates.has(child) ? index : -1))
|
|
528
|
-
.filter((index: number) => index >= 0);
|
|
529
|
-
const targetIndex = indexes[0];
|
|
530
|
-
// During construction getRenderShell() runs immediately before Pi mounts the
|
|
531
|
-
// selected shell. Do not mount it here or the constructor will add it twice.
|
|
532
|
-
if (targetIndex === undefined) return;
|
|
533
|
-
component.children[targetIndex] = target;
|
|
534
|
-
for (const index of indexes.sort((left: number, right: number) => right - left)) {
|
|
535
|
-
if (index !== targetIndex) component.children.splice(index, 1);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
function shouldGloballyStyleTool(component: any, patch: GlobalToolRenderPatch): boolean {
|
|
540
|
-
const extensionDefinition = component.toolDefinition;
|
|
541
|
-
const builtInDefinition = component.builtInToolDefinition;
|
|
542
|
-
const definition = extensionDefinition ?? builtInDefinition;
|
|
543
|
-
const toolName = String(component.toolName || definition?.name || "");
|
|
544
|
-
const useCcstyle =
|
|
545
|
-
patch.mode() === "on" &&
|
|
546
|
-
!DEDICATED_RENDERER_TOOLS.has(toolName) &&
|
|
547
|
-
!preservesOriginalRenderer(extensionDefinition, toolName, builtInDefinition);
|
|
548
|
-
component[COMPONENT_TOOL_RENDER_MODE] = useCcstyle;
|
|
549
|
-
return useCcstyle;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
function shouldUseSelfShell(component: any, _patch: GlobalToolRenderPatch): boolean {
|
|
553
|
-
component[COMPONENT_TOOL_SELF_SHELL_MODE] = false;
|
|
554
|
-
return false;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
function getGloballyStyledTool(component: any, patch: GlobalToolRenderPatch): any {
|
|
558
|
-
const definition = component.toolDefinition ?? component.builtInToolDefinition;
|
|
559
|
-
if (definition && typeof definition === "object") {
|
|
560
|
-
let wrapped = patch.byDefinition.get(definition);
|
|
561
|
-
if (!wrapped) {
|
|
562
|
-
wrapped = patch.wrap(definition);
|
|
563
|
-
patch.byDefinition.set(definition, wrapped);
|
|
564
|
-
}
|
|
565
|
-
return wrapped;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
const name = String(component.toolName || "tool");
|
|
569
|
-
let wrapped = patch.byName.get(name);
|
|
570
|
-
if (!wrapped) {
|
|
571
|
-
wrapped = patch.wrap({ name, label: name });
|
|
572
|
-
patch.byName.set(name, wrapped);
|
|
573
|
-
}
|
|
574
|
-
return wrapped;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
function prototypeToolRenderMethods(prototype: any): ToolRenderMethods {
|
|
578
|
-
return {
|
|
579
|
-
hasRendererDefinition: prototype.hasRendererDefinition,
|
|
580
|
-
getRenderShell: prototype.getRenderShell,
|
|
581
|
-
getCallRenderer: prototype.getCallRenderer,
|
|
582
|
-
getResultRenderer: prototype.getResultRenderer,
|
|
583
|
-
};
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
function isOwnershipAwarePatch(value: any): value is GlobalToolRenderPatch {
|
|
587
|
-
if (!value || value.version !== 2 || !value.installed || !value.downstream) return false;
|
|
588
|
-
return ["hasRendererDefinition", "getRenderShell", "getCallRenderer", "getResultRenderer"].every(
|
|
589
|
-
(name) =>
|
|
590
|
-
typeof value.installed[name] === "function" && typeof value.downstream[name] === "function",
|
|
591
|
-
);
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
function isLegacyInstalledWrapper(method: unknown, downstreamField: string): boolean {
|
|
595
|
-
if (typeof method !== "function") return false;
|
|
596
|
-
try {
|
|
597
|
-
const source = Function.prototype.toString.call(method);
|
|
598
|
-
return (
|
|
599
|
-
source.includes(downstreamField) &&
|
|
600
|
-
(source.includes("shouldGloballyStyleTool") ||
|
|
601
|
-
source.includes("shouldUseSelfShell") ||
|
|
602
|
-
source.includes("getGloballyStyledTool"))
|
|
603
|
-
);
|
|
604
|
-
} catch {
|
|
605
|
-
return false;
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
function downstreamForGlobalToolInstall(prototype: any, previous: any): ToolRenderMethods {
|
|
610
|
-
const current = prototypeToolRenderMethods(prototype);
|
|
611
|
-
if (!previous || previous.prototype !== prototype) return current;
|
|
612
|
-
if (isOwnershipAwarePatch(previous)) {
|
|
613
|
-
return {
|
|
614
|
-
hasRendererDefinition:
|
|
615
|
-
current.hasRendererDefinition === previous.installed.hasRendererDefinition
|
|
616
|
-
? previous.downstream.hasRendererDefinition
|
|
617
|
-
: current.hasRendererDefinition,
|
|
618
|
-
getRenderShell:
|
|
619
|
-
current.getRenderShell === previous.installed.getRenderShell
|
|
620
|
-
? previous.downstream.getRenderShell
|
|
621
|
-
: current.getRenderShell,
|
|
622
|
-
getCallRenderer:
|
|
623
|
-
current.getCallRenderer === previous.installed.getCallRenderer
|
|
624
|
-
? previous.downstream.getCallRenderer
|
|
625
|
-
: current.getCallRenderer,
|
|
626
|
-
getResultRenderer:
|
|
627
|
-
current.getResultRenderer === previous.installed.getResultRenderer
|
|
628
|
-
? previous.downstream.getResultRenderer
|
|
629
|
-
: current.getResultRenderer,
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// Pre-v2 Symbol state did not retain wrapper references. Recognize its known
|
|
634
|
-
// wrappers when possible; otherwise preserve the current method as external.
|
|
635
|
-
const legacyDownstream = (method: Function, field: string): Function => {
|
|
636
|
-
const saved = previous[field];
|
|
637
|
-
return typeof saved === "function" && isLegacyInstalledWrapper(method, field) ? saved : method;
|
|
638
|
-
};
|
|
639
|
-
return {
|
|
640
|
-
hasRendererDefinition: legacyDownstream(
|
|
641
|
-
current.hasRendererDefinition,
|
|
642
|
-
"originalHasRendererDefinition",
|
|
643
|
-
) as ToolRenderMethods["hasRendererDefinition"],
|
|
644
|
-
getRenderShell: legacyDownstream(
|
|
645
|
-
current.getRenderShell,
|
|
646
|
-
"originalGetRenderShell",
|
|
647
|
-
) as ToolRenderMethods["getRenderShell"],
|
|
648
|
-
getCallRenderer: legacyDownstream(
|
|
649
|
-
current.getCallRenderer,
|
|
650
|
-
"originalGetCallRenderer",
|
|
651
|
-
) as ToolRenderMethods["getCallRenderer"],
|
|
652
|
-
getResultRenderer: legacyDownstream(
|
|
653
|
-
current.getResultRenderer,
|
|
654
|
-
"originalGetResultRenderer",
|
|
655
|
-
) as ToolRenderMethods["getResultRenderer"],
|
|
656
|
-
};
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
function disconnectGlobalToolRenderPatch(patch: any): void {
|
|
660
|
-
if (!patch || typeof patch !== "object") return;
|
|
661
|
-
patch.active = false;
|
|
662
|
-
patch.enabled = () => false;
|
|
663
|
-
patch.mode = () => "off";
|
|
664
|
-
patch.wrap = (tool: any) => tool;
|
|
665
|
-
patch.byDefinition = new WeakMap();
|
|
666
|
-
if (patch.byName && typeof patch.byName.clear === "function") patch.byName.clear();
|
|
667
|
-
else patch.byName = new Map();
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
function installGlobalToolRendering(
|
|
671
|
-
writeExecutionMetadata: WriteExecutionMetadataStore,
|
|
672
|
-
): GlobalToolRenderPatch {
|
|
673
|
-
const prototype = (ToolExecutionComponent as any).prototype;
|
|
674
|
-
const host = globalThis as any;
|
|
675
|
-
const previous = host[GLOBAL_TOOL_RENDER_PATCH];
|
|
676
|
-
const downstream = downstreamForGlobalToolInstall(prototype, previous);
|
|
677
|
-
// Any wrapper retained by an external owner must become a callback-free
|
|
678
|
-
// pass-through before the new installation is linked above it.
|
|
679
|
-
disconnectGlobalToolRenderPatch(previous);
|
|
680
|
-
|
|
681
|
-
const patch: GlobalToolRenderPatch = {
|
|
682
|
-
version: 2,
|
|
683
|
-
prototype,
|
|
684
|
-
owner: {},
|
|
685
|
-
active: true,
|
|
686
|
-
enabled: () => config.mode === "on",
|
|
687
|
-
mode: () => config.mode,
|
|
688
|
-
wrap: (tool: any) => createCcstyleTool(tool, writeExecutionMetadata),
|
|
689
|
-
byDefinition: new WeakMap(),
|
|
690
|
-
byName: new Map(),
|
|
691
|
-
downstream,
|
|
692
|
-
installed: undefined as any,
|
|
693
|
-
originalHasRendererDefinition: downstream.hasRendererDefinition,
|
|
694
|
-
originalGetRenderShell: downstream.getRenderShell,
|
|
695
|
-
originalGetCallRenderer: downstream.getCallRenderer,
|
|
696
|
-
originalGetResultRenderer: downstream.getResultRenderer,
|
|
697
|
-
};
|
|
698
|
-
|
|
699
|
-
patch.installed = {
|
|
700
|
-
hasRendererDefinition: function (this: any, ...args: any[]) {
|
|
701
|
-
if (patch.active && shouldGloballyStyleTool(this, patch)) return true;
|
|
702
|
-
return patch.downstream.hasRendererDefinition.apply(this, args);
|
|
703
|
-
},
|
|
704
|
-
getRenderShell: function (this: any, ...args: any[]) {
|
|
705
|
-
if (!patch.active) return patch.downstream.getRenderShell.apply(this, args);
|
|
706
|
-
const useSelfShell = shouldUseSelfShell(this, patch);
|
|
707
|
-
const useCcstyle = shouldGloballyStyleTool(this, patch);
|
|
708
|
-
const shell =
|
|
709
|
-
useSelfShell || (useCcstyle && !this.expanded)
|
|
710
|
-
? "self"
|
|
711
|
-
: useCcstyle
|
|
712
|
-
? "default"
|
|
713
|
-
: patch.downstream.getRenderShell.apply(this, args);
|
|
714
|
-
syncToolShell(this, shell);
|
|
715
|
-
return shell;
|
|
716
|
-
},
|
|
717
|
-
getCallRenderer: function (this: any, ...args: any[]) {
|
|
718
|
-
if (patch.active && shouldGloballyStyleTool(this, patch)) {
|
|
719
|
-
return getGloballyStyledTool(this, patch).renderCall;
|
|
720
|
-
}
|
|
721
|
-
return patch.downstream.getCallRenderer.apply(this, args);
|
|
722
|
-
},
|
|
723
|
-
getResultRenderer: function (this: any, ...args: any[]) {
|
|
724
|
-
if (patch.active && shouldGloballyStyleTool(this, patch)) {
|
|
725
|
-
return getGloballyStyledTool(this, patch).renderResult;
|
|
726
|
-
}
|
|
727
|
-
return patch.downstream.getResultRenderer.apply(this, args);
|
|
728
|
-
},
|
|
729
|
-
};
|
|
730
|
-
|
|
731
|
-
prototype.hasRendererDefinition = patch.installed.hasRendererDefinition;
|
|
732
|
-
prototype.getRenderShell = patch.installed.getRenderShell;
|
|
733
|
-
prototype.getCallRenderer = patch.installed.getCallRenderer;
|
|
734
|
-
prototype.getResultRenderer = patch.installed.getResultRenderer;
|
|
735
|
-
host[GLOBAL_TOOL_RENDER_PATCH] = patch;
|
|
736
|
-
return patch;
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
function deactivateGlobalToolRendering(patch: GlobalToolRenderPatch): void {
|
|
740
|
-
if (!patch.active) return;
|
|
741
|
-
disconnectGlobalToolRenderPatch(patch);
|
|
742
|
-
const prototype = patch.prototype;
|
|
743
|
-
if (prototype.hasRendererDefinition === patch.installed.hasRendererDefinition) {
|
|
744
|
-
prototype.hasRendererDefinition = patch.downstream.hasRendererDefinition;
|
|
745
|
-
}
|
|
746
|
-
if (prototype.getRenderShell === patch.installed.getRenderShell) {
|
|
747
|
-
prototype.getRenderShell = patch.downstream.getRenderShell;
|
|
748
|
-
}
|
|
749
|
-
if (prototype.getCallRenderer === patch.installed.getCallRenderer) {
|
|
750
|
-
prototype.getCallRenderer = patch.downstream.getCallRenderer;
|
|
751
|
-
}
|
|
752
|
-
if (prototype.getResultRenderer === patch.installed.getResultRenderer) {
|
|
753
|
-
prototype.getResultRenderer = patch.downstream.getResultRenderer;
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
const GLOBAL_COMPACTION_RENDER_PATCH = Symbol.for("pi.ccstyle.compaction-render-patch");
|
|
758
|
-
|
|
759
|
-
const TOOL_EXPANDED_BACKGROUND_PATCH = Symbol.for("pi.ccstyle.tool-expanded-background-patch");
|
|
760
|
-
|
|
761
|
-
type ToolExpandedBackgroundPatch = {
|
|
762
|
-
active: boolean;
|
|
763
|
-
prototype: any;
|
|
764
|
-
installed: (...args: any[]) => void;
|
|
765
|
-
original: (...args: any[]) => void;
|
|
766
|
-
dispose: () => void;
|
|
767
|
-
};
|
|
768
|
-
|
|
769
|
-
/** 展开面板背景统一为 user message 背景色;折叠行保持原生状态色(由 original 重设)。 */
|
|
770
|
-
function installToolExpandedBackground(): () => void {
|
|
771
|
-
const host = globalThis as any;
|
|
772
|
-
const previous = host[TOOL_EXPANDED_BACKGROUND_PATCH] as ToolExpandedBackgroundPatch | undefined;
|
|
773
|
-
if (previous) previous.dispose();
|
|
774
|
-
const prototype = ToolExecutionComponent.prototype as unknown as { updateDisplay: () => void };
|
|
775
|
-
const original = prototype.updateDisplay;
|
|
776
|
-
const patch: ToolExpandedBackgroundPatch = {
|
|
777
|
-
active: true,
|
|
778
|
-
prototype,
|
|
779
|
-
installed: function (this: any) {
|
|
780
|
-
original.call(this);
|
|
781
|
-
if (!patch.active || config.mode !== "on" || !this.expanded) return;
|
|
782
|
-
const theme = getMessageDisplayTheme();
|
|
783
|
-
if (!theme?.bg) return;
|
|
784
|
-
const box = this.contentBox;
|
|
785
|
-
if (box?.setBgFn) box.setBgFn((text: string) => theme.bg("userMessageBg", text));
|
|
786
|
-
},
|
|
787
|
-
original,
|
|
788
|
-
dispose: () => {
|
|
789
|
-
patch.active = false;
|
|
790
|
-
if (prototype.updateDisplay === patch.installed) {
|
|
791
|
-
prototype.updateDisplay = original;
|
|
792
|
-
}
|
|
793
|
-
if (host[TOOL_EXPANDED_BACKGROUND_PATCH] === patch) {
|
|
794
|
-
delete host[TOOL_EXPANDED_BACKGROUND_PATCH];
|
|
795
|
-
}
|
|
796
|
-
},
|
|
797
|
-
};
|
|
798
|
-
prototype.updateDisplay = patch.installed;
|
|
799
|
-
host[TOOL_EXPANDED_BACKGROUND_PATCH] = patch;
|
|
800
|
-
return patch.dispose;
|
|
801
|
-
}
|
|
802
|
-
|
|
803
98
|
type LegacyCompactionRenderPatch = {
|
|
804
99
|
enabled?: () => boolean;
|
|
805
100
|
};
|
|
@@ -817,25 +112,26 @@ export default function (
|
|
|
817
112
|
configOverride?: Partial<Config>,
|
|
818
113
|
compactThinking?: CompactThinkingController,
|
|
819
114
|
) {
|
|
820
|
-
//
|
|
115
|
+
// 可选 override:集成测试不依赖用户全局配置。
|
|
821
116
|
if (configOverride) setConfig(normalizeConfig({ ...config, ...configOverride }));
|
|
822
117
|
const writeExecutionMetadata = new WriteExecutionMetadataStore();
|
|
823
118
|
const mouseOwner = {};
|
|
824
119
|
let installation:
|
|
825
120
|
| {
|
|
826
|
-
|
|
121
|
+
defaultMode: DefaultModeHooks;
|
|
827
122
|
toolGrouping: ToolGroupingHooks;
|
|
828
123
|
compactMode: CompactModeHooks;
|
|
829
124
|
disposeMessageDisplay: () => void;
|
|
830
125
|
disposeToolExpandedBackground: () => void;
|
|
831
126
|
}
|
|
832
127
|
| undefined;
|
|
128
|
+
|
|
833
129
|
const ensureTuiInstallation = (ctx: any) => {
|
|
834
130
|
if (ctx?.mode !== "tui" || !ctx?.hasUI) return undefined;
|
|
835
131
|
// 渲染层(工具样式/分组)是原型与组件级 patch,fullscreen 官方布局
|
|
836
132
|
// 同样渲染这些组件,因此两种模式都安装。
|
|
837
133
|
if (installation) return installation;
|
|
838
|
-
const
|
|
134
|
+
const defaultMode = installDefaultMode(writeExecutionMetadata);
|
|
839
135
|
const toolGrouping = installToolGrouping(() => config.mode === "on");
|
|
840
136
|
const compactMode = installCompactMode({
|
|
841
137
|
query: compactThinking as CompactThinkingQuery | undefined,
|
|
@@ -843,10 +139,11 @@ export default function (
|
|
|
843
139
|
});
|
|
844
140
|
compactModeHooks = compactMode;
|
|
845
141
|
const disposeMessageDisplay = installMessageDisplayRendering();
|
|
142
|
+
// 展开背景必须在 compact-mode 之后装,shutdown 时先于 compact 释放。
|
|
846
143
|
const disposeToolExpandedBackground = installToolExpandedBackground();
|
|
847
144
|
deactivateLegacyCompactionRendering();
|
|
848
145
|
installation = {
|
|
849
|
-
|
|
146
|
+
defaultMode,
|
|
850
147
|
toolGrouping,
|
|
851
148
|
compactMode,
|
|
852
149
|
disposeMessageDisplay,
|
|
@@ -910,8 +207,7 @@ export default function (
|
|
|
910
207
|
|
|
911
208
|
pi.on("tool_execution_end", async (event) => {
|
|
912
209
|
if (config.mode !== "compact") return;
|
|
913
|
-
// Agent
|
|
914
|
-
// 让 compact-thinking 先写入最终时长,再重绘摘要行。
|
|
210
|
+
// Agent 等工具收尾后延迟刷新,让 compact-thinking 先落最终态。
|
|
915
211
|
const toolCallId: string | undefined = event?.toolCallId;
|
|
916
212
|
setTimeout(() => {
|
|
917
213
|
compactModeHooks?.refreshToolCallMessage(toolCallId);
|
|
@@ -936,7 +232,7 @@ export default function (
|
|
|
936
232
|
// compact-thinking 的 session_start 处理在本 handler 之后执行(在其之上再装
|
|
937
233
|
// 一层 updateContent);延迟再同步一次,保证 compact 补丁最终位于外层。
|
|
938
234
|
setTimeout(() => syncCompactMode(ctx), 0);
|
|
939
|
-
scheduleSessionRender(() => hooks.toolGrouping.refresh(
|
|
235
|
+
scheduleSessionRender(() => hooks.toolGrouping.refresh(getToolMouseTui()));
|
|
940
236
|
});
|
|
941
237
|
|
|
942
238
|
pi.on("session_compact", async (event, ctx) => {
|
|
@@ -950,7 +246,7 @@ export default function (
|
|
|
950
246
|
syncCompactMode(ctx);
|
|
951
247
|
scheduleSessionRender(() => {
|
|
952
248
|
syncCompactMode(ctx);
|
|
953
|
-
hooks.toolGrouping.refresh(
|
|
249
|
+
hooks.toolGrouping.refresh(getToolMouseTui());
|
|
954
250
|
});
|
|
955
251
|
});
|
|
956
252
|
|
|
@@ -961,23 +257,18 @@ export default function (
|
|
|
961
257
|
scheduleSessionRender(() => syncCompactMode(ctx));
|
|
962
258
|
});
|
|
963
259
|
|
|
964
|
-
pi.on("tool_execution_start", async (
|
|
260
|
+
pi.on("tool_execution_start", async (_event, ctx) => {
|
|
965
261
|
installation?.toolGrouping.setTheme(ctx.ui.theme);
|
|
966
262
|
});
|
|
967
263
|
|
|
968
|
-
pi.on("session_shutdown", async (
|
|
264
|
+
pi.on("session_shutdown", async () => {
|
|
969
265
|
writeExecutionMetadata.clear();
|
|
970
266
|
// 鼠标交互独立于渲染层:fullscreen 下 installation 为 undefined,
|
|
971
267
|
// 但 onTerminalInput 监听与 handleViewportInput 包装仍需释放。
|
|
972
268
|
teardownToolMouseInteraction(mouseOwner);
|
|
973
269
|
const current = installation;
|
|
974
|
-
if (
|
|
975
|
-
|
|
976
|
-
(globalThis as any)[GLOBAL_TOOL_RENDER_PATCH] !== current.globalToolRendering ||
|
|
977
|
-
!current.globalToolRendering.active
|
|
978
|
-
)
|
|
979
|
-
return;
|
|
980
|
-
deactivateGlobalToolRendering(current.globalToolRendering);
|
|
270
|
+
if (!current || !current.defaultMode.isOwner()) return;
|
|
271
|
+
current.defaultMode.shutdown();
|
|
981
272
|
current.toolGrouping.shutdown();
|
|
982
273
|
current.disposeToolExpandedBackground();
|
|
983
274
|
current.compactMode.shutdown();
|
|
@@ -989,26 +280,18 @@ export default function (
|
|
|
989
280
|
});
|
|
990
281
|
}
|
|
991
282
|
|
|
992
|
-
// ----
|
|
283
|
+
// ---- 对外导出:入口/测试实际消费的符号 ----
|
|
284
|
+
export { getCompactThinkingConfig } from "../config/config.ts";
|
|
993
285
|
export {
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
} from "../config/config.ts";
|
|
1000
|
-
export type { CompactStyleMode, Config } from "../config/config.ts";
|
|
286
|
+
humanizeMcpToolName,
|
|
287
|
+
isMcpToolDefinition,
|
|
288
|
+
preservesOriginalRenderer,
|
|
289
|
+
shouldRenderRichDiff,
|
|
290
|
+
} from "./default-mode.ts";
|
|
1001
291
|
export {
|
|
1002
292
|
ExpandedToolIoView,
|
|
1003
293
|
ExpandedToolResultText,
|
|
1004
|
-
formatExpandHint,
|
|
1005
294
|
formatToolInputArgs,
|
|
1006
|
-
headTruncateToWidth,
|
|
1007
|
-
middleTruncateToWidth,
|
|
1008
|
-
outputLineCount,
|
|
1009
|
-
renderCollapsedToolResult,
|
|
1010
|
-
renderCollapsedToolResultToWidth,
|
|
1011
295
|
SHOW_MORE_LABEL,
|
|
1012
|
-
} from "./tool
|
|
1013
|
-
export
|
|
1014
|
-
export { installToolMouseInteraction } from "./mouse-interaction.ts";
|
|
296
|
+
} from "./tool/result.ts";
|
|
297
|
+
export { installToolMouseInteraction } from "./mouse/interaction.ts";
|