pi-cc-extensions 0.9.4 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +4 -4
- package/README.md +4 -4
- package/extensions/config/config.ts +2 -3
- package/extensions/config/panel.ts +2 -3
- package/extensions/feature/compact-thinking.ts +1 -1
- package/extensions/feature/reference/index.ts +23 -36
- package/extensions/feature/reference/session.ts +95 -1
- package/extensions/feature/reference/subagent.ts +60 -21
- package/extensions/renderer/compact-mode.ts +174 -62
- package/extensions/renderer/default-mode.ts +36 -39
- package/extensions/renderer/index.ts +5 -3
- package/extensions/renderer/mouse/interaction.ts +251 -80
- package/extensions/renderer/mouse/layout.ts +14 -5
- package/extensions/renderer/tool/diff/diff-component.ts +19 -2
- package/extensions/renderer/tool/diff/diff-edit-render.ts +18 -9
- package/extensions/renderer/tool/diff/diff-limits.ts +22 -21
- package/extensions/renderer/tool/diff/diff-write-render.ts +28 -18
- package/extensions/renderer/tool/grouping.ts +21 -5
- package/extensions/renderer/tool/mcp-title.ts +39 -0
- package/extensions/renderer/tool/names.ts +103 -1
- package/extensions/renderer/tool/result.ts +6 -1
- package/extensions/renderer/tool/show-more-hint.ts +10 -0
- package/package.json +2 -1
|
@@ -31,18 +31,16 @@ function renderDiffSpacerLine(width: number): string {
|
|
|
31
31
|
return safeWidth > 0 ? " ".repeat(safeWidth) : "";
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
): number {
|
|
39
|
-
|
|
40
|
-
? maxExpandedLines
|
|
41
|
-
: DEFAULT_TOOL_DISPLAY_CONFIG.expandedPreviewMaxLines;
|
|
34
|
+
/**
|
|
35
|
+
* Body lines to render before the remainder hint. Expanded diffs are never capped:
|
|
36
|
+
* clicking `click to show more` expands the card to the whole diff.
|
|
37
|
+
*/
|
|
38
|
+
export function resolveDiffDisplayLimit(expanded: boolean, maxCollapsedLines: number): number {
|
|
39
|
+
if (expanded) return Number.POSITIVE_INFINITY;
|
|
42
40
|
const collapsedLimit = Number.isFinite(maxCollapsedLines)
|
|
43
41
|
? maxCollapsedLines
|
|
44
42
|
: DEFAULT_TOOL_DISPLAY_CONFIG.editDiffCollapsedLines;
|
|
45
|
-
return
|
|
43
|
+
return Math.max(1, collapsedLimit);
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
export function resolveWriteCollapsedLimit(
|
|
@@ -99,7 +97,6 @@ export function applyLineLimit(
|
|
|
99
97
|
width: number,
|
|
100
98
|
expanded: boolean,
|
|
101
99
|
maxCollapsedLines: number,
|
|
102
|
-
maxExpandedLines: number,
|
|
103
100
|
totalHunks: number,
|
|
104
101
|
theme: DiffTheme,
|
|
105
102
|
/**
|
|
@@ -108,17 +105,17 @@ export function applyLineLimit(
|
|
|
108
105
|
*/
|
|
109
106
|
unprocessedLogicalRows = 0,
|
|
110
107
|
hovered = false,
|
|
111
|
-
): string[] {
|
|
112
|
-
const limit = resolveDiffDisplayLimit(expanded, maxCollapsedLines
|
|
108
|
+
): { lines: string[]; hintLine?: string } {
|
|
109
|
+
const limit = resolveDiffDisplayLimit(expanded, maxCollapsedLines);
|
|
113
110
|
const safeUnprocessed = Math.max(0, unprocessedLogicalRows);
|
|
114
111
|
if (limit === 0 || (rows.length <= limit && safeUnprocessed === 0)) {
|
|
115
|
-
return rows.map((row) => clampDiffLineToWidth(row.text, width));
|
|
112
|
+
return { lines: rows.map((row) => clampDiffLineToWidth(row.text, width)) };
|
|
116
113
|
}
|
|
117
114
|
|
|
118
115
|
const shown = rows.length <= limit ? rows : rows.slice(0, limit);
|
|
119
116
|
const remaining = Math.max(0, rows.length - shown.length) + safeUnprocessed;
|
|
120
117
|
if (remaining === 0) {
|
|
121
|
-
return shown.map((row) => clampDiffLineToWidth(row.text, width));
|
|
118
|
+
return { lines: shown.map((row) => clampDiffLineToWidth(row.text, width)) };
|
|
122
119
|
}
|
|
123
120
|
const visibleHunks = new Set(
|
|
124
121
|
shown
|
|
@@ -137,15 +134,19 @@ export function applyLineLimit(
|
|
|
137
134
|
|
|
138
135
|
const clickLabel = showMoreHintText();
|
|
139
136
|
const clickIndex = hintText.lastIndexOf(clickLabel);
|
|
137
|
+
// 展开态不会再截断,剩下的分支必是折叠态提示。
|
|
140
138
|
const styledHint =
|
|
141
|
-
hovered &&
|
|
139
|
+
hovered && clickIndex >= 0
|
|
142
140
|
? theme.fg("muted", hintText.slice(0, clickIndex)) +
|
|
143
141
|
theme.fg("text", clickLabel) +
|
|
144
142
|
theme.fg("muted", hintText.slice(clickIndex + clickLabel.length))
|
|
145
|
-
: theme.fg(
|
|
146
|
-
return
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
143
|
+
: theme.fg("muted", hintText);
|
|
144
|
+
return {
|
|
145
|
+
lines: [
|
|
146
|
+
...shown.map((row) => clampDiffLineToWidth(row.text, width)),
|
|
147
|
+
renderDiffSpacerLine(width),
|
|
148
|
+
clampDiffLineToWidth(styledHint, width),
|
|
149
|
+
],
|
|
150
|
+
hintLine: hintText,
|
|
151
|
+
};
|
|
151
152
|
}
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
displayConfigCacheKey,
|
|
47
47
|
resolveDiffIndicatorMode,
|
|
48
48
|
resolveLiveDisplayConfig,
|
|
49
|
+
normalizeCollapsedHintLine,
|
|
49
50
|
type DiffRenderOptions,
|
|
50
51
|
type DisplayConfigInput,
|
|
51
52
|
} from "./diff-component.ts";
|
|
@@ -407,6 +408,11 @@ export function renderWriteDiffResult(
|
|
|
407
408
|
|
|
408
409
|
return {
|
|
409
410
|
[RICH_DIFF_COMPONENT]: true,
|
|
411
|
+
/** 鼠标层只在真正的 remainder 行上提供展开入口。 */
|
|
412
|
+
isCollapsedHintLine(plainLine: string): boolean {
|
|
413
|
+
const hint = cache.getHintLine();
|
|
414
|
+
return hint !== undefined && normalizeCollapsedHintLine(plainLine) === hint;
|
|
415
|
+
},
|
|
410
416
|
render(width: number): string[] {
|
|
411
417
|
// Live config: panel can change indicator/wrap/limits after this component is created.
|
|
412
418
|
const live = resolveLiveDisplayConfig(config);
|
|
@@ -433,22 +439,21 @@ export function renderWriteDiffResult(
|
|
|
433
439
|
const writeCollapsedLimit = resolveWriteCollapsedLimit(live);
|
|
434
440
|
const statsOnlyCollapsed = !options.expanded && writeCollapsedLimit === 0;
|
|
435
441
|
if (statsOnlyCollapsed) {
|
|
442
|
+
const hintLines = renderWriteCollapsedHintLine(
|
|
443
|
+
options.fileExistedBeforeWrite === true,
|
|
444
|
+
safeWidth,
|
|
445
|
+
theme,
|
|
446
|
+
hovered,
|
|
447
|
+
options.headerLabel,
|
|
448
|
+
);
|
|
436
449
|
return cache.set(
|
|
437
450
|
safeWidth,
|
|
438
451
|
options.expanded,
|
|
439
452
|
mode,
|
|
440
453
|
configKey,
|
|
441
454
|
hovered,
|
|
442
|
-
clampDiffLinesToWidth(
|
|
443
|
-
|
|
444
|
-
options.fileExistedBeforeWrite === true,
|
|
445
|
-
safeWidth,
|
|
446
|
-
theme,
|
|
447
|
-
hovered,
|
|
448
|
-
options.headerLabel,
|
|
449
|
-
),
|
|
450
|
-
safeWidth,
|
|
451
|
-
),
|
|
455
|
+
clampDiffLinesToWidth(hintLines, safeWidth),
|
|
456
|
+
normalizeCollapsedHintLine(hintLines[0]),
|
|
452
457
|
);
|
|
453
458
|
}
|
|
454
459
|
|
|
@@ -500,11 +505,7 @@ export function renderWriteDiffResult(
|
|
|
500
505
|
}
|
|
501
506
|
|
|
502
507
|
const data = getDetailedData();
|
|
503
|
-
const displayLimit = resolveDiffDisplayLimit(
|
|
504
|
-
options.expanded,
|
|
505
|
-
writeCollapsedLimit,
|
|
506
|
-
live.expandedPreviewMaxLines,
|
|
507
|
-
);
|
|
508
|
+
const displayLimit = resolveDiffDisplayLimit(options.expanded, writeCollapsedLimit);
|
|
508
509
|
const processBudget = resolveDiffProcessBudget(displayLimit, wordWrap);
|
|
509
510
|
const entryBudget = takeEntriesForLineBudget(data.entries, processBudget);
|
|
510
511
|
const splitBudget = takeSplitRowsForBudget(data.splitRows, processBudget);
|
|
@@ -541,7 +542,6 @@ export function renderWriteDiffResult(
|
|
|
541
542
|
safeWidth,
|
|
542
543
|
options.expanded,
|
|
543
544
|
writeCollapsedLimit,
|
|
544
|
-
live.expandedPreviewMaxLines,
|
|
545
545
|
data.hunkCount,
|
|
546
546
|
theme,
|
|
547
547
|
unprocessedLogicalRows,
|
|
@@ -549,9 +549,19 @@ export function renderWriteDiffResult(
|
|
|
549
549
|
);
|
|
550
550
|
const frame = renderDiffFrameLine(safeWidth, theme);
|
|
551
551
|
const renderedLines =
|
|
552
|
-
mode === "unified"
|
|
552
|
+
mode === "unified"
|
|
553
|
+
? [header, frame, ...bodyWithLimit.lines, frame]
|
|
554
|
+
: [header, ...bodyWithLimit.lines];
|
|
553
555
|
const finalLines = clampDiffLinesToWidth(renderedLines, safeWidth);
|
|
554
|
-
return cache.set(
|
|
556
|
+
return cache.set(
|
|
557
|
+
safeWidth,
|
|
558
|
+
options.expanded,
|
|
559
|
+
mode,
|
|
560
|
+
configKey,
|
|
561
|
+
hovered,
|
|
562
|
+
finalLines,
|
|
563
|
+
normalizeCollapsedHintLine(bodyWithLimit.hintLine),
|
|
564
|
+
);
|
|
555
565
|
},
|
|
556
566
|
invalidate: cache.invalidate,
|
|
557
567
|
} as Component;
|
|
@@ -8,12 +8,14 @@ import {
|
|
|
8
8
|
} from "@earendil-works/pi-tui";
|
|
9
9
|
import { TOOL_LOADING_INTERVAL_MS, toolLoadingIcon } from "../../utils/tool-loading-icon.ts";
|
|
10
10
|
import { getToolMouseTui } from "../mouse/scroll.ts";
|
|
11
|
-
import {
|
|
11
|
+
import { mcpToolTitle } from "./mcp-title.ts";
|
|
12
|
+
import { collapseHintText, isToolTuiFullscreen, showMoreHintText } from "./show-more-hint.ts";
|
|
12
13
|
import { stripAnsi, stripBackgroundAnsi, stripLeadingStatusIcon } from "../../utils/ansi-text.ts";
|
|
13
14
|
import { walkComponentTree } from "../../utils/component-tree.ts";
|
|
14
15
|
import {
|
|
15
16
|
fitToolCallSummary,
|
|
16
17
|
humanizeToolLabel,
|
|
18
|
+
renderToolSummary,
|
|
17
19
|
toolCallSummary,
|
|
18
20
|
type ToolCallSummary,
|
|
19
21
|
} from "./names.ts";
|
|
@@ -44,6 +46,19 @@ function toolName(tool: any): string {
|
|
|
44
46
|
return String(tool?.toolName ?? tool?.toolDefinition?.name ?? "tool");
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
/**
|
|
50
|
+
* 标题:MCP 工具用 adapter 暴露的真实工具名,其余回退工具名人性化。
|
|
51
|
+
*/
|
|
52
|
+
function toolTitle(tool: any): string {
|
|
53
|
+
const name = toolName(tool);
|
|
54
|
+
return (
|
|
55
|
+
mcpToolTitle({
|
|
56
|
+
toolName: name,
|
|
57
|
+
definition: tool?.toolDefinition ?? tool?.builtInToolDefinition,
|
|
58
|
+
}) ?? humanizeToolLabel(name)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
47
62
|
function isGroupable(value: unknown): boolean {
|
|
48
63
|
return value instanceof ToolExecutionComponent && !NON_GROUPABLE.has(toolName(value));
|
|
49
64
|
}
|
|
@@ -170,6 +185,7 @@ export function paddedBackgroundRow(
|
|
|
170
185
|
|
|
171
186
|
function toolSummary(tool: any): ToolCallSummary {
|
|
172
187
|
return toolCallSummary(toolName(tool), tool?.args ?? {}, {
|
|
188
|
+
title: toolTitle(tool),
|
|
173
189
|
variant: "grouping",
|
|
174
190
|
cwd: tool?.cwd,
|
|
175
191
|
});
|
|
@@ -347,8 +363,7 @@ export class ToolGroupComponent extends Container {
|
|
|
347
363
|
})
|
|
348
364
|
.join(` ${fg("dim", "•")} `);
|
|
349
365
|
const names = new Set(this.children.map(toolName));
|
|
350
|
-
const label =
|
|
351
|
-
names.size === 1 ? humanizeToolLabel(toolName(this.children[0])) : "Multiple Tools";
|
|
366
|
+
const label = names.size === 1 ? toolTitle(this.children[0]) : "Multiple Tools";
|
|
352
367
|
const overall: ToolStatus = counts.error ? "error" : counts.pending ? "pending" : "success";
|
|
353
368
|
if (
|
|
354
369
|
(this.children as any[]).some((tool) => tool?.executionStarted && status(tool) === "pending")
|
|
@@ -357,7 +372,8 @@ export class ToolGroupComponent extends Container {
|
|
|
357
372
|
const overallColor = overall === "pending" ? "accent" : overall;
|
|
358
373
|
const nameList = names.size > 1 ? ` ${fg("dim", `• ${toolNameList(this.children)}`)}` : "";
|
|
359
374
|
// 圆点保持 dim;hover 只高亮可点击文字。
|
|
360
|
-
const
|
|
375
|
+
const hintText = this._expanded ? collapseHintText() : showMoreHintText();
|
|
376
|
+
const hint = `${fg("dim", "•")} ${fg(this.hintHovered ? "text" : "dim", hintText)}`;
|
|
361
377
|
const lines = [
|
|
362
378
|
"",
|
|
363
379
|
truncateToWidth(
|
|
@@ -398,7 +414,7 @@ export class ToolGroupComponent extends Container {
|
|
|
398
414
|
const mainWidth = Math.max(0, width - visibleWidth(prefix) - visibleWidth(detail));
|
|
399
415
|
lines.push(
|
|
400
416
|
truncateToWidth(
|
|
401
|
-
`${prefix}${
|
|
417
|
+
`${prefix}${renderToolSummary(summary, mainWidth, fg)}${detail}`,
|
|
402
418
|
width,
|
|
403
419
|
"",
|
|
404
420
|
),
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { humanizeToolLabel } from "./names.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP 工具的识别与标题,单卡(default-mode)与分组行/分组头(grouping)共用。
|
|
5
|
+
*
|
|
6
|
+
* - 判定:工具名带 mcp 片段(`mcp` 网关、`mcpScript`、`mcp__<server>`),或 adapter 注册时
|
|
7
|
+
* 给的 label 以 `MCP:` 开头。默认/none/short 前缀模式下直连工具的名字里没有 mcp 片段
|
|
8
|
+
* (`github_search_code`、`search_code`),只能靠 label 判定。
|
|
9
|
+
* - 标题:具体工具用 adapter 暴露的真实工具名,不做二次格式化;只有 `mcp` / `mcpScript`
|
|
10
|
+
* 这两个入口没有具体工具名,跟普通工具一样人性化(MCP / MCP Script)。
|
|
11
|
+
* - 参数摘要与其它工具共用 toolCallSummary 的字段链,网关额外把 `args.tool` 提到标题后。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** 单次 MCP 调用的识别信息。 */
|
|
15
|
+
export type McpToolCall = {
|
|
16
|
+
toolName: string;
|
|
17
|
+
/** 工具定义;默认/none/short 前缀模式要靠 label 判定,会话重放时可能缺失。 */
|
|
18
|
+
definition?: any;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const GATEWAY_TOOL_NAME = "mcp";
|
|
22
|
+
const SCRIPT_TOOL_NAME = "mcpScript";
|
|
23
|
+
|
|
24
|
+
export function isMcpToolDefinition(definition: any, toolName: string): boolean {
|
|
25
|
+
const label = typeof definition?.label === "string" ? definition.label.trim() : "";
|
|
26
|
+
if (/^MCP(?::|$)/i.test(label)) return true;
|
|
27
|
+
if (toolName === GATEWAY_TOOL_NAME || toolName === SCRIPT_TOOL_NAME) return true;
|
|
28
|
+
return /^mcp[_:-]|[_:-]mcp[_:-]/i.test(toolName);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** 标题:MCP 工具返回标题(入口走人性化,其余用真实工具名);非 MCP 返回 undefined。 */
|
|
32
|
+
export function mcpToolTitle(call: McpToolCall): string | undefined {
|
|
33
|
+
const { toolName } = call;
|
|
34
|
+
if (!isMcpToolDefinition(call.definition, toolName)) return undefined;
|
|
35
|
+
if (toolName === GATEWAY_TOOL_NAME || toolName === SCRIPT_TOOL_NAME) {
|
|
36
|
+
return humanizeToolLabel(toolName);
|
|
37
|
+
}
|
|
38
|
+
return toolName;
|
|
39
|
+
}
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import { posix, win32 } from "node:path";
|
|
2
|
+
import type { ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
2
3
|
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
3
4
|
import { config } from "../../config/config.ts";
|
|
4
5
|
import { oneLine } from "../../utils/format.ts";
|
|
6
|
+
import { headTruncateToWidth } from "./result.ts";
|
|
5
7
|
|
|
6
8
|
function clip(value: unknown): string {
|
|
7
9
|
return oneLine(value, config.inputClip);
|
|
8
10
|
}
|
|
9
11
|
|
|
12
|
+
/** 载荷(入参 JSON / 脚本代码)至少留出这么多宽度才显示,否则整段省略(避免只剩一个孤零零的 " {…")。 */
|
|
13
|
+
const MIN_PAYLOAD_WIDTH = 10;
|
|
14
|
+
|
|
10
15
|
/** 品牌大小写固定写法;humanize 推导不出来的工具名写在这里。 */
|
|
11
16
|
const TOOL_LABEL_OVERRIDES: Readonly<Record<string, string>> = {
|
|
12
17
|
powershell: "PowerShell",
|
|
18
|
+
// MCP 入口两个没有具体工具名,跟普通工具一样人性化;缩写固定大写
|
|
19
|
+
mcp: "MCP",
|
|
20
|
+
mcpscript: "MCP Script",
|
|
13
21
|
};
|
|
14
22
|
|
|
15
23
|
/**
|
|
@@ -49,6 +57,8 @@ export type ToolCallSummary = {
|
|
|
49
57
|
detail: string;
|
|
50
58
|
/** 路径摘要保留结构,供最终渲染按实际宽度优先保留文件名。 */
|
|
51
59
|
path?: { prefix: string; value: string };
|
|
60
|
+
/** 标题之外的原始载荷(完整入参 JSON、脚本代码…),已压成单行,渲染时用 dim 接在标题后。 */
|
|
61
|
+
payload?: string;
|
|
52
62
|
};
|
|
53
63
|
|
|
54
64
|
function pathApi(value: string) {
|
|
@@ -229,6 +239,18 @@ export function toolCallSummary(
|
|
|
229
239
|
const variant = opts.variant ?? "default";
|
|
230
240
|
if (!args || typeof args !== "object") return { main: title, detail: "" };
|
|
231
241
|
const name = toolName.toLowerCase();
|
|
242
|
+
|
|
243
|
+
// MCP 入口沿用 mcp-adapter 自己的标题风格(adapter 的 formatMcpProxyToolCallLines):
|
|
244
|
+
// `MCP <动作> <目标>` / `MCP Script <代码>`,比把整包参数摊成 JSON 好读
|
|
245
|
+
if (name === "mcp") {
|
|
246
|
+
const gateway = mcpGatewaySummary(title, args);
|
|
247
|
+
if (gateway) return gateway;
|
|
248
|
+
}
|
|
249
|
+
if (name === "mcpscript") {
|
|
250
|
+
const code = typeof args.code === "string" && args.code ? clip(args.code) : "";
|
|
251
|
+
return code ? { main: title, detail: "", payload: code } : { main: title, detail: "" };
|
|
252
|
+
}
|
|
253
|
+
|
|
232
254
|
const value = (fallback: string, ...keys: string[]) => {
|
|
233
255
|
const found = keys.map((key) => args[key]).find((item) => typeof item === "string" && item);
|
|
234
256
|
return `${title} ${clip(found || fallback)}`;
|
|
@@ -315,5 +337,85 @@ export function toolCallSummary(
|
|
|
315
337
|
if (typeof preferredPath === "string" && preferredPath) {
|
|
316
338
|
return pathSummary(title, preferredPath, opts.cwd);
|
|
317
339
|
}
|
|
318
|
-
|
|
340
|
+
|
|
341
|
+
// 字段链认不出的参数(命名空间代理的 tool+args、第三方工具的自定义键…)展开完整入参 JSON,
|
|
342
|
+
// 而不是只剩一个标题;网关与脚本的专属形状在上面处理
|
|
343
|
+
const payload = jsonArgs(args);
|
|
344
|
+
return payload ? { main: title, detail: "", payload } : { main: title, detail: "" };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* MCP 网关注解 `mcp <动作> <目标>`:动作与目标照搬 mcp-adapter 的 formatMcpProxyToolCallLines,
|
|
349
|
+
* 人眼读起来比参数 JSON 快;`instructions` 是 adapter 自己漏掉的一档(它那里会落到 mcp status)。
|
|
350
|
+
* 认不出的入参返回 undefined,交给通用 JSON 回退,不猜成 status。
|
|
351
|
+
*/
|
|
352
|
+
function mcpGatewaySummary(title: string, args: any): ToolCallSummary | undefined {
|
|
353
|
+
const text = (value: unknown) => (typeof value === "string" && value ? value : "");
|
|
354
|
+
const tool = text(args.tool);
|
|
355
|
+
const connect = text(args.connect);
|
|
356
|
+
const describe = text(args.describe);
|
|
357
|
+
const instructions = text(args.instructions);
|
|
358
|
+
const search = text(args.search);
|
|
359
|
+
const server = text(args.server);
|
|
360
|
+
const action = text(args.action);
|
|
361
|
+
const scoped = (target: string) => (server ? `${target} @ ${server}` : target);
|
|
362
|
+
|
|
363
|
+
if (action === "ui-messages") return { main: `${title} ${action}`, detail: "" };
|
|
364
|
+
if (tool) {
|
|
365
|
+
const main = `${title} call ${scoped(tool)}`;
|
|
366
|
+
const payload = innerArgsPayload(args.args);
|
|
367
|
+
return payload ? { main, detail: "", payload } : { main, detail: "" };
|
|
368
|
+
}
|
|
369
|
+
if (connect) return { main: `${title} connect ${connect}`, detail: "" };
|
|
370
|
+
if (describe) return { main: `${title} describe ${scoped(describe)}`, detail: "" };
|
|
371
|
+
if (instructions) return { main: `${title} instructions ${instructions}`, detail: "" };
|
|
372
|
+
if (search) {
|
|
373
|
+
const flags = [
|
|
374
|
+
args.regex === true ? "regex" : "",
|
|
375
|
+
args.includeSchemas === false ? "schemas hidden" : "",
|
|
376
|
+
].filter(Boolean);
|
|
377
|
+
return {
|
|
378
|
+
main: `${title} search ${scoped(search)}`,
|
|
379
|
+
detail: flags.length ? ` (${flags.join(", ")})` : "",
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
if (server) return { main: `${title} list ${server}`, detail: "" };
|
|
383
|
+
if (action) return { main: `${title} ${action}`, detail: "" };
|
|
384
|
+
if (Object.keys(args).length === 0) return { main: `${title} status`, detail: "" };
|
|
385
|
+
return undefined;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** 网关 call 的内层工具入参:对象转单行 JSON;网关允许传 JSON 字符串,就原样取用不再转义。 */
|
|
389
|
+
function innerArgsPayload(value: unknown): string {
|
|
390
|
+
if (typeof value === "string") return value ? clip(value) : "";
|
|
391
|
+
if (!value || typeof value !== "object") return "";
|
|
392
|
+
return jsonArgs(value);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** 入参序列化成单行 JSON;空对象或不可序列化时返回 ""。 */
|
|
396
|
+
function jsonArgs(args: any): string {
|
|
397
|
+
try {
|
|
398
|
+
const json = JSON.stringify(args);
|
|
399
|
+
return json && json !== "{}" ? clip(json) : "";
|
|
400
|
+
} catch {
|
|
401
|
+
return "";
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* 标题行内容:标题用调用方的 toolTitle 色,原始载荷(入参 JSON / 脚本代码)单独用 dim。
|
|
407
|
+
* 标题优先占宽(它是身份),剩下的宽度给载荷;载荷超宽时尾部省略,连
|
|
408
|
+
* MIN_PAYLOAD_WIDTH 都放不下时整段省略。
|
|
409
|
+
*/
|
|
410
|
+
export function renderToolSummary(
|
|
411
|
+
summary: ToolCallSummary,
|
|
412
|
+
width: number,
|
|
413
|
+
fg: (color: ThemeColor, text: string) => string,
|
|
414
|
+
): string {
|
|
415
|
+
const title = fitToolCallSummary(summary, width);
|
|
416
|
+
if (!summary.payload) return fg("toolTitle", title);
|
|
417
|
+
const room = width - visibleWidth(title);
|
|
418
|
+
return room >= MIN_PAYLOAD_WIDTH
|
|
419
|
+
? `${fg("toolTitle", title)}${fg("dim", headTruncateToWidth(` ${summary.payload}`, room))}`
|
|
420
|
+
: fg("toolTitle", title);
|
|
319
421
|
}
|
|
@@ -14,7 +14,7 @@ export function toolViewportWidth(width: number): number {
|
|
|
14
14
|
|
|
15
15
|
/** 与默认工具结果相同的一级缩进包装。子组件只扣始终加上的 1 列;↳ 行多出的 2 列由 truncateToWidth 吃掉。 */
|
|
16
16
|
export function insetComponent(component: any): any {
|
|
17
|
-
|
|
17
|
+
const wrapped: Record<string, unknown> = {
|
|
18
18
|
render: (width: number) =>
|
|
19
19
|
component.render(Math.max(1, width - 1)).map((line: string) => {
|
|
20
20
|
const nestedMarker = line.replace(/^((?:\x1b\[[0-?]*[ -/]*[@-~])*)↳/, "$1 ↳");
|
|
@@ -22,6 +22,11 @@ export function insetComponent(component: any): any {
|
|
|
22
22
|
}),
|
|
23
23
|
invalidate: () => component.invalidate?.(),
|
|
24
24
|
};
|
|
25
|
+
// 内层 diff 声明的 remainder 行:鼠标层只能看到这个包装组件。
|
|
26
|
+
if (typeof component.isCollapsedHintLine === "function") {
|
|
27
|
+
wrapped.isCollapsedHintLine = (line: string) => component.isCollapsedHintLine(line);
|
|
28
|
+
}
|
|
29
|
+
return wrapped;
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
function rawTextFromResult(result: any): string {
|
|
@@ -36,3 +36,13 @@ export function expandShortcutText(): string {
|
|
|
36
36
|
export function showMoreHintText(): string {
|
|
37
37
|
return isToolTuiFullscreen() ? "click to show more" : `${expandShortcutText()} to show more`;
|
|
38
38
|
}
|
|
39
|
+
|
|
40
|
+
/** 展开卡的收起提示,与折叠态 showMoreHintText 同一位置对称。 */
|
|
41
|
+
export function collapseHintText(): string {
|
|
42
|
+
return isToolTuiFullscreen() ? "↑ Collapse" : `${expandShortcutText()} to collapse`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** 该行是否带展开态收起提示(用于展开卡的 hint hover 命中)。 */
|
|
46
|
+
export function isCollapseHintLine(plainLine: string): boolean {
|
|
47
|
+
return plainLine.includes(collapseHintText());
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cc-extensions",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.6",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Claude Code-style TUI output with some personal touches and handy utilities.",
|
|
6
6
|
"type": "module",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"test": "node --test tests/**/*.test.ts",
|
|
41
|
+
"coverage": "node --test --experimental-test-coverage --test-coverage-include=extensions/**/*.ts tests/**/*.test.ts",
|
|
41
42
|
"typecheck": "tsc --noEmit",
|
|
42
43
|
"format": "biome format --write .",
|
|
43
44
|
"format:check": "biome format .",
|