@herbertgao/pi-extensions 2026.10.4 → 2026.10.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.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +13 -9
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +13 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +72 -16
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +262 -48
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary/core.ts +2 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +131 -28
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/index.ts +2 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/subagent.ts +3 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer-currency.ts +29 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer.ts +96 -59
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/git-stats.ts +162 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/token-rate.ts +204 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/working-message.ts +51 -86
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +212 -74
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +50 -362
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +35 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +16 -83
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +28 -26
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/packets.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/sticky-prompt.ts +450 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/ask-user-question.ts +173 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/ccstyle-tool.ts +467 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/codemode.ts +5 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/ansi-utils.ts +1 -24
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-edit-render.ts +64 -11
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-limits.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-presentation.ts +41 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +5 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-text.ts +2 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-write-render.ts +36 -10
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/line-width-safety.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +140 -51
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/message-display.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/names.ts +44 -4
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +34 -803
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/tool-io.ts +735 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/tool-state.ts +138 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/format.ts +9 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/patch-keys.ts +5 -0
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +5 -4
- package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-dark.json +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-light.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/package.json +2 -1
- package/node_modules/@herbertgao/sol-pi/README.md +3 -3
- package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +4 -4
- package/node_modules/@herbertgao/sol-pi/agents-install.md +3 -3
- package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +1 -1
- package/package.json +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/show-more-hint.ts +0 -48
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* default mode(config.mode === "on"):Claude Code 风格工具卡渲染。
|
|
3
3
|
*
|
|
4
|
-
* 经 ToolExecutionComponent 原型补丁接管 call/result
|
|
4
|
+
* 经 ToolExecutionComponent 原型补丁接管 call/result。
|
|
5
|
+
* 卡片怎么画在 tool/ccstyle-tool.ts,本文件只负责安装与所有权。
|
|
5
6
|
* 生命周期对齐 compact-mode:installDefaultMode → hooks.shutdown。
|
|
6
7
|
*/
|
|
7
8
|
import { ToolExecutionComponent } from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import {
|
|
9
|
-
import { config, getToolDisplayConfig, type CompactStyleMode } from "../config/config.ts";
|
|
9
|
+
import { config, type CompactStyleMode } from "../config/config.ts";
|
|
10
10
|
import {
|
|
11
11
|
COMPONENT_TOOL_RENDER_MODE,
|
|
12
12
|
GLOBAL_TOOL_RENDER_PATCH,
|
|
@@ -14,48 +14,31 @@ import {
|
|
|
14
14
|
TOOL_EXPANDED_BACKGROUND_PATCH,
|
|
15
15
|
} from "../utils/patch-keys.ts";
|
|
16
16
|
import { isToolCallHovered } from "./mouse/hover.ts";
|
|
17
|
-
import {
|
|
18
|
-
countLines,
|
|
19
|
-
hasExpandableDetail,
|
|
20
|
-
insetComponent,
|
|
21
|
-
isToolExpanded,
|
|
22
|
-
outputLineCount,
|
|
23
|
-
pendingIcon,
|
|
24
|
-
renderCollapsedToolResultToWidth,
|
|
25
|
-
renderExpandedToolResult,
|
|
26
|
-
resolveToolVisualState,
|
|
27
|
-
scheduleAnimation,
|
|
28
|
-
settledIcon,
|
|
29
|
-
setToolVisualState,
|
|
30
|
-
textFromResult,
|
|
31
|
-
toolIconColor,
|
|
32
|
-
toolViewportWidth,
|
|
33
|
-
} from "./tool/result.ts";
|
|
34
|
-
import { oneLine } from "../utils/format.ts";
|
|
35
|
-
import { showMoreHintText } from "./tool/show-more-hint.ts";
|
|
36
|
-
import {
|
|
37
|
-
CODEMODE_TOOL_NAME,
|
|
38
|
-
codemodeExpandedBody,
|
|
39
|
-
createCodemodeResultComponent,
|
|
40
|
-
} from "./tool/codemode.ts";
|
|
41
|
-
import { countWriteDiffStats } from "./tool/diff/diff-renderer.ts";
|
|
42
|
-
import { renderRichToolResult, type WriteExecutionMetadataStore } from "./tool/diff/index.ts";
|
|
17
|
+
import { isLiveToolCall } from "./tool/tool-state.ts";
|
|
43
18
|
import { getMessageDisplayTheme } from "./tool/message-display.ts";
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
46
|
-
|
|
47
|
-
humanizeToolLabel,
|
|
48
|
-
renderToolSummary,
|
|
49
|
-
toolCallSummary,
|
|
50
|
-
} from "./tool/names.ts";
|
|
51
|
-
|
|
52
|
-
// 成功勾:亮绿 truecolor(与 message-display 一致)
|
|
53
|
-
const BRIGHT_GREEN = "\x1b[38;2;80;220;100m";
|
|
54
|
-
const ANSI_FG_RESET = "\x1b[39m";
|
|
19
|
+
import { createCcstyleTool } from "./tool/ccstyle-tool.ts";
|
|
20
|
+
import type { WriteExecutionMetadataStore } from "./tool/diff/index.ts";
|
|
21
|
+
export { shouldRenderRichDiff } from "./tool/ccstyle-tool.ts";
|
|
55
22
|
|
|
56
23
|
// pi-subagents 等扩展为 Agent 提供专用渲染器,ccstyle 必须保留。
|
|
57
24
|
const DEDICATED_RENDERER_TOOLS = new Set(["Agent"]);
|
|
58
25
|
|
|
26
|
+
/** 排除名单内且自带 renderer 的工具保留原渲染。 */
|
|
27
|
+
export function preservesOriginalRenderer(
|
|
28
|
+
extensionDefinition: any,
|
|
29
|
+
toolName: string,
|
|
30
|
+
builtInToolDefinition?: any,
|
|
31
|
+
excludeRenderers: readonly string[] = config.excludeRenderers,
|
|
32
|
+
): boolean {
|
|
33
|
+
if (!excludeRenderers.includes(toolName)) return false;
|
|
34
|
+
return [extensionDefinition, builtInToolDefinition].some(
|
|
35
|
+
(definition) =>
|
|
36
|
+
definition?.renderShell === "self" ||
|
|
37
|
+
typeof definition?.renderCall === "function" ||
|
|
38
|
+
typeof definition?.renderResult === "function",
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
59
42
|
export type DefaultModeHooks = {
|
|
60
43
|
/** 本安装是否仍持有全局工具渲染补丁。 */
|
|
61
44
|
isOwner(): boolean;
|
|
@@ -95,6 +78,9 @@ type GlobalToolRenderPatch = {
|
|
|
95
78
|
renderPaint?: (width: number) => string[];
|
|
96
79
|
invalidatePaint?: () => void;
|
|
97
80
|
paintCache?: WeakMap<object, ToolPaintHit>;
|
|
81
|
+
/** 给渲染上下文补 result;卸下时还原。 */
|
|
82
|
+
originalGetRenderContext?: (...args: any[]) => any;
|
|
83
|
+
getRenderContext?: (this: any, ...args: any[]) => any;
|
|
98
84
|
};
|
|
99
85
|
|
|
100
86
|
type ToolExpandedBackgroundPatch = {
|
|
@@ -105,326 +91,6 @@ type ToolExpandedBackgroundPatch = {
|
|
|
105
91
|
dispose: () => void;
|
|
106
92
|
};
|
|
107
93
|
|
|
108
|
-
/** rich diff 的两个入口(本处与 compact 的 paintCompactEditWrite)都靠 renderRichToolResult
|
|
109
|
-
* 返回 undefined 让位,这里只判断模式与工具名,避免多一个可能与让位不一致的 gate。 */
|
|
110
|
-
export function shouldRenderRichDiff(
|
|
111
|
-
mode: CompactStyleMode,
|
|
112
|
-
toolName: string,
|
|
113
|
-
isError: boolean,
|
|
114
|
-
): boolean {
|
|
115
|
-
return mode === "on" && !isError && (toolName === "edit" || toolName === "write");
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/** 排除名单内且自带 renderer 的工具保留原渲染。 */
|
|
119
|
-
export function preservesOriginalRenderer(
|
|
120
|
-
extensionDefinition: any,
|
|
121
|
-
toolName: string,
|
|
122
|
-
builtInToolDefinition?: any,
|
|
123
|
-
excludeRenderers: readonly string[] = config.excludeRenderers,
|
|
124
|
-
): boolean {
|
|
125
|
-
if (!excludeRenderers.includes(toolName)) return false;
|
|
126
|
-
return [extensionDefinition, builtInToolDefinition].some(
|
|
127
|
-
(definition) =>
|
|
128
|
-
definition?.renderShell === "self" ||
|
|
129
|
-
typeof definition?.renderCall === "function" ||
|
|
130
|
-
typeof definition?.renderResult === "function",
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function renderDefault(tool: any, slot: "renderCall" | "renderResult", args: any[], fallback = "") {
|
|
135
|
-
try {
|
|
136
|
-
if (typeof tool?.[slot] === "function") return tool[slot](...args);
|
|
137
|
-
} catch {
|
|
138
|
-
// Fall through to raw fallback.
|
|
139
|
-
}
|
|
140
|
-
return new Text(fallback, 0, 0);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
type ParsedTask = { id: string; status: string; subject: string };
|
|
144
|
-
|
|
145
|
-
function parseTaskList(text: string): ParsedTask[] {
|
|
146
|
-
return text
|
|
147
|
-
.split("\n")
|
|
148
|
-
.map((line) => line.match(/^#(\d+) \[([^\]]+)] (.+)$/))
|
|
149
|
-
.filter((match): match is RegExpMatchArray => Boolean(match))
|
|
150
|
-
.map((match) => ({ id: match[1]!, status: match[2]!, subject: match[3]! }));
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function taskListSummary(tasks: ParsedTask[]): string {
|
|
154
|
-
const counts = { pending: 0, in_progress: 0, completed: 0 };
|
|
155
|
-
for (const task of tasks) {
|
|
156
|
-
if (task.status in counts) counts[task.status as keyof typeof counts]++;
|
|
157
|
-
}
|
|
158
|
-
return [
|
|
159
|
-
`${tasks.length} tasks`,
|
|
160
|
-
counts.in_progress ? `${counts.in_progress} in progress` : "",
|
|
161
|
-
counts.pending ? `${counts.pending} pending` : "",
|
|
162
|
-
counts.completed ? `${counts.completed} completed` : "",
|
|
163
|
-
]
|
|
164
|
-
.filter(Boolean)
|
|
165
|
-
.join(" · ");
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function renderExpandedTaskResult(
|
|
169
|
-
toolName: string,
|
|
170
|
-
text: string,
|
|
171
|
-
theme: any,
|
|
172
|
-
isError: boolean,
|
|
173
|
-
): any | undefined {
|
|
174
|
-
if (isError) return undefined;
|
|
175
|
-
if (toolName === "TaskList") {
|
|
176
|
-
const tasks = parseTaskList(text);
|
|
177
|
-
if (!tasks.length) return undefined;
|
|
178
|
-
const limit = Math.max(1, config.expandedPreviewMaxLines);
|
|
179
|
-
const rows = tasks.slice(0, limit).map((task) => {
|
|
180
|
-
const color =
|
|
181
|
-
task.status === "completed"
|
|
182
|
-
? "success"
|
|
183
|
-
: task.status === "in_progress"
|
|
184
|
-
? "warning"
|
|
185
|
-
: "muted";
|
|
186
|
-
return `${theme.fg("accent", `#${task.id}`)} ${theme.fg(color, task.status)} ${theme.fg("dim", task.subject)}`;
|
|
187
|
-
});
|
|
188
|
-
if (tasks.length > rows.length)
|
|
189
|
-
rows.push(theme.fg("muted", `… ${tasks.length - rows.length} more tasks`));
|
|
190
|
-
// 贴左:外层展开卡片 Box(1,1) 提供 1 格 padding
|
|
191
|
-
return new Text(
|
|
192
|
-
`↳ ${theme.fg("muted", taskListSummary(tasks))}\n${rows.map((r) => ` ${r}`).join("\n")}`,
|
|
193
|
-
0,
|
|
194
|
-
0,
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
const line = text.trim();
|
|
198
|
-
if (!line || line.includes("\n")) return undefined;
|
|
199
|
-
let formatted: string | undefined;
|
|
200
|
-
let match: RegExpMatchArray | null;
|
|
201
|
-
if (
|
|
202
|
-
toolName === "TaskCreate" &&
|
|
203
|
-
(match = line.match(/^Task #(\d+) created successfully: (.+)$/))
|
|
204
|
-
) {
|
|
205
|
-
formatted = `${theme.fg("success", "Created task")} ${theme.fg("accent", `#${match[1]}`)} ${theme.fg("muted", match[2])}`;
|
|
206
|
-
} else if (toolName === "TaskUpdate" && (match = line.match(/^Updated task #(\d+) (.+)$/))) {
|
|
207
|
-
formatted = `${theme.fg("success", "Updated task")} ${theme.fg("accent", `#${match[1]}`)} ${theme.fg("muted", match[2])}`;
|
|
208
|
-
} else if (toolName === "TaskExecute") {
|
|
209
|
-
formatted = `${theme.fg("success", "Started")} ${theme.fg("muted", line)}`;
|
|
210
|
-
} else if (toolName === "TaskStop") {
|
|
211
|
-
formatted = `${theme.fg("success", "Stopped")} ${theme.fg("muted", line)}`;
|
|
212
|
-
}
|
|
213
|
-
return formatted ? new Text(`↳ ${formatted}`, 0, 0) : undefined;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/** 用 ccstyle call/result 包装任意工具定义。 */
|
|
217
|
-
function createCcstyleTool(
|
|
218
|
-
originalTool: any,
|
|
219
|
-
writeExecutionMetadata: WriteExecutionMetadataStore,
|
|
220
|
-
toolName: string,
|
|
221
|
-
): any {
|
|
222
|
-
const label = originalTool.label || toolName;
|
|
223
|
-
const defaultTitle = label === toolName ? humanizeToolLabel(label) : label;
|
|
224
|
-
// MCP 工具直接用 adapter 暴露的真实工具名,不做人性化
|
|
225
|
-
const title = mcpToolTitle({ toolName, definition: originalTool }) ?? defaultTitle;
|
|
226
|
-
|
|
227
|
-
return {
|
|
228
|
-
...originalTool,
|
|
229
|
-
renderShell: "self",
|
|
230
|
-
renderCall(args: any, theme: any, context: any) {
|
|
231
|
-
if (config.mode === "off") {
|
|
232
|
-
return renderDefault(originalTool, "renderCall", [args, theme, context], String(toolName));
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const visualState = resolveToolVisualState(context);
|
|
236
|
-
const isPending =
|
|
237
|
-
visualState === "pending" ||
|
|
238
|
-
(!visualState && (context?.isPartial || context?.executionStarted));
|
|
239
|
-
const animating = isPending && Boolean(context?.executionStarted);
|
|
240
|
-
if (animating) scheduleAnimation(context, { light: true });
|
|
241
|
-
const settledRawIcon = isPending ? "" : settledIcon(toolName, visualState);
|
|
242
|
-
const settledIconStyled =
|
|
243
|
-
visualState === "success"
|
|
244
|
-
? `${BRIGHT_GREEN}${settledRawIcon}${ANSI_FG_RESET}`
|
|
245
|
-
: theme.fg(toolIconColor(context), settledRawIcon);
|
|
246
|
-
// 逐帧 spinner:帧在 render() 内取,动画定时器只需 requestRender,
|
|
247
|
-
// 不再每次 tick 走 updateDisplay 把整张卡重建一遍。
|
|
248
|
-
const pendingIconStyled = () => theme.fg(toolIconColor(context), pendingIcon(toolName));
|
|
249
|
-
const summary = toolCallSummary(toolName, args, {
|
|
250
|
-
title,
|
|
251
|
-
variant: "default",
|
|
252
|
-
cwd: context?.cwd,
|
|
253
|
-
});
|
|
254
|
-
let writeStatsText = "";
|
|
255
|
-
let writeStatsStyled = "";
|
|
256
|
-
if (toolName === "write" && visualState === "success") {
|
|
257
|
-
const meta = writeExecutionMetadata.get(context?.toolCallId);
|
|
258
|
-
const stats = countWriteDiffStats(
|
|
259
|
-
typeof args?.content === "string" ? args.content : undefined,
|
|
260
|
-
meta?.previousContent,
|
|
261
|
-
meta?.fileExistedBeforeWrite,
|
|
262
|
-
);
|
|
263
|
-
if (stats) {
|
|
264
|
-
writeStatsText = ` (+${stats.added} -${stats.removed})`;
|
|
265
|
-
writeStatsStyled = ` ${theme.fg("dim", "(")}${theme.fg("success", `+${stats.added}`)} ${theme.fg("error", `-${stats.removed}`)}${theme.fg("dim", ")")}`;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
const extraText = writeStatsText || summary.detail;
|
|
269
|
-
const extraStyled = writeStatsStyled || theme.fg("dim", summary.detail);
|
|
270
|
-
let cachedWidth: number | undefined;
|
|
271
|
-
let cachedLine: string | undefined;
|
|
272
|
-
let cachedIcon: string | undefined;
|
|
273
|
-
const expanded = Boolean(context?.expanded);
|
|
274
|
-
return {
|
|
275
|
-
render(width: number) {
|
|
276
|
-
// 轻量 tick 只会 requestRender,靠这里续期,动画才能自维持。
|
|
277
|
-
if (animating) scheduleAnimation(context, { light: true });
|
|
278
|
-
const icon = isPending ? pendingIconStyled() : settledIconStyled;
|
|
279
|
-
if (cachedLine !== undefined && cachedWidth === width && cachedIcon === icon) {
|
|
280
|
-
return [cachedLine];
|
|
281
|
-
}
|
|
282
|
-
const viewportWidth = toolViewportWidth(width);
|
|
283
|
-
// 展开态贴左(外层 Box 已 pad 1);折叠 self-shell 保留 1 格前导空格
|
|
284
|
-
const lead = expanded ? "" : " ";
|
|
285
|
-
const callWidth = Math.max(
|
|
286
|
-
0,
|
|
287
|
-
viewportWidth - visibleWidth(icon) - 1 - (expanded ? 0 : 1),
|
|
288
|
-
);
|
|
289
|
-
const mainWidth = Math.max(0, callWidth - visibleWidth(extraText));
|
|
290
|
-
cachedWidth = width;
|
|
291
|
-
cachedIcon = icon;
|
|
292
|
-
// 路径按最终可用宽度中间截断,避免整行二次截断隐藏文件名。
|
|
293
|
-
cachedLine = `${lead}${icon} ${renderToolSummary(summary, mainWidth, theme.fg.bind(theme))}${extraStyled}`;
|
|
294
|
-
return [truncateToWidth(cachedLine, viewportWidth, "")];
|
|
295
|
-
},
|
|
296
|
-
invalidate() {
|
|
297
|
-
cachedLine = undefined;
|
|
298
|
-
cachedIcon = undefined;
|
|
299
|
-
},
|
|
300
|
-
};
|
|
301
|
-
},
|
|
302
|
-
renderResult(result: any, options: any, theme: any, context: any) {
|
|
303
|
-
if (config.mode === "off") {
|
|
304
|
-
return renderDefault(
|
|
305
|
-
originalTool,
|
|
306
|
-
"renderResult",
|
|
307
|
-
[result, options, theme, context],
|
|
308
|
-
textFromResult(result),
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const expanded = isToolExpanded(options, context);
|
|
313
|
-
const isError = Boolean(options?.isError || context?.isError);
|
|
314
|
-
// codemode:折叠态自己画子调用树,展开态给全部子调用与去掉脚本头的输出
|
|
315
|
-
if (toolName === CODEMODE_TOOL_NAME) {
|
|
316
|
-
if (!options?.isPartial) setToolVisualState(context, isError ? "error" : "success");
|
|
317
|
-
if (expanded) {
|
|
318
|
-
return renderExpandedToolResult(
|
|
319
|
-
codemodeExpandedBody(result),
|
|
320
|
-
theme,
|
|
321
|
-
isError,
|
|
322
|
-
context?.lastComponent,
|
|
323
|
-
context?.args,
|
|
324
|
-
context,
|
|
325
|
-
true,
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
// 折叠态不再复用展开视图,免得 hover/鼠标命中指向已隐藏的组件
|
|
329
|
-
if (context?.state) context.state.ccstyleIoView = undefined;
|
|
330
|
-
const toolCallId = context?.toolCallId;
|
|
331
|
-
return createCodemodeResultComponent({
|
|
332
|
-
result,
|
|
333
|
-
theme,
|
|
334
|
-
running: Boolean(options?.isPartial),
|
|
335
|
-
isError,
|
|
336
|
-
isHovered: () => isToolCallHovered(toolCallId),
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
if (options?.isPartial) {
|
|
340
|
-
// 展开态贴左(Box 提供 1 格 pad);折叠态保持 3 格对齐标题
|
|
341
|
-
const pending = expanded ? "↳ Pending…" : " ↳ Pending…";
|
|
342
|
-
return new Text(theme.fg("muted", pending), 0, 0);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
setToolVisualState(context, isError ? "error" : "success");
|
|
346
|
-
const toolCallId = context?.toolCallId;
|
|
347
|
-
if (shouldRenderRichDiff(config.mode, toolName, Boolean(isError))) {
|
|
348
|
-
// getter 保证 Diff indicator / wrap / limits 下次绘制即更新
|
|
349
|
-
const richResult = renderRichToolResult(
|
|
350
|
-
toolName,
|
|
351
|
-
result,
|
|
352
|
-
{
|
|
353
|
-
...options,
|
|
354
|
-
expanded,
|
|
355
|
-
// 全局共享状态避免 /reload 后旧 result renderer 闭包失联
|
|
356
|
-
isHovered: () => isToolCallHovered(toolCallId),
|
|
357
|
-
},
|
|
358
|
-
theme,
|
|
359
|
-
context,
|
|
360
|
-
writeExecutionMetadata,
|
|
361
|
-
getToolDisplayConfig,
|
|
362
|
-
);
|
|
363
|
-
// 展开态由 Box(1,1) 提供内边距;折叠态 inset 一级缩进
|
|
364
|
-
if (richResult) return expanded ? richResult : insetComponent(richResult);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
const text = textFromResult(result, expanded);
|
|
368
|
-
const args = context?.args;
|
|
369
|
-
if (expanded) {
|
|
370
|
-
const taskResult = renderExpandedTaskResult(toolName, text, theme, Boolean(isError));
|
|
371
|
-
if (taskResult) return taskResult;
|
|
372
|
-
}
|
|
373
|
-
const tasks = !isError && toolName === "TaskList" ? parseTaskList(text) : [];
|
|
374
|
-
const outputLines = outputLineCount(result) || countLines(text);
|
|
375
|
-
const lineWord = outputLines === 1 ? "line" : "lines";
|
|
376
|
-
const action = toolName === "read" ? "loaded" : "returned";
|
|
377
|
-
const rendered = tasks.length
|
|
378
|
-
? taskListSummary(tasks)
|
|
379
|
-
: isError
|
|
380
|
-
? text
|
|
381
|
-
? // 72 与原 result.ts 默认一致,保持错误摘要截断宽度。
|
|
382
|
-
oneLine(text, 72)
|
|
383
|
-
: "Failed"
|
|
384
|
-
: outputLines
|
|
385
|
-
? `${outputLines} ${lineWord} ${action}`
|
|
386
|
-
: "Done";
|
|
387
|
-
const expandable = !expanded && (tasks.length > 0 || hasExpandableDetail(text, args));
|
|
388
|
-
const hintText = showMoreHintText();
|
|
389
|
-
const hintPrefix = expandable ? theme.fg("dim", " · ") : "";
|
|
390
|
-
const hint = expandable ? hintPrefix + theme.fg("dim", hintText) : "";
|
|
391
|
-
const hoveredHint = expandable ? hintPrefix + theme.fg("text", hintText) : "";
|
|
392
|
-
if (expanded) {
|
|
393
|
-
return renderExpandedToolResult(
|
|
394
|
-
text || "",
|
|
395
|
-
theme,
|
|
396
|
-
Boolean(isError),
|
|
397
|
-
context?.lastComponent,
|
|
398
|
-
args,
|
|
399
|
-
context,
|
|
400
|
-
true, // mode=on:贴左,由外层 Box(1,1) 提供 1 格 padding
|
|
401
|
-
);
|
|
402
|
-
}
|
|
403
|
-
if (context?.state) context.state.ccstyleIoView = undefined;
|
|
404
|
-
let cachedWidth: number | undefined;
|
|
405
|
-
let cachedLine: string | undefined;
|
|
406
|
-
let cachedHoveredLine: string | undefined;
|
|
407
|
-
return {
|
|
408
|
-
render(width: number) {
|
|
409
|
-
if (cachedLine === undefined || cachedWidth !== width) {
|
|
410
|
-
cachedWidth = width;
|
|
411
|
-
cachedLine = theme.fg(
|
|
412
|
-
isError ? "error" : "muted",
|
|
413
|
-
renderCollapsedToolResultToWidth(rendered, hint, width),
|
|
414
|
-
);
|
|
415
|
-
cachedHoveredLine = theme.fg(
|
|
416
|
-
isError ? "error" : "muted",
|
|
417
|
-
renderCollapsedToolResultToWidth(rendered, hoveredHint, width),
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
return [isToolCallHovered(toolCallId) ? cachedHoveredLine! : cachedLine];
|
|
421
|
-
},
|
|
422
|
-
invalidate() {},
|
|
423
|
-
};
|
|
424
|
-
},
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
|
|
428
94
|
function syncToolShell(component: any, shell: "default" | "self"): void {
|
|
429
95
|
const target = shell === "self" ? component.selfRenderContainer : component.contentBox;
|
|
430
96
|
if (!target || !Array.isArray(component.children)) return;
|
|
@@ -566,6 +232,10 @@ function installGlobalToolRendering(
|
|
|
566
232
|
previous?.originalInvalidate && prototype.invalidate === previous.invalidatePaint
|
|
567
233
|
? previous.originalInvalidate
|
|
568
234
|
: prototype.invalidate;
|
|
235
|
+
const originalGetRenderContext =
|
|
236
|
+
previous?.originalGetRenderContext && prototype.getRenderContext === previous.getRenderContext
|
|
237
|
+
? previous.originalGetRenderContext
|
|
238
|
+
: prototype.getRenderContext;
|
|
569
239
|
if (isOwnershipAwarePatch(previous)) disconnectGlobalToolRenderPatch(previous);
|
|
570
240
|
|
|
571
241
|
const patch: GlobalToolRenderPatch = {
|
|
@@ -581,6 +251,7 @@ function installGlobalToolRendering(
|
|
|
581
251
|
installed: undefined as any,
|
|
582
252
|
originalRender,
|
|
583
253
|
originalInvalidate,
|
|
254
|
+
originalGetRenderContext,
|
|
584
255
|
paintCache: new WeakMap(),
|
|
585
256
|
};
|
|
586
257
|
|
|
@@ -618,6 +289,13 @@ function installGlobalToolRendering(
|
|
|
618
289
|
prototype.getRenderShell = patch.installed.getRenderShell;
|
|
619
290
|
prototype.getCallRenderer = patch.installed.getCallRenderer;
|
|
620
291
|
prototype.getResultRenderer = patch.installed.getResultRenderer;
|
|
292
|
+
// 渲染上下文没有 result。ask_user_question 的标题要报问题数,只能从这里补。
|
|
293
|
+
patch.getRenderContext = function (this: any, ...args: any[]) {
|
|
294
|
+
const context = originalGetRenderContext.apply(this, args);
|
|
295
|
+
if (context && typeof context === "object") context.result = this.result;
|
|
296
|
+
return context;
|
|
297
|
+
};
|
|
298
|
+
prototype.getRenderContext = patch.getRenderContext;
|
|
621
299
|
// fullscreen 滚动/选区每帧整树 render;内容未变时复用上一帧行,避免重做 diff/Box。
|
|
622
300
|
patch.renderPaint = function (this: any, width: number): string[] {
|
|
623
301
|
if (!patch.active || patch.mode() === "off") {
|
|
@@ -626,8 +304,11 @@ function installGlobalToolRendering(
|
|
|
626
304
|
const cache = patch.paintCache;
|
|
627
305
|
// A running partial tool owns a time-varying spinner. Light animation ticks
|
|
628
306
|
// request a repaint without invalidating the component, so settled-content
|
|
629
|
-
// caching must not intercept those paints.
|
|
630
|
-
|
|
307
|
+
// caching must not intercept those paints. The waiting window before the
|
|
308
|
+
// tool is dispatched animates too, so it must bypass the cache as well.
|
|
309
|
+
const animatingRow =
|
|
310
|
+
this.isPartial && (this.executionStarted || isLiveToolCall(this.toolCallId));
|
|
311
|
+
const cacheable = !animatingRow;
|
|
631
312
|
const hit = cacheable ? cache?.get(this) : undefined;
|
|
632
313
|
if (hit && toolPaintMatches(hit, this, width)) return hit.lines;
|
|
633
314
|
const lines = originalRender.call(this, width);
|
|
@@ -683,6 +364,13 @@ function deactivateGlobalToolRendering(patch: GlobalToolRenderPatch): void {
|
|
|
683
364
|
) {
|
|
684
365
|
prototype.invalidate = patch.originalInvalidate;
|
|
685
366
|
}
|
|
367
|
+
if (
|
|
368
|
+
patch.getRenderContext &&
|
|
369
|
+
prototype.getRenderContext === patch.getRenderContext &&
|
|
370
|
+
patch.originalGetRenderContext
|
|
371
|
+
) {
|
|
372
|
+
prototype.getRenderContext = patch.originalGetRenderContext;
|
|
373
|
+
}
|
|
686
374
|
}
|
|
687
375
|
|
|
688
376
|
/** 展开面板背景统一为 user message 背景色;折叠行保持原生状态色。
|
|
@@ -31,8 +31,18 @@ import {
|
|
|
31
31
|
TOOL_MOUSE_DISABLE,
|
|
32
32
|
} from "./mouse/interaction.ts";
|
|
33
33
|
import { getToolMouseTui, noteNewTranscriptItem } from "./mouse/scroll.ts";
|
|
34
|
+
import {
|
|
35
|
+
disposeStickyPromptPatches,
|
|
36
|
+
installStickyPromptPatches,
|
|
37
|
+
setStickyPromptTheme,
|
|
38
|
+
} from "./mouse/sticky-prompt.ts";
|
|
34
39
|
import { setHoveredToolGroup, setHoveredToolIo } from "./mouse/hover.ts";
|
|
35
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
clearAllAnimations,
|
|
42
|
+
clearLiveToolCalls,
|
|
43
|
+
noteStreamingToolCalls,
|
|
44
|
+
retireLiveToolCall,
|
|
45
|
+
} from "./tool/tool-state.ts";
|
|
36
46
|
import { installWriteOverride, WriteExecutionMetadataStore } from "./tool/diff/index.ts";
|
|
37
47
|
import {
|
|
38
48
|
installMessageDisplayRendering,
|
|
@@ -123,6 +133,8 @@ export default function (
|
|
|
123
133
|
if (staleGlobalPatch) staleGlobalPatch.mode = () => "off";
|
|
124
134
|
// regular 主屏的差分渲染补丁:视口上方变化不再触发清回滚的 fullRender。
|
|
125
135
|
installMainScreenDoRenderPatch();
|
|
136
|
+
// 粘性用户消息头:标记注入 + fullscreen 帧合成。
|
|
137
|
+
installStickyPromptPatches();
|
|
126
138
|
const defaultMode = installDefaultMode(writeExecutionMetadata);
|
|
127
139
|
const toolGrouping = installToolGrouping(() => config.mode === "on");
|
|
128
140
|
const compactMode = installCompactMode({
|
|
@@ -189,6 +201,9 @@ export default function (
|
|
|
189
201
|
});
|
|
190
202
|
|
|
191
203
|
pi.on("message_update", async (event) => {
|
|
204
|
+
// 声明期(assistant 消息结束前)就记住工具 id:这段工具行已在等启动,
|
|
205
|
+
// spinner 该转,但 Pi 还没跑 markExecutionStarted。
|
|
206
|
+
if (event.message?.role === "assistant") noteStreamingToolCalls(event.message);
|
|
192
207
|
// compact-thinking 在 session_start 时于 compact 补丁之上再装一层;
|
|
193
208
|
// 扩展事件先于 interactive-mode 的 updateContent 派发,此处先重新认领。
|
|
194
209
|
if (config.mode === "compact" && event.message?.role === "assistant") {
|
|
@@ -204,6 +219,7 @@ export default function (
|
|
|
204
219
|
});
|
|
205
220
|
|
|
206
221
|
pi.on("tool_execution_end", async (event) => {
|
|
222
|
+
retireLiveToolCall(event?.toolCallId);
|
|
207
223
|
if (config.mode !== "compact") return;
|
|
208
224
|
// Agent 等工具收尾后延迟刷新,让 compact-thinking 先落最终态。
|
|
209
225
|
const toolCallId: string | undefined = event?.toolCallId;
|
|
@@ -232,12 +248,16 @@ export default function (
|
|
|
232
248
|
if (!hooks) return;
|
|
233
249
|
hooks.toolGrouping.setTheme(ctx.ui.theme);
|
|
234
250
|
setMessageDisplayTheme(ctx.ui.theme);
|
|
251
|
+
setStickyPromptTheme(ctx.ui.theme);
|
|
235
252
|
ctx.ui.setStatus("ccstyle", undefined);
|
|
236
253
|
// 先收集 resume transcript,再同步 compact 补丁与全局展开状态。
|
|
237
254
|
syncCompactMode(ctx);
|
|
238
255
|
// compact-thinking 的 session_start 处理在本 handler 之后执行(在其之上再装
|
|
239
256
|
// 一层 updateContent);延迟再同步一次,保证 compact 补丁最终位于外层。
|
|
240
257
|
setTimeout(() => syncCompactMode(ctx), 0);
|
|
258
|
+
// 历史回放发生在 session_start 之后,延迟到回放结束再收尾最后一个回合,
|
|
259
|
+
// 否则它没有后续消息,摘要行会停在 Running。
|
|
260
|
+
setTimeout(() => compactModeHooks?.settleReplayedRound(), 0);
|
|
241
261
|
scheduleSessionRender(() => hooks.toolGrouping.refresh(getToolMouseTui()));
|
|
242
262
|
});
|
|
243
263
|
|
|
@@ -249,6 +269,7 @@ export default function (
|
|
|
249
269
|
if (!hooks) return;
|
|
250
270
|
hooks.toolGrouping.setTheme(ctx.ui.theme);
|
|
251
271
|
setMessageDisplayTheme(ctx.ui.theme);
|
|
272
|
+
setStickyPromptTheme(ctx.ui.theme);
|
|
252
273
|
syncCompactMode(ctx);
|
|
253
274
|
scheduleSessionRender(() => {
|
|
254
275
|
syncCompactMode(ctx);
|
|
@@ -268,8 +289,18 @@ export default function (
|
|
|
268
289
|
noteNewTranscriptItem();
|
|
269
290
|
});
|
|
270
291
|
|
|
292
|
+
pi.on("turn_end", async () => {
|
|
293
|
+
clearLiveToolCalls();
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
pi.on("agent_end", async () => {
|
|
297
|
+
clearLiveToolCalls();
|
|
298
|
+
});
|
|
299
|
+
|
|
271
300
|
pi.on("session_shutdown", async () => {
|
|
272
301
|
writeExecutionMetadata.clear();
|
|
302
|
+
// 会话结束就清空,不能等 installation 早退判断;残留的 id 会让下一会话误转。
|
|
303
|
+
clearLiveToolCalls();
|
|
273
304
|
// 鼠标交互独立于渲染层:fullscreen 下 installation 为 undefined,
|
|
274
305
|
// 但 onTerminalInput 监听与 handleViewportInput 包装仍需释放。
|
|
275
306
|
teardownToolMouseInteraction(mouseOwner);
|
|
@@ -281,6 +312,7 @@ export default function (
|
|
|
281
312
|
current.compactMode.shutdown();
|
|
282
313
|
compactModeHooks = undefined;
|
|
283
314
|
current.disposeMessageDisplay();
|
|
315
|
+
disposeStickyPromptPatches();
|
|
284
316
|
clearAllAnimations();
|
|
285
317
|
installation = undefined;
|
|
286
318
|
});
|
|
@@ -300,6 +332,6 @@ export {
|
|
|
300
332
|
ExpandedToolIoView,
|
|
301
333
|
ExpandedToolResultText,
|
|
302
334
|
formatToolInputArgs,
|
|
303
|
-
|
|
304
|
-
} from "./tool/result.ts";
|
|
335
|
+
} from "./tool/tool-io.ts";
|
|
336
|
+
export { SHOW_MORE_LABEL } from "./tool/result.ts";
|
|
305
337
|
export { installToolMouseInteraction } from "./mouse/interaction.ts";
|