pi-cc-extensions 0.8.59 → 0.8.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +2 -1
- package/README.md +2 -1
- package/extensions/config/config.ts +19 -0
- package/extensions/config/panel.ts +26 -3
- package/extensions/feature/compact-thinking.ts +211 -22
- package/extensions/feature/context.ts +150 -115
- package/extensions/renderer/compact-mode.ts +112 -43
- package/extensions/renderer/default-mode.ts +19 -2
- package/extensions/renderer/markdown-enhance.ts +40 -2
- package/extensions/renderer/mouse/hover.ts +14 -0
- package/extensions/renderer/mouse/interaction.ts +9 -1
- package/extensions/renderer/mouse/layout.ts +6 -1
- package/extensions/renderer/tool/diff/diff-component.ts +1 -0
- package/extensions/renderer/tool/diff/diff-limits.ts +15 -1
- package/extensions/renderer/tool/diff/diff-write-render.ts +62 -2
- package/extensions/renderer/tool/grouping.ts +58 -1
- package/package.json +1 -1
|
@@ -13,10 +13,10 @@ import { padLine } from "../utils/format.ts";
|
|
|
13
13
|
export type ContextPart = {
|
|
14
14
|
label: string;
|
|
15
15
|
tokens: number;
|
|
16
|
-
color: "accent" | "success" | "warning" | "muted" | "dim";
|
|
16
|
+
color: "accent" | "success" | "warning" | "customMessageLabel" | "muted" | "dim";
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
type PreviewKey = "systemPrompt" | "tools" | "
|
|
19
|
+
type PreviewKey = "systemPrompt" | "tools" | "toolResults" | "contextFiles";
|
|
20
20
|
|
|
21
21
|
type ContextPreview = {
|
|
22
22
|
key: PreviewKey;
|
|
@@ -259,20 +259,30 @@ const tokenEstimate = (value: unknown): number => {
|
|
|
259
259
|
return Math.max(0, Math.ceil(text.length / 4));
|
|
260
260
|
};
|
|
261
261
|
|
|
262
|
-
export function
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
262
|
+
export function capParts(parts: ContextPart[], target: number, fixedPrefix = 0): ContextPart[] {
|
|
263
|
+
const fixed = parts.slice(0, fixedPrefix);
|
|
264
|
+
const variable = parts.slice(fixedPrefix);
|
|
265
|
+
const fixedTokens = fixed.reduce((sum, part) => sum + part.tokens, 0);
|
|
266
|
+
const variableTarget = Math.max(0, target - fixedTokens);
|
|
267
|
+
const estimated = variable.reduce((sum, part) => sum + part.tokens, 0);
|
|
268
|
+
if (estimated <= variableTarget || estimated === 0) return parts;
|
|
269
|
+
if (variableTarget === 0) {
|
|
270
|
+
return [...fixed, ...variable.map((part) => ({ ...part, tokens: 0 }))];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
let previous = 0;
|
|
274
|
+
let cumulative = 0;
|
|
275
|
+
const capped = variable.map((part, index) => {
|
|
276
|
+
cumulative += part.tokens;
|
|
277
|
+
const next =
|
|
278
|
+
index === variable.length - 1
|
|
279
|
+
? variableTarget
|
|
280
|
+
: Math.round((cumulative / estimated) * variableTarget);
|
|
281
|
+
const tokens = next - previous;
|
|
282
|
+
previous = next;
|
|
283
|
+
return { ...part, tokens };
|
|
284
|
+
});
|
|
285
|
+
return [...fixed, ...capped];
|
|
276
286
|
}
|
|
277
287
|
|
|
278
288
|
export function formatTokens(tokens: number): string {
|
|
@@ -281,72 +291,122 @@ export function formatTokens(tokens: number): string {
|
|
|
281
291
|
return `${Math.round(tokens / 1_000)}k`;
|
|
282
292
|
}
|
|
283
293
|
|
|
294
|
+
export function resolveUsedTokens(
|
|
295
|
+
usage: { tokens: number | null; percent: number | null } | undefined,
|
|
296
|
+
estimated: number,
|
|
297
|
+
contextWindow: number,
|
|
298
|
+
): number {
|
|
299
|
+
const reported = usage?.tokens;
|
|
300
|
+
const fromPercent =
|
|
301
|
+
usage?.percent !== null && usage?.percent !== undefined && contextWindow > 0
|
|
302
|
+
? Math.round((usage.percent / 100) * contextWindow)
|
|
303
|
+
: undefined;
|
|
304
|
+
let resolved = reported ?? fromPercent ?? estimated;
|
|
305
|
+
if (reported !== null && reported !== undefined && fromPercent !== undefined) {
|
|
306
|
+
// 某些 Provider 会返回异常 totalTokens;百分比与底部状态栏不一致时优先采用百分比。
|
|
307
|
+
const tolerance = Math.max(32, Math.round(contextWindow * 0.001));
|
|
308
|
+
if (Math.abs(reported - fromPercent) > tolerance) resolved = fromPercent;
|
|
309
|
+
}
|
|
310
|
+
// tokens 与 percent 可能同时源自异常 usage;数量级明显偏小时回退到实际内容估算。
|
|
311
|
+
if (estimated > 0 && resolved < estimated * 0.25) return estimated;
|
|
312
|
+
return resolved;
|
|
313
|
+
}
|
|
314
|
+
|
|
284
315
|
type ContextBreakdown = {
|
|
285
316
|
parts: ContextPart[];
|
|
286
|
-
|
|
287
|
-
systemPrompt: string;
|
|
317
|
+
previews: Record<PreviewKey, string>;
|
|
288
318
|
};
|
|
289
319
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
320
|
+
function previewValue(value: unknown): string {
|
|
321
|
+
if (typeof value === "string") return value;
|
|
322
|
+
return `\`\`\`json\n${JSON.stringify(value, null, 2)}\n\`\`\``;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** 按真实请求的 systemPrompt、tools、messages 三部分同步组装计数与预览。 */
|
|
326
|
+
export function collectContextBreakdown(
|
|
327
|
+
ctx: ExtensionCommandContext,
|
|
328
|
+
allTools: ToolInfo[],
|
|
329
|
+
): ContextBreakdown {
|
|
295
330
|
const options = (ctx.getSystemPromptOptions?.() ?? {}) as BuildSystemPromptOptions;
|
|
296
331
|
const systemPrompt = typeof ctx.getSystemPrompt === "function" ? ctx.getSystemPrompt() : "";
|
|
297
|
-
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
let toolTokens = tokenEstimate(options.promptGuidelines);
|
|
315
|
-
for (const name of tools) {
|
|
316
|
-
toolTokens += tokenEstimate(name) + tokenEstimate(snippets?.[name]);
|
|
332
|
+
const selectedTools = new Set(options.selectedTools ?? ["read", "bash", "edit", "write"]);
|
|
333
|
+
const toolDefinitionPreview: string[] = [];
|
|
334
|
+
const toolResultPreview: string[] = [];
|
|
335
|
+
const contextPreview: string[] = [];
|
|
336
|
+
let toolDefinitionTokens = 0;
|
|
337
|
+
let toolResultTokens = 0;
|
|
338
|
+
let contextTokens = 0;
|
|
339
|
+
|
|
340
|
+
for (const tool of allTools) {
|
|
341
|
+
if (!selectedTools.has(tool.name)) continue;
|
|
342
|
+
const definition = {
|
|
343
|
+
name: tool.name,
|
|
344
|
+
description: tool.description,
|
|
345
|
+
parameters: tool.parameters,
|
|
346
|
+
};
|
|
347
|
+
toolDefinitionTokens += tokenEstimate(definition);
|
|
348
|
+
toolDefinitionPreview.push(`## Definition: ${tool.name}\n\n${previewValue(definition)}`);
|
|
317
349
|
}
|
|
318
350
|
|
|
319
|
-
let user = 0;
|
|
320
|
-
let assistant = 0;
|
|
321
|
-
let toolResults = 0;
|
|
322
|
-
let summaries = 0;
|
|
323
351
|
for (const entry of ctx.sessionManager.buildContextEntries()) {
|
|
324
352
|
if (entry.type === "message") {
|
|
325
|
-
const
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
|
|
353
|
+
const message = entry.message;
|
|
354
|
+
if (message.role === "assistant") {
|
|
355
|
+
for (const block of message.content) {
|
|
356
|
+
if (block.type === "toolCall") {
|
|
357
|
+
contextTokens += tokenEstimate(block.name) + tokenEstimate(block.arguments);
|
|
358
|
+
contextPreview.push(
|
|
359
|
+
`## Assistant tool call: ${block.name}\n\n${previewValue(block.arguments)}`,
|
|
360
|
+
);
|
|
361
|
+
} else if (block.type === "text") {
|
|
362
|
+
contextTokens += tokenEstimate(block.text);
|
|
363
|
+
contextPreview.push(`## Assistant\n\n${block.text}`);
|
|
364
|
+
} else if (block.type === "thinking") {
|
|
365
|
+
contextTokens += tokenEstimate(block.thinking);
|
|
366
|
+
contextPreview.push(`## Assistant thinking\n\n${block.thinking}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
} else if (message.role === "toolResult") {
|
|
370
|
+
toolResultTokens += estimateTokens(message);
|
|
371
|
+
toolResultPreview.push(
|
|
372
|
+
`## Result: ${message.toolName}\n\n${previewValue(message.content)}`,
|
|
373
|
+
);
|
|
374
|
+
} else if (message.role === "bashExecution") {
|
|
375
|
+
toolResultTokens += estimateTokens(message);
|
|
376
|
+
toolResultPreview.push(
|
|
377
|
+
`## Bash\n\nCommand:\n\n${previewValue(message.command)}\n\nOutput:\n\n${previewValue(message.output)}`,
|
|
378
|
+
);
|
|
379
|
+
} else if (message.role === "branchSummary" || message.role === "compactionSummary") {
|
|
380
|
+
contextTokens += estimateTokens(message);
|
|
381
|
+
contextPreview.push(`## ${message.role}\n\n${message.summary}`);
|
|
382
|
+
} else {
|
|
383
|
+
contextTokens += estimateTokens(message);
|
|
384
|
+
contextPreview.push(`## ${message.role}\n\n${previewValue(message.content)}`);
|
|
385
|
+
}
|
|
329
386
|
} else if (entry.type === "compaction" || entry.type === "branch_summary") {
|
|
330
|
-
|
|
387
|
+
contextTokens += tokenEstimate(entry.summary);
|
|
388
|
+
contextPreview.push(
|
|
389
|
+
`## ${entry.type === "compaction" ? "Compaction" : "Branch summary"}\n\n${entry.summary}`,
|
|
390
|
+
);
|
|
391
|
+
} else if (entry.type === "custom_message") {
|
|
392
|
+
contextTokens += tokenEstimate(entry.content);
|
|
393
|
+
contextPreview.push(`## Custom: ${entry.customType}\n\n${previewValue(entry.content)}`);
|
|
331
394
|
}
|
|
332
395
|
}
|
|
333
396
|
|
|
334
|
-
const systemTotal = tokenEstimate(systemPrompt);
|
|
335
|
-
const baseSystem = Math.max(0, systemTotal - contextFileTokens - skillTokens - toolTokens);
|
|
336
|
-
const parts: ContextPart[] = [
|
|
337
|
-
{ label: "System prompt", tokens: baseSystem, color: "accent" },
|
|
338
|
-
{ label: "Tools", tokens: toolTokens, color: "success" },
|
|
339
|
-
{ label: "Context files", tokens: contextFileTokens, color: "warning" },
|
|
340
|
-
{ label: "Skills", tokens: skillTokens, color: "warning" },
|
|
341
|
-
{ label: "User messages", tokens: user, color: "muted" },
|
|
342
|
-
{ label: "Assistant messages", tokens: assistant, color: "accent" },
|
|
343
|
-
{ label: "Tool results", tokens: toolResults, color: "dim" },
|
|
344
|
-
{ label: "Compaction summaries", tokens: summaries, color: "success" },
|
|
345
|
-
];
|
|
346
397
|
return {
|
|
347
|
-
parts:
|
|
348
|
-
|
|
349
|
-
|
|
398
|
+
parts: [
|
|
399
|
+
{ label: "System prompt", tokens: tokenEstimate(systemPrompt), color: "accent" },
|
|
400
|
+
{ label: "Tools", tokens: toolDefinitionTokens, color: "success" },
|
|
401
|
+
{ label: "Tool results", tokens: toolResultTokens, color: "customMessageLabel" },
|
|
402
|
+
{ label: "Context", tokens: contextTokens, color: "warning" },
|
|
403
|
+
] satisfies ContextPart[],
|
|
404
|
+
previews: {
|
|
405
|
+
systemPrompt: systemPrompt || "No system prompt.",
|
|
406
|
+
tools: toolDefinitionPreview.join("\n\n") || "No active tool definitions.",
|
|
407
|
+
toolResults: toolResultPreview.join("\n\n") || "No tool results in the current context.",
|
|
408
|
+
contextFiles: contextPreview.join("\n\n") || "No conversation context.",
|
|
409
|
+
},
|
|
350
410
|
};
|
|
351
411
|
}
|
|
352
412
|
|
|
@@ -356,11 +416,20 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
|
|
|
356
416
|
handler: async (_args, ctx) => {
|
|
357
417
|
const usage = ctx.getContextUsage();
|
|
358
418
|
const contextWindow = usage?.contextWindow ?? ctx.model?.contextWindow ?? 0;
|
|
359
|
-
const
|
|
360
|
-
const
|
|
361
|
-
const
|
|
419
|
+
const tools = pi.getAllTools();
|
|
420
|
+
const breakdown = collectContextBreakdown(ctx, tools);
|
|
421
|
+
const estimated = breakdown.parts.reduce((sum, part) => sum + part.tokens, 0);
|
|
422
|
+
const fixedTokens = breakdown.parts.slice(0, 2).reduce((sum, part) => sum + part.tokens, 0);
|
|
423
|
+
const used = Math.max(resolveUsedTokens(usage, estimated, contextWindow), fixedTokens);
|
|
424
|
+
const parts = capParts(breakdown.parts, used, 2);
|
|
425
|
+
const attributed = parts.reduce((sum, part) => sum + part.tokens, 0);
|
|
426
|
+
const other = Math.max(0, used - attributed);
|
|
362
427
|
const free = Math.max(0, contextWindow - used);
|
|
363
|
-
const allParts = [
|
|
428
|
+
const allParts = [
|
|
429
|
+
...parts,
|
|
430
|
+
{ label: "Other", tokens: other, color: "muted" as const },
|
|
431
|
+
{ label: "Free space", tokens: free, color: "dim" as const },
|
|
432
|
+
];
|
|
364
433
|
|
|
365
434
|
if (ctx.mode !== "tui") {
|
|
366
435
|
const lines = allParts.map((part) => `${part.label}: ${formatTokens(part.tokens)} tokens`);
|
|
@@ -368,64 +437,30 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
|
|
|
368
437
|
return;
|
|
369
438
|
}
|
|
370
439
|
|
|
371
|
-
const options = breakdown.options;
|
|
372
|
-
const toolByName = new Map<string, ToolInfo>(
|
|
373
|
-
pi.getAllTools().map((tool) => [tool.name, tool] as const),
|
|
374
|
-
);
|
|
375
|
-
const toolContent = (options.selectedTools ?? []).map((name) => {
|
|
376
|
-
const tool = toolByName.get(name);
|
|
377
|
-
const lines = [`## ${name}`];
|
|
378
|
-
if (tool?.description) lines.push(tool.description);
|
|
379
|
-
if (options.toolSnippets?.[name]) lines.push(`Prompt: ${options.toolSnippets[name]}`);
|
|
380
|
-
if (tool?.promptGuidelines?.length) {
|
|
381
|
-
lines.push("Guidelines:", ...tool.promptGuidelines.map((guideline) => `- ${guideline}`));
|
|
382
|
-
}
|
|
383
|
-
return lines.join("\n");
|
|
384
|
-
});
|
|
385
|
-
if (options.promptGuidelines?.length) {
|
|
386
|
-
toolContent.push(
|
|
387
|
-
`## Shared prompt guidelines\n${options.promptGuidelines.map((guideline) => `- ${guideline}`).join("\n")}`,
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
const contextFilesContent = (options.contextFiles ?? [])
|
|
391
|
-
.map((file) => `===== ${file.path} =====\n${file.content}`)
|
|
392
|
-
.join("\n\n");
|
|
393
|
-
const skillsContent = (options.skills ?? [])
|
|
394
|
-
.map((skill) =>
|
|
395
|
-
[
|
|
396
|
-
`## ${skill.name}`,
|
|
397
|
-
skill.description,
|
|
398
|
-
`Path: ${skill.filePath}`,
|
|
399
|
-
`Model invocation: ${skill.disableModelInvocation ? "disabled" : "enabled"}`,
|
|
400
|
-
]
|
|
401
|
-
.filter(Boolean)
|
|
402
|
-
.join("\n"),
|
|
403
|
-
)
|
|
404
|
-
.join("\n\n");
|
|
405
440
|
const rawPreviews: ContextPreview[] = [
|
|
406
441
|
{
|
|
407
442
|
key: "systemPrompt",
|
|
408
443
|
label: "System prompt",
|
|
409
444
|
title: "System Prompt",
|
|
410
|
-
content: breakdown.systemPrompt,
|
|
445
|
+
content: breakdown.previews.systemPrompt,
|
|
411
446
|
},
|
|
412
447
|
{
|
|
413
448
|
key: "tools",
|
|
414
449
|
label: "Tools",
|
|
415
450
|
title: "Tools",
|
|
416
|
-
content:
|
|
451
|
+
content: breakdown.previews.tools,
|
|
417
452
|
},
|
|
418
453
|
{
|
|
419
|
-
key: "
|
|
420
|
-
label: "
|
|
421
|
-
title: "
|
|
422
|
-
content:
|
|
454
|
+
key: "toolResults",
|
|
455
|
+
label: "Tool results",
|
|
456
|
+
title: "Tool Results",
|
|
457
|
+
content: breakdown.previews.toolResults,
|
|
423
458
|
},
|
|
424
459
|
{
|
|
425
|
-
key: "
|
|
426
|
-
label: "
|
|
427
|
-
title: "
|
|
428
|
-
content:
|
|
460
|
+
key: "contextFiles",
|
|
461
|
+
label: "Context",
|
|
462
|
+
title: "Context",
|
|
463
|
+
content: breakdown.previews.contextFiles,
|
|
429
464
|
},
|
|
430
465
|
];
|
|
431
466
|
const previews = rawPreviews.map((preview) => ({
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Compact mode:每条含 toolCall 的 assistant message 折叠为一条逐步累加的摘要行
|
|
3
|
-
* (`Ran for 8s, bash×2, read×2`),edit/write
|
|
3
|
+
* (`Ran for 8s, bash×2, read×2`),edit/write 独立标题行(`✓ write <path> (+25 -0)`),
|
|
4
4
|
* 普通工具折叠时不显示独立行;展开(Ctrl+O / fullscreen 点击)在单个工具卡中恢复
|
|
5
|
-
* compact-thinking/Pi 原生或专用 renderer。edit/write
|
|
5
|
+
* compact-thinking/Pi 原生或专用 renderer。edit/write 复用 mode=on 的 rich diff
|
|
6
|
+
* 与 `diffCollapsedLines` / `writeDiffCollapsedLines` / `expandedPreviewMaxLines`。
|
|
6
7
|
* Agent/Task 族:调用只进摘要计数,tool 卡始终折叠(避免 pending→完成高度闪动)。
|
|
7
8
|
* 底部 Agents/Tasks 面板由 pi-subagents/pi-tasks 独立 widget 负责,不经 tool 卡外置。
|
|
8
9
|
*
|
|
@@ -19,7 +20,7 @@ import {
|
|
|
19
20
|
ToolExecutionComponent,
|
|
20
21
|
type Theme,
|
|
21
22
|
} from "@earendil-works/pi-coding-agent";
|
|
22
|
-
import { truncateToWidth, visibleWidth, Box } from "@earendil-works/pi-tui";
|
|
23
|
+
import { truncateToWidth, visibleWidth, Box, Spacer } from "@earendil-works/pi-tui";
|
|
23
24
|
import { config, getToolDisplayConfig } from "../config/config.ts";
|
|
24
25
|
import { toolLoadingIcon } from "../utils/tool-loading-icon.ts";
|
|
25
26
|
import { sanitizeToolResultText } from "../utils/tool-result-sanitize.ts";
|
|
@@ -33,6 +34,7 @@ import {
|
|
|
33
34
|
} from "./tool/diff/diff-renderer.ts";
|
|
34
35
|
import { renderRichToolResult } from "./tool/diff/index.ts";
|
|
35
36
|
import type { WriteExecutionMetadataStore } from "./tool/diff/write-execution.ts";
|
|
37
|
+
import { isToolCallHovered } from "./mouse/hover.ts";
|
|
36
38
|
import { insetComponent, renderExpandedToolResult, scheduleAnimation } from "./tool/result.ts";
|
|
37
39
|
import { oneLine } from "../utils/format.ts";
|
|
38
40
|
import { paddedBackgroundRow } from "./tool/grouping.ts";
|
|
@@ -475,6 +477,7 @@ function compactEditWriteLine(
|
|
|
475
477
|
component: any,
|
|
476
478
|
width: number,
|
|
477
479
|
writeMetadata?: WriteExecutionMetadataStore,
|
|
480
|
+
options: { hint?: boolean } = {},
|
|
478
481
|
): string[] {
|
|
479
482
|
const theme = themeOf();
|
|
480
483
|
const name = String(component.toolName ?? "tool");
|
|
@@ -510,7 +513,8 @@ function compactEditWriteLine(
|
|
|
510
513
|
}
|
|
511
514
|
const iconPart = ` ${theme.fg(iconColor, icon)} `;
|
|
512
515
|
const namePart = theme.fg("toolTitle", name);
|
|
513
|
-
const hintText =
|
|
516
|
+
const hintText =
|
|
517
|
+
options.hint !== false && component.expanded !== true ? ` • ${showMoreHintText()}` : "";
|
|
514
518
|
const fixedWidth =
|
|
515
519
|
visibleWidth(iconPart) +
|
|
516
520
|
visibleWidth(namePart) +
|
|
@@ -522,34 +526,50 @@ function compactEditWriteLine(
|
|
|
522
526
|
return ["", truncateToWidth(line, width, "")];
|
|
523
527
|
}
|
|
524
528
|
|
|
525
|
-
/**
|
|
526
|
-
|
|
529
|
+
/**
|
|
530
|
+
* compact edit/write:标题行 + mode=on 同一套 rich diff。
|
|
531
|
+
* 折叠/展开都走 `renderRichToolResult`,limits 不另开一套。
|
|
532
|
+
*/
|
|
533
|
+
function compactEditWriteLines(
|
|
527
534
|
component: any,
|
|
528
535
|
width: number,
|
|
529
536
|
writeMetadata?: WriteExecutionMetadataStore,
|
|
530
537
|
): string[] {
|
|
531
538
|
const theme = themeOf();
|
|
532
539
|
const result = component.result;
|
|
540
|
+
const expanded = component.expanded === true;
|
|
533
541
|
const isError = result?.isError === true;
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
542
|
+
const isPending = !result || component.isPartial === true;
|
|
543
|
+
const candidate =
|
|
544
|
+
!isPending && writeMetadata
|
|
545
|
+
? renderRichToolResult(
|
|
546
|
+
String(component.toolName ?? ""),
|
|
547
|
+
result,
|
|
548
|
+
{
|
|
549
|
+
expanded,
|
|
550
|
+
isPartial: component.isPartial === true,
|
|
551
|
+
isError,
|
|
552
|
+
isHovered: () => isToolCallHovered(component.toolCallId),
|
|
553
|
+
},
|
|
554
|
+
theme,
|
|
555
|
+
component,
|
|
556
|
+
writeMetadata,
|
|
557
|
+
getToolDisplayConfig,
|
|
558
|
+
)
|
|
559
|
+
: undefined;
|
|
560
|
+
const hasRich = isRichDiffComponent(candidate);
|
|
561
|
+
if (hasRich) {
|
|
551
562
|
component.resultRendererComponent = candidate;
|
|
552
|
-
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const title = compactEditWriteLine(component, width, writeMetadata, { hint: !hasRich });
|
|
566
|
+
if (!hasRich && !expanded) {
|
|
567
|
+
return title;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
let detail: any;
|
|
571
|
+
if (hasRich) {
|
|
572
|
+
detail = expanded ? candidate : insetComponent(candidate as any);
|
|
553
573
|
} else {
|
|
554
574
|
const outputText = sanitizeToolResultText(
|
|
555
575
|
Array.isArray(result?.content)
|
|
@@ -571,10 +591,14 @@ function compactEditWriteExpandedLines(
|
|
|
571
591
|
component.resultRendererComponent = detail;
|
|
572
592
|
}
|
|
573
593
|
|
|
594
|
+
if (!expanded) {
|
|
595
|
+
return [...title, ...detail.render(width)];
|
|
596
|
+
}
|
|
597
|
+
|
|
574
598
|
const box = editWriteExpandedCard(theme);
|
|
575
599
|
box.addChild({
|
|
576
600
|
render(innerWidth: number): string[] {
|
|
577
|
-
return compactEditWriteLine(component, innerWidth, writeMetadata).slice(1);
|
|
601
|
+
return compactEditWriteLine(component, innerWidth, writeMetadata, { hint: false }).slice(1);
|
|
578
602
|
},
|
|
579
603
|
invalidate() {},
|
|
580
604
|
});
|
|
@@ -788,7 +812,8 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
788
812
|
return;
|
|
789
813
|
}
|
|
790
814
|
try {
|
|
791
|
-
|
|
815
|
+
// 非 force:保留 fullscreen 布局缓存和差分绘制。
|
|
816
|
+
uiRef?.requestRender?.();
|
|
792
817
|
} catch {
|
|
793
818
|
/* 无 UI */
|
|
794
819
|
}
|
|
@@ -864,18 +889,65 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
864
889
|
}
|
|
865
890
|
|
|
866
891
|
if (round.anchor.expanded === true) {
|
|
867
|
-
const
|
|
892
|
+
const toolsById = new Map<string, any>();
|
|
893
|
+
for (const tool of trackedToolComponents) {
|
|
894
|
+
if (typeof tool?.toolCallId === "string") toolsById.set(tool.toolCallId, tool);
|
|
895
|
+
}
|
|
896
|
+
const cardItems: Array<{ child?: any; tool?: any }> = [];
|
|
897
|
+
const placedToolIds = new Set<string>();
|
|
898
|
+
const takeAssistantRun = (kids: any[], cursor: { i: number }) => {
|
|
899
|
+
while (cursor.i < kids.length && kids[cursor.i] instanceof Spacer) {
|
|
900
|
+
cardItems.push({ child: kids[cursor.i++] });
|
|
901
|
+
}
|
|
902
|
+
if (cursor.i < kids.length) cardItems.push({ child: kids[cursor.i++] });
|
|
903
|
+
};
|
|
904
|
+
const placeTool = (id: string, fallbackName?: string) => {
|
|
905
|
+
const tool = toolsById.get(id);
|
|
906
|
+
if (!tool || EDIT_WRITE_TOOLS.has(String(tool.toolName ?? fallbackName ?? ""))) return;
|
|
907
|
+
if (placedToolIds.has(id)) return;
|
|
908
|
+
placedToolIds.add(id);
|
|
909
|
+
cardItems.push({ tool });
|
|
910
|
+
};
|
|
868
911
|
for (const [component, message] of round.messages) {
|
|
869
912
|
passThroughAssistant(component, message);
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
913
|
+
const kids = Array.isArray(component.contentContainer?.children)
|
|
914
|
+
? [...component.contentContainer.children]
|
|
915
|
+
: [];
|
|
873
916
|
component.contentContainer?.clear?.();
|
|
917
|
+
const cursor = { i: 0 };
|
|
918
|
+
let inThinkingRun = false;
|
|
919
|
+
for (const item of Array.isArray(message?.content) ? message.content : []) {
|
|
920
|
+
if (item?.type === "thinking") {
|
|
921
|
+
if (!String(item.thinking ?? "").trim()) continue;
|
|
922
|
+
if (!inThinkingRun) {
|
|
923
|
+
takeAssistantRun(kids, cursor);
|
|
924
|
+
inThinkingRun = true;
|
|
925
|
+
}
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
inThinkingRun = false;
|
|
929
|
+
if (item?.type === "text" && String(item.text ?? "").trim()) {
|
|
930
|
+
takeAssistantRun(kids, cursor);
|
|
931
|
+
continue;
|
|
932
|
+
}
|
|
933
|
+
if (item?.type === "toolCall" && typeof item.id === "string") {
|
|
934
|
+
placeTool(item.id, item.name);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
while (cursor.i < kids.length) cardItems.push({ child: kids[cursor.i++] });
|
|
938
|
+
}
|
|
939
|
+
for (const message of round.detachedMessages) {
|
|
940
|
+
for (const item of Array.isArray(message?.content) ? message.content : []) {
|
|
941
|
+
if (item?.type === "toolCall" && typeof item.id === "string") {
|
|
942
|
+
placeTool(item.id, item.name);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
874
945
|
}
|
|
875
946
|
const ids = roundToolCallIds(round);
|
|
876
947
|
for (const id of ids) {
|
|
877
948
|
round.suppressedToolIds.add(id);
|
|
878
949
|
expandedRoundToolIds.add(id);
|
|
950
|
+
if (!placedToolIds.has(id)) placeTool(id);
|
|
879
951
|
}
|
|
880
952
|
// Round 展开只打开外层卡片。普通工具保持折叠,避免长输出递归撑满屏幕。
|
|
881
953
|
for (const tool of trackedToolComponents) {
|
|
@@ -896,21 +968,20 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
896
968
|
render(width: number): string[] {
|
|
897
969
|
const theme = themeOf();
|
|
898
970
|
const box = expandedToolCard(theme);
|
|
899
|
-
for (const
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
971
|
+
for (const item of cardItems) {
|
|
972
|
+
if (item.child) box.addChild(item.child);
|
|
973
|
+
else if (item.tool) {
|
|
974
|
+
const tool = item.tool;
|
|
975
|
+
box.addToolChild({
|
|
976
|
+
render: (innerWidth: number) => patch.toolOriginalRender.call(tool, innerWidth),
|
|
977
|
+
invalidate: () => tool.invalidate?.(),
|
|
978
|
+
});
|
|
904
979
|
}
|
|
905
|
-
box.addToolChild({
|
|
906
|
-
render: (innerWidth: number) => patch.toolOriginalRender.call(tool, innerWidth),
|
|
907
|
-
invalidate: () => tool.invalidate?.(),
|
|
908
|
-
});
|
|
909
980
|
}
|
|
910
981
|
return ["", ...box.render(width)];
|
|
911
982
|
},
|
|
912
983
|
invalidate() {
|
|
913
|
-
for (const
|
|
984
|
+
for (const item of cardItems) item.child?.invalidate?.();
|
|
914
985
|
},
|
|
915
986
|
});
|
|
916
987
|
// 展开卡内工具会显示 error,外层仍挂 abort/length,避免只藏在折叠工具里。
|
|
@@ -1066,9 +1137,7 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
1066
1137
|
const name = String(this.toolName ?? "");
|
|
1067
1138
|
if (EDIT_WRITE_TOOLS.has(name)) {
|
|
1068
1139
|
if (!this.result || this.isPartial === true) scheduleAnimation(this);
|
|
1069
|
-
|
|
1070
|
-
if (this.expanded !== true) return lines;
|
|
1071
|
-
return compactEditWriteExpandedLines(this, width, deps.writeMetadata);
|
|
1140
|
+
return compactEditWriteLines(this, width, deps.writeMetadata);
|
|
1072
1141
|
}
|
|
1073
1142
|
// Agent/Task 等同普通工具:折叠不外置(live 面板走独立 widget)。
|
|
1074
1143
|
if (expandedRoundToolIds.has(String(this.toolCallId ?? ""))) return [];
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
} from "./tool/result.ts";
|
|
35
35
|
import { oneLine } from "../utils/format.ts";
|
|
36
36
|
import { showMoreHintText } from "./tool/show-more-hint.ts";
|
|
37
|
+
import { countWriteDiffStats } from "./tool/diff/diff-renderer.ts";
|
|
37
38
|
import { renderRichToolResult, type WriteExecutionMetadataStore } from "./tool/diff/index.ts";
|
|
38
39
|
import { getMessageDisplayTheme } from "./tool/message-display.ts";
|
|
39
40
|
import { humanizeToolLabel, toolCallSummary } from "./tool/names.ts";
|
|
@@ -242,6 +243,22 @@ function createCcstyleTool(
|
|
|
242
243
|
title: label === toolName ? humanizeToolLabel(label) : label,
|
|
243
244
|
variant: "default",
|
|
244
245
|
});
|
|
246
|
+
let writeStatsText = "";
|
|
247
|
+
let writeStatsStyled = "";
|
|
248
|
+
if (toolName === "write" && visualState === "success") {
|
|
249
|
+
const meta = writeExecutionMetadata.get(context?.toolCallId);
|
|
250
|
+
const stats = countWriteDiffStats(
|
|
251
|
+
typeof args?.content === "string" ? args.content : undefined,
|
|
252
|
+
meta?.previousContent,
|
|
253
|
+
meta?.fileExistedBeforeWrite,
|
|
254
|
+
);
|
|
255
|
+
if (stats) {
|
|
256
|
+
writeStatsText = ` (+${stats.added} -${stats.removed})`;
|
|
257
|
+
writeStatsStyled = ` ${theme.fg("dim", "(")}${theme.fg("success", `+${stats.added}`)} ${theme.fg("error", `-${stats.removed}`)}${theme.fg("dim", ")")}`;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const extraText = writeStatsText || summary.detail;
|
|
261
|
+
const extraStyled = writeStatsStyled || theme.fg("dim", summary.detail);
|
|
245
262
|
let cachedWidth: number | undefined;
|
|
246
263
|
let cachedLine: string | undefined;
|
|
247
264
|
const expanded = Boolean(context?.expanded);
|
|
@@ -255,10 +272,10 @@ function createCcstyleTool(
|
|
|
255
272
|
0,
|
|
256
273
|
viewportWidth - visibleWidth(icon) - 1 - (expanded ? 0 : 1),
|
|
257
274
|
);
|
|
258
|
-
const mainWidth = Math.max(0, callWidth - visibleWidth(
|
|
275
|
+
const mainWidth = Math.max(0, callWidth - visibleWidth(extraText));
|
|
259
276
|
cachedWidth = width;
|
|
260
277
|
// 纯文本先截断再着色(省略号不带 ANSI);从头截断,与多 tool 一致
|
|
261
|
-
cachedLine = `${lead}${icon} ${theme.fg("toolTitle", headTruncateToWidth(summary.main, mainWidth))}${
|
|
278
|
+
cachedLine = `${lead}${icon} ${theme.fg("toolTitle", headTruncateToWidth(summary.main, mainWidth))}${extraStyled}`;
|
|
262
279
|
return [truncateToWidth(cachedLine, viewportWidth, "")];
|
|
263
280
|
},
|
|
264
281
|
invalidate() {},
|