pi-cc-extensions 0.9.1 → 0.9.3
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 +47 -42
- package/README.md +44 -39
- package/assets/readme/preview.webp +0 -0
- package/extensions/config/config.ts +17 -0
- package/extensions/config/panel.ts +221 -41
- package/extensions/feature/shell/footer-layout.ts +229 -0
- package/extensions/feature/shell/footer.ts +92 -41
- package/extensions/renderer/default-mode.ts +21 -7
- package/extensions/renderer/tool/grouping.ts +22 -1
- package/extensions/renderer/tool/names.ts +100 -54
- package/extensions/renderer/tool/result.ts +18 -3
- package/package.json +3 -4
- package/assets/readme/hero.en.svg +0 -101
- package/assets/readme/hero.svg +0 -101
- package/assets/readme/preview.png +0 -0
- package/assets/readme/welcome.webp +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 自定义底栏:chips + zentui 图标/句子
|
|
3
3
|
*
|
|
4
|
-
* 第一行:model thinking · ██████░░░░ 45%/200k · 42% · $0.01 ·
|
|
5
|
-
* 第二行:cwd in session on branch (+16 −1) ·
|
|
4
|
+
* 第一行:model thinking · ██████░░░░ 45%/200k · 42% · $0.01 · 用户排到 line1 的插件芯片
|
|
5
|
+
* 第二行:cwd in session on branch (+16 −1) · 用户排到 line2 的插件芯片
|
|
6
|
+
* 第三行:仅当 line3 有可见插件芯片时出现
|
|
6
7
|
*
|
|
7
8
|
* - line1 短芯片,· 分隔;缓存用 zentui ,费用 success
|
|
8
9
|
* - line2 用 zentui 句式 in / on +
|
|
@@ -20,13 +21,26 @@ import { join } from "node:path";
|
|
|
20
21
|
import { pathToFileURL } from "node:url";
|
|
21
22
|
import { config } from "../../config/config.ts";
|
|
22
23
|
import { stripAnsi } from "../../utils/ansi-text.ts";
|
|
24
|
+
import {
|
|
25
|
+
PI_USAGE_KEY,
|
|
26
|
+
isSkippedFooterStatusKey,
|
|
27
|
+
resolveFooterChipLayout,
|
|
28
|
+
visibleFooterPluginTexts,
|
|
29
|
+
type FooterChipLayout,
|
|
30
|
+
} from "./footer-layout.ts";
|
|
23
31
|
|
|
24
32
|
const GIT_REFRESH_INTERVAL_MS = 10_000;
|
|
25
33
|
const USAGE_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
|
|
26
34
|
const USAGE_TIMEOUT_MS = 15_000;
|
|
27
|
-
// zentui NERD_DEFAULT_ICONS:git / cacheHit
|
|
28
|
-
const
|
|
29
|
-
const
|
|
35
|
+
// zentui NERD_DEFAULT_ICONS:git / cacheHit。无 Nerd Font 时留空,只保留文字。
|
|
36
|
+
export const FOOTER_NERD_ICON_GIT = "";
|
|
37
|
+
export const FOOTER_NERD_ICON_CACHE = "";
|
|
38
|
+
|
|
39
|
+
export function footerGlyphs(nerdIcons: boolean): { git: string; cache: string } {
|
|
40
|
+
return nerdIcons
|
|
41
|
+
? { git: FOOTER_NERD_ICON_GIT, cache: FOOTER_NERD_ICON_CACHE }
|
|
42
|
+
: { git: "", cache: "" };
|
|
43
|
+
}
|
|
30
44
|
|
|
31
45
|
type GitStats = { add: number; del: number };
|
|
32
46
|
|
|
@@ -54,12 +68,6 @@ export function parseGitStats(stdout: string): GitStats {
|
|
|
54
68
|
return { add, del };
|
|
55
69
|
}
|
|
56
70
|
|
|
57
|
-
export function classifyStatus(key: string): "usage" | "skip" | "other" {
|
|
58
|
-
if (key === "model") return "skip"; // line1 已有供应商/模型
|
|
59
|
-
if (/usage|quota|balance/i.test(key)) return "usage";
|
|
60
|
-
return "other";
|
|
61
|
-
}
|
|
62
|
-
|
|
63
71
|
export function formatXaiFooterChip(report: XaiFooterReport): string | undefined {
|
|
64
72
|
const included = report.buckets?.find((b) => b.id === "included-allowance");
|
|
65
73
|
if (included?.unit === "percent" && typeof included.used === "number") {
|
|
@@ -87,14 +95,44 @@ export function formatXaiFooterChip(report: XaiFooterReport): string | undefined
|
|
|
87
95
|
return undefined;
|
|
88
96
|
}
|
|
89
97
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
+
const cachedExtensionStatuses = new Map<string, string>();
|
|
99
|
+
let cachedLocalUsageText = "";
|
|
100
|
+
|
|
101
|
+
/** 面板用:当前 setStatus 文案 + 本包 pi-usage(无文案时为空字符串)。 */
|
|
102
|
+
export function getFooterStatusSnapshot(): Map<string, string> {
|
|
103
|
+
const out = new Map<string, string>();
|
|
104
|
+
for (const [key, text] of cachedExtensionStatuses) {
|
|
105
|
+
if (isSkippedFooterStatusKey(key)) continue;
|
|
106
|
+
out.set(key, text);
|
|
107
|
+
}
|
|
108
|
+
out.set(PI_USAGE_KEY, cachedLocalUsageText);
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function rememberExtensionStatuses(entries: Iterable<[string, string]>): void {
|
|
113
|
+
cachedExtensionStatuses.clear();
|
|
114
|
+
for (const [key, raw] of entries) {
|
|
115
|
+
if (isSkippedFooterStatusKey(key)) continue;
|
|
116
|
+
const text = stripAnsi(raw.replace(/[\r\n\t]+/g, " ").trim());
|
|
117
|
+
if (text) cachedExtensionStatuses.set(key, text);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function pluginTextsForRender(localUsageChip: string): Map<string, string> {
|
|
122
|
+
cachedLocalUsageText = stripAnsi(localUsageChip.replace(/[\r\n\t]+/g, " ").trim());
|
|
123
|
+
const texts = new Map<string, string>(cachedExtensionStatuses);
|
|
124
|
+
if (cachedLocalUsageText) texts.set(PI_USAGE_KEY, cachedLocalUsageText);
|
|
125
|
+
else texts.delete(PI_USAGE_KEY);
|
|
126
|
+
return texts;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function layoutFromConfig(): FooterChipLayout {
|
|
130
|
+
return {
|
|
131
|
+
footerHiddenKeys: config.footerHiddenKeys,
|
|
132
|
+
footerLine1Keys: config.footerLine1Keys,
|
|
133
|
+
footerLine2Keys: config.footerLine2Keys,
|
|
134
|
+
footerLine3Keys: config.footerLine3Keys,
|
|
135
|
+
};
|
|
98
136
|
}
|
|
99
137
|
|
|
100
138
|
let piUsageMod: any | null | undefined;
|
|
@@ -317,26 +355,33 @@ const createCustomFooterFactory =
|
|
|
317
355
|
const thinkingLevelStr = ctx.thinkingLevel || "off";
|
|
318
356
|
const thinkingColor = theme.getThinkingBorderColor(thinkingLevelStr);
|
|
319
357
|
|
|
320
|
-
|
|
321
|
-
const usageStatuses: string[] = [];
|
|
322
|
-
for (const [key, raw] of Array.from(
|
|
358
|
+
rememberExtensionStatuses(
|
|
323
359
|
footerData.getExtensionStatuses().entries() as Iterable<[string, string]>,
|
|
324
|
-
)
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
360
|
+
);
|
|
361
|
+
const pluginTexts = pluginTextsForRender(localUsageChip);
|
|
362
|
+
const layout = resolveFooterChipLayout(layoutFromConfig(), [...pluginTexts.keys()]);
|
|
363
|
+
const dimPlugin = (text: string) => colorUsageChip(theme, text);
|
|
364
|
+
const line1Plugins = visibleFooterPluginTexts(
|
|
365
|
+
layout.footerLine1Keys,
|
|
366
|
+
layout.footerHiddenKeys,
|
|
367
|
+
pluginTexts,
|
|
368
|
+
).map(dimPlugin);
|
|
369
|
+
const line2Plugins = visibleFooterPluginTexts(
|
|
370
|
+
layout.footerLine2Keys,
|
|
371
|
+
layout.footerHiddenKeys,
|
|
372
|
+
pluginTexts,
|
|
373
|
+
).map(dimPlugin);
|
|
374
|
+
const line3Plugins = visibleFooterPluginTexts(
|
|
375
|
+
layout.footerLine3Keys,
|
|
376
|
+
layout.footerHiddenKeys,
|
|
377
|
+
pluginTexts,
|
|
378
|
+
).map(dimPlugin);
|
|
337
379
|
|
|
338
380
|
const pctLabel = percent === null || percent === undefined ? "?" : `${Math.floor(percent)}%`;
|
|
339
381
|
const cachePct = getCachePct();
|
|
382
|
+
const glyphs = footerGlyphs(config.footerNerdIcons);
|
|
383
|
+
const cacheLabel =
|
|
384
|
+
cachePct > 0 ? `${glyphs.cache ? `${glyphs.cache} ` : ""}${Math.floor(cachePct)}%` : "";
|
|
340
385
|
const costChip =
|
|
341
386
|
cost || usingSubscription
|
|
342
387
|
? theme.fg("dim", `$${cost.toFixed(2)}`) +
|
|
@@ -346,9 +391,9 @@ const createCustomFooterFactory =
|
|
|
346
391
|
theme.fg("accent", modelLabel),
|
|
347
392
|
model?.reasoning ? thinkingColor(thinkingLevelStr) : "",
|
|
348
393
|
barGauge(percent ?? 0) + theme.fg("dim", ` ${pctLabel}/${fmt(contextWindow)}`),
|
|
349
|
-
|
|
394
|
+
cacheLabel ? theme.fg("dim", cacheLabel) : "",
|
|
350
395
|
costChip,
|
|
351
|
-
|
|
396
|
+
...line1Plugins,
|
|
352
397
|
]);
|
|
353
398
|
|
|
354
399
|
const gitColor = theme.getThinkingBorderColor("medium");
|
|
@@ -367,11 +412,17 @@ const createCustomFooterFactory =
|
|
|
367
412
|
theme.fg("error", `−${gitStats.del}`) +
|
|
368
413
|
theme.fg("dim", ")")
|
|
369
414
|
: "";
|
|
370
|
-
|
|
415
|
+
const gitLabel = glyphs.git ? `${glyphs.git} ${branch}` : branch;
|
|
416
|
+
place += theme.fg("muted", " on ") + gitColor(gitLabel) + stats;
|
|
371
417
|
}
|
|
372
|
-
const line2 = joinChips([place, ...
|
|
373
|
-
|
|
374
|
-
|
|
418
|
+
const line2 = joinChips([place, ...line2Plugins]);
|
|
419
|
+
const line3 = joinChips(line3Plugins);
|
|
420
|
+
|
|
421
|
+
return [
|
|
422
|
+
truncateToWidth(line1, width),
|
|
423
|
+
...(line2 ? [truncateToWidth(line2, width)] : []),
|
|
424
|
+
...(line3 ? [truncateToWidth(line3, width)] : []),
|
|
425
|
+
];
|
|
375
426
|
};
|
|
376
427
|
|
|
377
428
|
return {
|
|
@@ -241,12 +241,16 @@ function createCcstyleTool(
|
|
|
241
241
|
const isPending =
|
|
242
242
|
visualState === "pending" ||
|
|
243
243
|
(!visualState && (context?.isPartial || context?.executionStarted));
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
244
|
+
const animating = isPending && Boolean(context?.executionStarted);
|
|
245
|
+
if (animating) scheduleAnimation(context, { light: true });
|
|
246
|
+
const settledRawIcon = isPending ? "" : settledIcon(toolName, visualState);
|
|
247
|
+
const settledIconStyled =
|
|
247
248
|
visualState === "success"
|
|
248
|
-
? `${BRIGHT_GREEN}${
|
|
249
|
-
: theme.fg(toolIconColor(context),
|
|
249
|
+
? `${BRIGHT_GREEN}${settledRawIcon}${ANSI_FG_RESET}`
|
|
250
|
+
: theme.fg(toolIconColor(context), settledRawIcon);
|
|
251
|
+
// 逐帧 spinner:帧在 render() 内取,动画定时器只需 requestRender,
|
|
252
|
+
// 不再每次 tick 走 updateDisplay 把整张卡重建一遍。
|
|
253
|
+
const pendingIconStyled = () => theme.fg(toolIconColor(context), pendingIcon(toolName));
|
|
250
254
|
const summary = toolCallSummary(toolName, args, {
|
|
251
255
|
title: label === toolName ? humanizeToolLabel(label) : label,
|
|
252
256
|
variant: "default",
|
|
@@ -270,10 +274,16 @@ function createCcstyleTool(
|
|
|
270
274
|
const extraStyled = writeStatsStyled || theme.fg("dim", summary.detail);
|
|
271
275
|
let cachedWidth: number | undefined;
|
|
272
276
|
let cachedLine: string | undefined;
|
|
277
|
+
let cachedIcon: string | undefined;
|
|
273
278
|
const expanded = Boolean(context?.expanded);
|
|
274
279
|
return {
|
|
275
280
|
render(width: number) {
|
|
276
|
-
|
|
281
|
+
// 轻量 tick 只会 requestRender,靠这里续期,动画才能自维持。
|
|
282
|
+
if (animating) scheduleAnimation(context, { light: true });
|
|
283
|
+
const icon = isPending ? pendingIconStyled() : settledIconStyled;
|
|
284
|
+
if (cachedLine !== undefined && cachedWidth === width && cachedIcon === icon) {
|
|
285
|
+
return [cachedLine];
|
|
286
|
+
}
|
|
277
287
|
const viewportWidth = toolViewportWidth(width);
|
|
278
288
|
// 展开态贴左(外层 Box 已 pad 1);折叠 self-shell 保留 1 格前导空格
|
|
279
289
|
const lead = expanded ? "" : " ";
|
|
@@ -283,11 +293,15 @@ function createCcstyleTool(
|
|
|
283
293
|
);
|
|
284
294
|
const mainWidth = Math.max(0, callWidth - visibleWidth(extraText));
|
|
285
295
|
cachedWidth = width;
|
|
296
|
+
cachedIcon = icon;
|
|
286
297
|
// 路径按最终可用宽度中间截断,避免整行二次截断隐藏文件名。
|
|
287
298
|
cachedLine = `${lead}${icon} ${theme.fg("toolTitle", fitToolCallSummary(summary, mainWidth))}${extraStyled}`;
|
|
288
299
|
return [truncateToWidth(cachedLine, viewportWidth, "")];
|
|
289
300
|
},
|
|
290
|
-
invalidate() {
|
|
301
|
+
invalidate() {
|
|
302
|
+
cachedLine = undefined;
|
|
303
|
+
cachedIcon = undefined;
|
|
304
|
+
},
|
|
291
305
|
};
|
|
292
306
|
},
|
|
293
307
|
renderResult(result: any, options: any, theme: any, context: any) {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type Component,
|
|
8
8
|
} from "@earendil-works/pi-tui";
|
|
9
9
|
import { TOOL_LOADING_INTERVAL_MS, toolLoadingIcon } from "../../utils/tool-loading-icon.ts";
|
|
10
|
+
import { getToolMouseTui } from "../mouse/scroll.ts";
|
|
10
11
|
import { isToolTuiFullscreen, showMoreHintText } from "./show-more-hint.ts";
|
|
11
12
|
import { stripAnsi, stripBackgroundAnsi, stripLeadingStatusIcon } from "../../utils/ansi-text.ts";
|
|
12
13
|
import { walkComponentTree } from "../../utils/component-tree.ts";
|
|
@@ -89,18 +90,38 @@ function scheduleGroupAnimation(patch: Patch): void {
|
|
|
89
90
|
patch.animationTimer = setTimeout(() => {
|
|
90
91
|
patch.animationTimer = null;
|
|
91
92
|
if (!patch.active) return;
|
|
93
|
+
let needsRender = false;
|
|
92
94
|
for (const group of patch.groups) {
|
|
93
95
|
if (
|
|
94
96
|
(group.children as any[]).some(
|
|
95
97
|
(tool) => tool?.executionStarted && status(tool) === "pending",
|
|
96
98
|
)
|
|
97
|
-
)
|
|
99
|
+
) {
|
|
98
100
|
group.invalidate();
|
|
101
|
+
// 分组卡不是 ToolExecutionComponent:它的 invalidate() 不会请求渲染,
|
|
102
|
+
// 不补这一步 spinner 只在别人渲染时才跳帧(并行工具下表现为卡顿/冻结)。
|
|
103
|
+
needsRender = true;
|
|
104
|
+
}
|
|
99
105
|
}
|
|
106
|
+
if (needsRender) requestAnimationRender(patch);
|
|
100
107
|
}, TOOL_LOADING_INTERVAL_MS);
|
|
101
108
|
patch.animationTimer.unref?.();
|
|
102
109
|
}
|
|
103
110
|
|
|
111
|
+
/** 借用子工具卡的 ui 请求一帧;子卡缺失时回退到扩展持有的 TUI 槽。 */
|
|
112
|
+
function requestAnimationRender(patch: Patch): void {
|
|
113
|
+
for (const group of patch.groups) {
|
|
114
|
+
const ui = (group.children as any[]).find(
|
|
115
|
+
(tool) => typeof tool?.ui?.requestRender === "function",
|
|
116
|
+
)?.ui;
|
|
117
|
+
if (ui) {
|
|
118
|
+
ui.requestRender();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
getToolMouseTui()?.requestRender?.();
|
|
123
|
+
}
|
|
124
|
+
|
|
104
125
|
function visibleLines(lines: string[]): string[] {
|
|
105
126
|
return lines.filter((line) => stripAnsi(line).trim());
|
|
106
127
|
}
|
|
@@ -7,11 +7,18 @@ function clip(value: unknown): string {
|
|
|
7
7
|
return oneLine(value, config.inputClip);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/** 品牌大小写固定写法;humanize 推导不出来的工具名写在这里。 */
|
|
11
|
+
const TOOL_LABEL_OVERRIDES: Readonly<Record<string, string>> = {
|
|
12
|
+
powershell: "PowerShell",
|
|
13
|
+
};
|
|
14
|
+
|
|
10
15
|
/**
|
|
11
16
|
* 工具名/标签人性化:与 default-mode 的 humanizeToolLabel、grouping 的 humanizeToolName
|
|
12
17
|
* 逐字相同,收敛为一个共享实现。
|
|
13
18
|
*/
|
|
14
19
|
export function humanizeToolLabel(label: string): string {
|
|
20
|
+
const brand = TOOL_LABEL_OVERRIDES[label.toLowerCase()];
|
|
21
|
+
if (brand) return brand;
|
|
15
22
|
return label
|
|
16
23
|
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
17
24
|
.replace(/[_-]+/g, " ")
|
|
@@ -134,11 +141,84 @@ function pathSummary(
|
|
|
134
141
|
};
|
|
135
142
|
}
|
|
136
143
|
|
|
144
|
+
/** 入参是 shell 命令的工具。 */
|
|
145
|
+
const SHELL_TOOL_NAMES = new Set(["bash", "powershell", "pwsh", "sh", "zsh", "cmd"]);
|
|
146
|
+
|
|
147
|
+
/** 内置检索工具:pattern 缺失(无效调用)时保留 "…" 占位。 */
|
|
148
|
+
const SEARCH_TOOL_NAMES = new Set(["grep", "find"]);
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 通用摘要字段优先级:default 与 grouping 共用同一份取值链。
|
|
152
|
+
* 两处曾各自维护,grouping 因此漏掉 command(powershell)和数组型入参;
|
|
153
|
+
* 路径不在链上,由 pathSummary 兜底,以便按 cwd 相对化并中间截断。
|
|
154
|
+
*/
|
|
155
|
+
const GENERIC_SUMMARY_FIELDS = [
|
|
156
|
+
"agent_id",
|
|
157
|
+
"command",
|
|
158
|
+
"query",
|
|
159
|
+
"queries",
|
|
160
|
+
"claim",
|
|
161
|
+
"question",
|
|
162
|
+
"questions",
|
|
163
|
+
"url",
|
|
164
|
+
"urls",
|
|
165
|
+
"responseId",
|
|
166
|
+
"findText",
|
|
167
|
+
"name",
|
|
168
|
+
"tool_use_id",
|
|
169
|
+
"toolCallId",
|
|
170
|
+
"id",
|
|
171
|
+
"subject",
|
|
172
|
+
"taskId",
|
|
173
|
+
"task_id",
|
|
174
|
+
"message",
|
|
175
|
+
"description",
|
|
176
|
+
"prompt",
|
|
177
|
+
] as const;
|
|
178
|
+
|
|
179
|
+
/** 通用字段链取值:字符串直接用;数组取首个可展示项并标注剩余条数。 */
|
|
180
|
+
type SummaryValue = { text: string; more: number };
|
|
181
|
+
|
|
182
|
+
function arrayItemText(item: unknown): string | undefined {
|
|
183
|
+
if (typeof item === "string" && item) return item;
|
|
184
|
+
if (!item || typeof item !== "object") return undefined;
|
|
185
|
+
for (const key of ["query", "url", "question", "header", "name"] as const) {
|
|
186
|
+
const text = (item as Record<string, unknown>)[key];
|
|
187
|
+
if (typeof text === "string" && text) return text;
|
|
188
|
+
}
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function summaryFieldValue(value: unknown): SummaryValue | undefined {
|
|
193
|
+
if (typeof value === "string" && value) return { text: value, more: 0 };
|
|
194
|
+
if (!Array.isArray(value)) return undefined;
|
|
195
|
+
let first: string | undefined;
|
|
196
|
+
let count = 0;
|
|
197
|
+
for (const item of value) {
|
|
198
|
+
const text = arrayItemText(item);
|
|
199
|
+
if (!text) continue;
|
|
200
|
+
count += 1;
|
|
201
|
+
first ??= text;
|
|
202
|
+
}
|
|
203
|
+
return first === undefined ? undefined : { text: first, more: count - 1 };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** 通用字段链取值;无可用字段时返回 undefined,交给调用方继续降级。 */
|
|
207
|
+
function genericSummary(title: string, args: any): ToolCallSummary | undefined {
|
|
208
|
+
for (const key of GENERIC_SUMMARY_FIELDS) {
|
|
209
|
+
const found = summaryFieldValue(args[key]);
|
|
210
|
+
if (!found) continue;
|
|
211
|
+
// (+N) 追加在截断之后,避免首个元素过长时把条数挤没
|
|
212
|
+
const more = found.more > 0 ? ` (+${found.more})` : "";
|
|
213
|
+
return { main: `${title} ${clip(found.text)}${more}`, detail: "" };
|
|
214
|
+
}
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
|
|
137
218
|
/**
|
|
138
219
|
* 单工具调用摘要({ main, detail })。
|
|
139
220
|
*
|
|
140
|
-
*
|
|
141
|
-
* 不改动任何现有渲染字符串。
|
|
221
|
+
* 两个变体共用同一份取值链,只有 read/agent/skill/task 等具名分支存在必要差异。
|
|
142
222
|
*/
|
|
143
223
|
export function toolCallSummary(
|
|
144
224
|
toolName: string,
|
|
@@ -216,58 +296,24 @@ export function toolCallSummary(
|
|
|
216
296
|
detail,
|
|
217
297
|
};
|
|
218
298
|
}
|
|
219
|
-
if (variant === "grouping") {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const pattern = clip(args.pattern || "...");
|
|
223
|
-
return {
|
|
224
|
-
main: `Grep ${JSON.stringify(pattern)}${args.path ? ` in ${clip(args.path)}` : ""}`,
|
|
225
|
-
detail: "",
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
if (toolName === "find") {
|
|
229
|
-
const pattern = clip(args.pattern || "...");
|
|
230
|
-
return {
|
|
231
|
-
main: `Find ${JSON.stringify(pattern)}${args.path ? ` in ${clip(args.path)}` : ""}`,
|
|
232
|
-
detail: "",
|
|
233
|
-
};
|
|
234
|
-
}
|
|
299
|
+
if (variant === "grouping" && SHELL_TOOL_NAMES.has(name)) {
|
|
300
|
+
// 分组行缺 command 时保留占位,避免与单工具卡一样只剩标题
|
|
301
|
+
return { main: value("...", "command"), detail: "" };
|
|
235
302
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
args.command ??
|
|
243
|
-
args.query ??
|
|
244
|
-
args.question ??
|
|
245
|
-
args.pattern ??
|
|
246
|
-
args.url ??
|
|
247
|
-
args.name ??
|
|
248
|
-
args.tool_use_id ??
|
|
249
|
-
args.toolCallId ??
|
|
250
|
-
args.id ??
|
|
251
|
-
args.message;
|
|
252
|
-
return {
|
|
253
|
-
main:
|
|
254
|
-
preferred !== undefined && preferred !== null && typeof preferred !== "object"
|
|
255
|
-
? `${title} ${clip(preferred)}`
|
|
256
|
-
: title,
|
|
257
|
-
detail: "",
|
|
258
|
-
};
|
|
303
|
+
|
|
304
|
+
// 检索类:pattern 是正文,path 只是范围;两个变体同格式
|
|
305
|
+
if (typeof args.pattern === "string" || SEARCH_TOOL_NAMES.has(name)) {
|
|
306
|
+
const pattern = clip(args.pattern || "...");
|
|
307
|
+
const scope = typeof args.path === "string" && args.path ? ` in ${clip(args.path)}` : "";
|
|
308
|
+
return { main: `${title} ${JSON.stringify(pattern)}${scope}`, detail: "" };
|
|
259
309
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
return {
|
|
270
|
-
main: `${title}${preferred === undefined ? "" : ` ${clip(preferred)}`}`,
|
|
271
|
-
detail: "",
|
|
272
|
-
};
|
|
310
|
+
|
|
311
|
+
const generic = genericSummary(title, args);
|
|
312
|
+
if (generic) return generic;
|
|
313
|
+
|
|
314
|
+
const preferredPath = args.path ?? args.file_path;
|
|
315
|
+
if (typeof preferredPath === "string" && preferredPath) {
|
|
316
|
+
return pathSummary(title, preferredPath, opts.cwd);
|
|
317
|
+
}
|
|
318
|
+
return { main: title, detail: "" };
|
|
273
319
|
}
|
|
@@ -3,6 +3,7 @@ import { inspect } from "node:util";
|
|
|
3
3
|
import { config } from "../../config/config.ts";
|
|
4
4
|
import { showMoreHintText } from "./show-more-hint.ts";
|
|
5
5
|
import { TOOL_LOADING_INTERVAL_MS, toolLoadingIcon } from "../../utils/tool-loading-icon.ts";
|
|
6
|
+
import { getToolMouseTui } from "../mouse/scroll.ts";
|
|
6
7
|
import { sanitizeToolResultText } from "../../utils/tool-result-sanitize.ts";
|
|
7
8
|
|
|
8
9
|
const TOOL_VIEWPORT_WIDTH_RATIO = 0.8;
|
|
@@ -70,6 +71,7 @@ const activeAnimationContexts = new Set<any>();
|
|
|
70
71
|
let sharedAnimationTimer: ReturnType<typeof setTimeout> | null = null;
|
|
71
72
|
|
|
72
73
|
function clearAnimation(context: any) {
|
|
74
|
+
if (context?.state) context.state.ccstyleAnimationLight = false;
|
|
73
75
|
if (!context?.state?.ccstyleAnimationScheduled) return;
|
|
74
76
|
context.state.ccstyleAnimationScheduled = false;
|
|
75
77
|
activeAnimationContexts.delete(context);
|
|
@@ -82,6 +84,7 @@ function clearAnimation(context: any) {
|
|
|
82
84
|
export function clearAllAnimations() {
|
|
83
85
|
for (const ctx of activeAnimationContexts) {
|
|
84
86
|
ctx.state.ccstyleAnimationScheduled = false;
|
|
87
|
+
ctx.state.ccstyleAnimationLight = false;
|
|
85
88
|
}
|
|
86
89
|
activeAnimationContexts.clear();
|
|
87
90
|
if (sharedAnimationTimer) {
|
|
@@ -90,8 +93,15 @@ export function clearAllAnimations() {
|
|
|
90
93
|
}
|
|
91
94
|
}
|
|
92
95
|
|
|
93
|
-
export function scheduleAnimation(
|
|
96
|
+
export function scheduleAnimation(
|
|
97
|
+
context: any,
|
|
98
|
+
options: { light?: boolean; intervalMs?: number } = {},
|
|
99
|
+
) {
|
|
94
100
|
const state = (context.state ??= {});
|
|
101
|
+
// light:调用方自己在 render() 内重取 loading 帧,定时器只需请求重绘;
|
|
102
|
+
// 否则定时器走 context.invalidate()(compact 摘要等靠它重建)。
|
|
103
|
+
// 每次调用都按本次 options 写入,避免上次 light 残留后非 light 调用仍跳过 invalidate。
|
|
104
|
+
state.ccstyleAnimationLight = Boolean(options.light);
|
|
95
105
|
if (state.ccstyleAnimationScheduled) return;
|
|
96
106
|
state.ccstyleAnimationScheduled = true;
|
|
97
107
|
activeAnimationContexts.add(context);
|
|
@@ -100,11 +110,16 @@ export function scheduleAnimation(context: any, intervalMs = TOOL_LOADING_INTERV
|
|
|
100
110
|
sharedAnimationTimer = null;
|
|
101
111
|
const contexts = Array.from(activeAnimationContexts);
|
|
102
112
|
activeAnimationContexts.clear();
|
|
113
|
+
const tui = getToolMouseTui();
|
|
114
|
+
const canRequestRender = typeof tui?.requestRender === "function";
|
|
115
|
+
let lightFrames = false;
|
|
103
116
|
for (const ctx of contexts) {
|
|
104
117
|
ctx.state.ccstyleAnimationScheduled = false;
|
|
105
|
-
ctx.
|
|
118
|
+
if (ctx.state.ccstyleAnimationLight && canRequestRender) lightFrames = true;
|
|
119
|
+
else ctx.invalidate?.();
|
|
106
120
|
}
|
|
107
|
-
|
|
121
|
+
if (lightFrames) tui.requestRender();
|
|
122
|
+
}, options.intervalMs ?? TOOL_LOADING_INTERVAL_MS);
|
|
108
123
|
}
|
|
109
124
|
}
|
|
110
125
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cc-extensions",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Claude Code-style TUI output with some personal touches and handy utilities.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"pi-package",
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"files": [
|
|
22
22
|
"extensions",
|
|
23
23
|
"assets/readme",
|
|
24
|
-
"assets/video",
|
|
25
24
|
"themes",
|
|
26
25
|
"README.md",
|
|
27
26
|
"README.en.md"
|
|
@@ -34,7 +33,7 @@
|
|
|
34
33
|
"./themes/cc-dark.json",
|
|
35
34
|
"./themes/cc-light.json"
|
|
36
35
|
],
|
|
37
|
-
"image": "https://raw.githubusercontent.com/minuque/pi-cc-extensions/main/assets/readme/preview.
|
|
36
|
+
"image": "https://raw.githubusercontent.com/minuque/pi-cc-extensions/main/assets/readme/preview.webp",
|
|
38
37
|
"video": "https://github.com/user-attachments/assets/6c858000-fdad-43f9-957f-4d0278648498"
|
|
39
38
|
},
|
|
40
39
|
"scripts": {
|