pi-cc-extensions 0.8.26 → 0.8.28
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/extensions/claude-code-style.ts +322 -104
- package/extensions/index.ts +20 -0
- package/extensions/subagent-notification.ts +114 -0
- package/extensions/tool-diff/diff-renderer.ts +95 -55
- package/extensions/tool-diff/index.ts +2 -1
- package/extensions/tool-diff/shiki-highlight.ts +90 -0
- package/extensions/tool-grouping.ts +492 -0
- package/extensions/tool-loading-icon.ts +9 -0
- package/extensions/tool-result-sanitize.ts +2 -2
- package/package.json +3 -8
|
@@ -6,6 +6,12 @@ import {
|
|
|
6
6
|
type CompactStyleMode,
|
|
7
7
|
} from "./compact-style.ts";
|
|
8
8
|
import { showTextPreview } from "./context.ts";
|
|
9
|
+
import {
|
|
10
|
+
installToolGrouping,
|
|
11
|
+
ToolGroupComponent,
|
|
12
|
+
type ToolGroupingHooks,
|
|
13
|
+
} from "./tool-grouping.ts";
|
|
14
|
+
import { TOOL_LOADING_INTERVAL_MS, toolLoadingIcon } from "./tool-loading-icon.ts";
|
|
9
15
|
import { sanitizeToolResultText } from "./tool-result-sanitize.ts";
|
|
10
16
|
import {
|
|
11
17
|
DEFAULT_TOOL_DISPLAY_CONFIG,
|
|
@@ -27,6 +33,7 @@ import {
|
|
|
27
33
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
28
34
|
import { homedir } from "node:os";
|
|
29
35
|
import { join } from "node:path";
|
|
36
|
+
import { inspect } from "node:util";
|
|
30
37
|
|
|
31
38
|
/**
|
|
32
39
|
* Claude Code Style for pi.
|
|
@@ -238,13 +245,30 @@ function oneLine(value: unknown, max = 72): string {
|
|
|
238
245
|
}
|
|
239
246
|
|
|
240
247
|
function rawTextFromResult(result: any): string {
|
|
241
|
-
|
|
242
|
-
|
|
248
|
+
return Array.isArray(result?.content)
|
|
249
|
+
? result.content
|
|
250
|
+
.filter((item: any) => item?.type === "text")
|
|
251
|
+
.map((item: any) => String(item.text ?? ""))
|
|
252
|
+
.join("\n")
|
|
253
|
+
: "";
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function detailsFromResult(result: any): string {
|
|
257
|
+
if (result?.details === undefined) return "";
|
|
258
|
+
const details =
|
|
259
|
+
typeof result.details === "string"
|
|
260
|
+
? result.details
|
|
261
|
+
: inspect(result.details, { depth: 8, breakLength: 100, compact: false });
|
|
262
|
+
return sanitizeToolResultText(details, 16_384);
|
|
243
263
|
}
|
|
244
264
|
|
|
245
|
-
function textFromResult(result: any): string {
|
|
265
|
+
function textFromResult(result: any, expanded = false): string {
|
|
246
266
|
// Compact previews only need short text; bound sanitize work.
|
|
247
|
-
|
|
267
|
+
const content = sanitizeToolResultText(rawTextFromResult(result), 16_384);
|
|
268
|
+
const details = detailsFromResult(result);
|
|
269
|
+
if (!content) return details;
|
|
270
|
+
if (!expanded || !details || details === content) return content;
|
|
271
|
+
return `${content}\nDetails:\n${details}`;
|
|
248
272
|
}
|
|
249
273
|
|
|
250
274
|
export function outputLineCount(result: any): number {
|
|
@@ -267,23 +291,6 @@ function toolIcon(_name: string): string {
|
|
|
267
291
|
return "●";
|
|
268
292
|
}
|
|
269
293
|
|
|
270
|
-
// Match the braille loader shown to the left of pi's "Working..." row.
|
|
271
|
-
// Every frame is one cell wide, so tool titles remain horizontally stable.
|
|
272
|
-
const WORKING_LOADER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
273
|
-
const TOOL_PENDING_FRAMES: Record<string, string[]> = {
|
|
274
|
-
read: WORKING_LOADER_FRAMES,
|
|
275
|
-
bash: WORKING_LOADER_FRAMES,
|
|
276
|
-
edit: WORKING_LOADER_FRAMES,
|
|
277
|
-
write: WORKING_LOADER_FRAMES,
|
|
278
|
-
find: WORKING_LOADER_FRAMES,
|
|
279
|
-
grep: WORKING_LOADER_FRAMES,
|
|
280
|
-
ls: WORKING_LOADER_FRAMES,
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
function animationFrame(frames: string[], intervalMs = 120): string {
|
|
284
|
-
return frames[Math.floor(Date.now() / intervalMs) % frames.length] ?? frames[0] ?? "";
|
|
285
|
-
}
|
|
286
|
-
|
|
287
294
|
const activeAnimationContexts = new Set<any>();
|
|
288
295
|
let sharedAnimationTimer: ReturnType<typeof setTimeout> | null = null;
|
|
289
296
|
|
|
@@ -308,7 +315,7 @@ function clearAllAnimations() {
|
|
|
308
315
|
}
|
|
309
316
|
}
|
|
310
317
|
|
|
311
|
-
function scheduleAnimation(context: any, intervalMs =
|
|
318
|
+
function scheduleAnimation(context: any, intervalMs = TOOL_LOADING_INTERVAL_MS) {
|
|
312
319
|
const state = (context.state ??= {});
|
|
313
320
|
if (state.ccstyleAnimationScheduled) return;
|
|
314
321
|
state.ccstyleAnimationScheduled = true;
|
|
@@ -326,8 +333,8 @@ function scheduleAnimation(context: any, intervalMs = 80) {
|
|
|
326
333
|
}
|
|
327
334
|
}
|
|
328
335
|
|
|
329
|
-
function pendingIcon(
|
|
330
|
-
return
|
|
336
|
+
function pendingIcon(_name: string): string {
|
|
337
|
+
return toolLoadingIcon();
|
|
331
338
|
}
|
|
332
339
|
|
|
333
340
|
type ToolVisualState = "pending" | "success" | "error";
|
|
@@ -434,7 +441,7 @@ const EXPANDED_TOOL_IO_VIEW_GENERATION = Symbol("ccstyle-expanded-tool-io-view")
|
|
|
434
441
|
* Expanded tool body with clear Input / Output sections (Grok Build–style).
|
|
435
442
|
*
|
|
436
443
|
* Visual frame:
|
|
437
|
-
*
|
|
444
|
+
* ├ Input [show more]
|
|
438
445
|
* │ path: src/a.ts
|
|
439
446
|
* │
|
|
440
447
|
* └ Output [show more]
|
|
@@ -538,7 +545,7 @@ export class ExpandedToolIoView {
|
|
|
538
545
|
|
|
539
546
|
const theme = this.theme;
|
|
540
547
|
const safeWidth = Math.max(1, Math.floor(width));
|
|
541
|
-
const rail = "
|
|
548
|
+
const rail = " │ ";
|
|
542
549
|
const railWidth = visibleWidth(rail);
|
|
543
550
|
const bodyWidth = toolViewportWidth(safeWidth);
|
|
544
551
|
const contentWidth = Math.max(1, bodyWidth - railWidth);
|
|
@@ -547,12 +554,12 @@ export class ExpandedToolIoView {
|
|
|
547
554
|
this.truncated = { input: false, output: false };
|
|
548
555
|
|
|
549
556
|
const pushHeader = (
|
|
550
|
-
corner: "
|
|
557
|
+
corner: "├" | "└",
|
|
551
558
|
label: string,
|
|
552
559
|
section: ToolIoSection,
|
|
553
560
|
showMore: boolean,
|
|
554
561
|
) => {
|
|
555
|
-
const mark = theme.fg("dim", `
|
|
562
|
+
const mark = theme.fg("dim", ` ${corner} `);
|
|
556
563
|
const title = theme.fg(
|
|
557
564
|
"accent",
|
|
558
565
|
typeof theme.bold === "function" ? theme.bold(label) : label,
|
|
@@ -563,12 +570,13 @@ export class ExpandedToolIoView {
|
|
|
563
570
|
lines.push(truncateToWidth(mark + title + more, safeWidth, ""));
|
|
564
571
|
};
|
|
565
572
|
|
|
566
|
-
const pushRailLine = (styledContent: string) => {
|
|
567
|
-
|
|
573
|
+
const pushRailLine = (styledContent: string, continued = true) => {
|
|
574
|
+
const prefix = continued ? rail : " ";
|
|
575
|
+
lines.push(truncateToWidth(theme.fg("dim", prefix) + styledContent, safeWidth, ""));
|
|
568
576
|
};
|
|
569
577
|
|
|
570
578
|
const pushBlankRail = () => {
|
|
571
|
-
lines.push(truncateToWidth(theme.fg("dim", "
|
|
579
|
+
lines.push(truncateToWidth(theme.fg("dim", " │"), safeWidth, ""));
|
|
572
580
|
};
|
|
573
581
|
|
|
574
582
|
/** Style `key: value` input rows — dim keys, readable values. */
|
|
@@ -576,16 +584,16 @@ export class ExpandedToolIoView {
|
|
|
576
584
|
const match = rawLine.match(/^([A-Za-z_][\w.-]*)(:\s*)(.*)$/);
|
|
577
585
|
if (!match) return theme.fg("muted", rawLine);
|
|
578
586
|
const [, key, sep, rest] = match;
|
|
579
|
-
return theme.fg("dim", key + sep) + theme.fg("
|
|
587
|
+
return theme.fg("dim", key + sep) + theme.fg("muted", rest ?? "");
|
|
580
588
|
};
|
|
581
589
|
|
|
582
590
|
const pushBody = (
|
|
583
591
|
body: string,
|
|
584
|
-
opts: { input?: boolean; limit: number },
|
|
592
|
+
opts: { input?: boolean; limit: number; continued?: boolean },
|
|
585
593
|
): boolean /* truncated */ => {
|
|
586
594
|
const raw = body.replace(/\t/g, " ").replace(/\n+$/, "");
|
|
587
595
|
if (!raw.trim()) {
|
|
588
|
-
pushRailLine(theme.fg("dim", "(empty)"));
|
|
596
|
+
pushRailLine(theme.fg("dim", "(empty)"), opts.continued);
|
|
589
597
|
return false;
|
|
590
598
|
}
|
|
591
599
|
const sourceLines = raw.split("\n");
|
|
@@ -600,11 +608,11 @@ export class ExpandedToolIoView {
|
|
|
600
608
|
// theme/wrap measurements disagree slightly.
|
|
601
609
|
const truncated = wrapped.length > opts.limit || sourceLines.length > opts.limit;
|
|
602
610
|
const visible = truncated ? wrapped.slice(0, Math.min(opts.limit, wrapped.length)) : wrapped;
|
|
603
|
-
for (const line of visible) pushRailLine(line);
|
|
611
|
+
for (const line of visible) pushRailLine(line, opts.continued);
|
|
604
612
|
if (truncated) {
|
|
605
613
|
const hidden = Math.max(0, wrapped.length - visible.length);
|
|
606
614
|
if (hidden > 0) {
|
|
607
|
-
pushRailLine(theme.fg("dim", `… +${hidden} more lines`));
|
|
615
|
+
pushRailLine(theme.fg("dim", `… +${hidden} more lines`), opts.continued);
|
|
608
616
|
}
|
|
609
617
|
}
|
|
610
618
|
return truncated;
|
|
@@ -628,16 +636,20 @@ export class ExpandedToolIoView {
|
|
|
628
636
|
|
|
629
637
|
if (hasInput) {
|
|
630
638
|
this.truncated.input = inputWouldTruncate;
|
|
631
|
-
pushHeader("
|
|
632
|
-
pushBody(this.inputBody, {
|
|
639
|
+
pushHeader("├", "Input", "input", inputWouldTruncate);
|
|
640
|
+
pushBody(this.inputBody, {
|
|
641
|
+
input: true,
|
|
642
|
+
limit: this.maxInputLines,
|
|
643
|
+
continued: true,
|
|
644
|
+
});
|
|
633
645
|
pushBlankRail();
|
|
634
646
|
this.truncated.output = outputWouldTruncate;
|
|
635
647
|
pushHeader("└", "Output", "output", outputWouldTruncate);
|
|
636
|
-
pushBody(outputText, { limit: this.maxOutputLines });
|
|
648
|
+
pushBody(outputText, { limit: this.maxOutputLines, continued: false });
|
|
637
649
|
} else {
|
|
638
650
|
this.truncated.output = outputWouldTruncate;
|
|
639
|
-
pushHeader("
|
|
640
|
-
pushBody(outputText, { limit: this.maxOutputLines });
|
|
651
|
+
pushHeader("└", "Output", "output", outputWouldTruncate);
|
|
652
|
+
pushBody(outputText, { limit: this.maxOutputLines, continued: false });
|
|
641
653
|
}
|
|
642
654
|
|
|
643
655
|
this.cachedWidth = width;
|
|
@@ -682,7 +694,7 @@ function bodyExceedsLineLimit(
|
|
|
682
694
|
if (asInput) {
|
|
683
695
|
const match = source.match(/^([A-Za-z_][\w.-]*)(:\s*)(.*)$/);
|
|
684
696
|
styled = match
|
|
685
|
-
? theme.fg("dim", match[1] + match[2]) + theme.fg("
|
|
697
|
+
? theme.fg("dim", match[1] + match[2]) + theme.fg("muted", match[3] ?? "")
|
|
686
698
|
: theme.fg("muted", source);
|
|
687
699
|
} else {
|
|
688
700
|
styled = theme.fg(bodyColor, source);
|
|
@@ -695,16 +707,16 @@ function bodyExceedsLineLimit(
|
|
|
695
707
|
}
|
|
696
708
|
|
|
697
709
|
export function renderCollapsedToolResult(body: string, collapsedHint = ""): string {
|
|
698
|
-
return `
|
|
710
|
+
return ` ↳ ${body}${collapsedHint}`;
|
|
699
711
|
}
|
|
700
712
|
|
|
701
713
|
export function renderCollapsedToolResultToWidth(
|
|
702
714
|
body: string,
|
|
703
715
|
collapsedHint: string,
|
|
704
716
|
width: number,
|
|
717
|
+
prefix = " ↳ ",
|
|
705
718
|
): string {
|
|
706
719
|
const previewWidth = toolViewportWidth(width);
|
|
707
|
-
const prefix = " ↳ ";
|
|
708
720
|
const bodyWidth = Math.max(1, previewWidth - visibleWidth(prefix) - visibleWidth(collapsedHint));
|
|
709
721
|
return truncateToWidth(
|
|
710
722
|
prefix + middleTruncateToWidth(body, bodyWidth) + collapsedHint,
|
|
@@ -879,6 +891,7 @@ let assistantMessageActive = false;
|
|
|
879
891
|
let scrollButtonSyncScheduled = false;
|
|
880
892
|
let sessionRenderTimer: ReturnType<typeof setTimeout> | null = null;
|
|
881
893
|
let hoveredToolCallId: string | null = null;
|
|
894
|
+
let hoveredToolGroup: ToolGroupComponent | null = null;
|
|
882
895
|
|
|
883
896
|
function parseSgrMousePackets(data: string): SgrMousePacket[] | null {
|
|
884
897
|
const pattern = /\x1b\[<(\d+);(\d+);(\d+)([Mm])/g;
|
|
@@ -1004,7 +1017,7 @@ function fixedEditorContextScore(
|
|
|
1004
1017
|
}
|
|
1005
1018
|
|
|
1006
1019
|
/** Summary markers used by Pi and ccstyle; unlike the trailing hint, these survive truncation. */
|
|
1007
|
-
const COLLAPSED_TOOL_SUMMARY = /^\s*(
|
|
1020
|
+
const COLLAPSED_TOOL_SUMMARY = /^\s*(?:↳|└|⎿|●|✓|✗)/;
|
|
1008
1021
|
/** fixedEditorContextScore max ≈ 100 + 2*(5+4+3+2); stop once we hit a near-perfect match. */
|
|
1009
1022
|
const TOOL_CLICK_EARLY_EXIT_SCORE = 120;
|
|
1010
1023
|
|
|
@@ -1498,7 +1511,7 @@ function visibleMouseLine(tui: any, packet: SgrMousePacket): string {
|
|
|
1498
1511
|
}
|
|
1499
1512
|
|
|
1500
1513
|
function isCollapsedHintAtColumn(line: string, col: number): boolean {
|
|
1501
|
-
const match = /(\([^()\n]* \/ click\))\s*$/.exec(line);
|
|
1514
|
+
const match = /(\([^()\n]* \/ click\)|click to show more)\s*$/.exec(line);
|
|
1502
1515
|
if (!match?.[1] || col <= 0) return false;
|
|
1503
1516
|
const start = visibleWidth(line.slice(0, match.index)) + 1;
|
|
1504
1517
|
return col >= start && col < start + visibleWidth(match[1]);
|
|
@@ -1544,10 +1557,19 @@ function setHoveredToolIo(view: ExpandedToolIoView | null, section: ToolIoSectio
|
|
|
1544
1557
|
return true;
|
|
1545
1558
|
}
|
|
1546
1559
|
|
|
1560
|
+
function setHoveredToolGroup(group: ToolGroupComponent | null): boolean {
|
|
1561
|
+
if (group === hoveredToolGroup) return false;
|
|
1562
|
+
hoveredToolGroup?.setHintHovered(false);
|
|
1563
|
+
hoveredToolGroup = group;
|
|
1564
|
+
group?.setHintHovered(true);
|
|
1565
|
+
return true;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1547
1568
|
function updateToolSummaryHover(tui: any, packet: SgrMousePacket): void {
|
|
1548
1569
|
if ((packet.code & 32) === 0 || packet.final !== "M") return;
|
|
1549
1570
|
const visibleLine = visibleMouseLine(tui, packet);
|
|
1550
1571
|
let nextToolCallId: string | null = null;
|
|
1572
|
+
let nextGroup: ToolGroupComponent | null = null;
|
|
1551
1573
|
let nextIoView: ExpandedToolIoView | null = null;
|
|
1552
1574
|
let nextIoSection: ToolIoSection | null = null;
|
|
1553
1575
|
|
|
@@ -1557,6 +1579,7 @@ function updateToolSummaryHover(tui: any, packet: SgrMousePacket): void {
|
|
|
1557
1579
|
) {
|
|
1558
1580
|
const hit = findToolAtScreenRow(tui, packet.row);
|
|
1559
1581
|
nextToolCallId = hit && !hit.component.expanded ? hit.component.toolCallId : null;
|
|
1582
|
+
nextGroup = hit?.component instanceof ToolGroupComponent ? hit.component : null;
|
|
1560
1583
|
} else if (visibleLine.includes(SHOW_MORE_LABEL)) {
|
|
1561
1584
|
const hit = findToolAtScreenRow(tui, packet.row);
|
|
1562
1585
|
const view = hit && hit.component.expanded ? resolveIoViewFromTool(hit.component) : null;
|
|
@@ -1570,11 +1593,13 @@ function updateToolSummaryHover(tui: any, packet: SgrMousePacket): void {
|
|
|
1570
1593
|
|
|
1571
1594
|
const changed = nextToolCallId !== hoveredToolCallId;
|
|
1572
1595
|
hoveredToolCallId = nextToolCallId;
|
|
1573
|
-
if (setHoveredToolIo(nextIoView, nextIoSection) ||
|
|
1596
|
+
if (setHoveredToolIo(nextIoView, nextIoSection) || setHoveredToolGroup(nextGroup) || changed)
|
|
1597
|
+
tui.requestRender?.();
|
|
1574
1598
|
}
|
|
1575
1599
|
|
|
1576
1600
|
function isExpandedToolCardHit(hit: ToolRenderHit, width: number): boolean {
|
|
1577
1601
|
if (!Boolean(hit.component.expanded)) return false;
|
|
1602
|
+
if (hit.component instanceof ToolGroupComponent) return true;
|
|
1578
1603
|
const box = hit.component.contentBox;
|
|
1579
1604
|
if (!box || !Array.isArray(hit.component.children) || !hit.component.children.includes(box)) {
|
|
1580
1605
|
return false;
|
|
@@ -1588,11 +1613,11 @@ function isExpandedToolCardHit(hit: ToolRenderHit, width: number): boolean {
|
|
|
1588
1613
|
|
|
1589
1614
|
function toggleToolAtMouseClick(tui: any, packet: SgrMousePacket): boolean {
|
|
1590
1615
|
const line = visibleMouseLine(tui, packet);
|
|
1591
|
-
const collapsedHint = isCollapsedHintAtColumn(line, packet.col);
|
|
1592
1616
|
const hasShowMore = line.includes(SHOW_MORE_LABEL);
|
|
1593
1617
|
|
|
1594
1618
|
const hit = findToolAtScreenRow(tui, packet.row);
|
|
1595
1619
|
if (!hit) return false;
|
|
1620
|
+
const collapsedHint = isCollapsedHintAtColumn(line, packet.col);
|
|
1596
1621
|
const width = Math.max(1, Number(tui?.terminal?.columns) || 80);
|
|
1597
1622
|
if (Boolean(hit.component.expanded)) {
|
|
1598
1623
|
if (hasShowMore && tryOpenToolIoShowMore(tui, packet, hit)) return true;
|
|
@@ -1600,6 +1625,7 @@ function toggleToolAtMouseClick(tui: any, packet: SgrMousePacket): boolean {
|
|
|
1600
1625
|
const previousHeight = renderComponentTree(hit.component, width).length;
|
|
1601
1626
|
hit.component.setExpanded(false);
|
|
1602
1627
|
hoveredToolCallId = null;
|
|
1628
|
+
setHoveredToolGroup(null);
|
|
1603
1629
|
setHoveredToolIo(null, null);
|
|
1604
1630
|
hit.component.invalidate?.();
|
|
1605
1631
|
const nextHeight = renderComponentTree(hit.component, width).length;
|
|
@@ -1611,6 +1637,7 @@ function toggleToolAtMouseClick(tui: any, packet: SgrMousePacket): boolean {
|
|
|
1611
1637
|
|
|
1612
1638
|
hit.component.setExpanded(true);
|
|
1613
1639
|
hoveredToolCallId = null;
|
|
1640
|
+
setHoveredToolGroup(null);
|
|
1614
1641
|
setHoveredToolIo(null, null);
|
|
1615
1642
|
hit.component.invalidate?.();
|
|
1616
1643
|
tui.requestRender?.();
|
|
@@ -1798,6 +1825,7 @@ function teardownToolMouseInteraction(): void {
|
|
|
1798
1825
|
toolMouseInputUnsubscribe?.();
|
|
1799
1826
|
toolMouseInputUnsubscribe = null;
|
|
1800
1827
|
hoveredToolCallId = null;
|
|
1828
|
+
setHoveredToolGroup(null);
|
|
1801
1829
|
setHoveredToolIo(null, null);
|
|
1802
1830
|
try {
|
|
1803
1831
|
toolMouseTui?.terminal?.write?.(TOOL_MOUSE_DISABLE);
|
|
@@ -1877,16 +1905,26 @@ function scheduleSessionRender(refresh?: () => void): void {
|
|
|
1877
1905
|
const BRIGHT_GREEN = "\x1b[38;2;80;220;100m";
|
|
1878
1906
|
const ANSI_FG_RESET = "\x1b[39m";
|
|
1879
1907
|
|
|
1880
|
-
function refreshCurrentTranscript(
|
|
1908
|
+
function refreshCurrentTranscript(
|
|
1909
|
+
compactStyle: CompactStyleHooks,
|
|
1910
|
+
ctx?: any,
|
|
1911
|
+
toolGrouping?: ToolGroupingHooks,
|
|
1912
|
+
): void {
|
|
1913
|
+
toolGrouping?.refresh();
|
|
1881
1914
|
compactStyle.refresh();
|
|
1882
1915
|
toolMouseTui?.requestRender?.(true);
|
|
1883
1916
|
ctx?.ui?.requestRender?.(true);
|
|
1884
1917
|
}
|
|
1885
1918
|
|
|
1886
|
-
function applyStyleMode(
|
|
1919
|
+
function applyStyleMode(
|
|
1920
|
+
mode: CompactStyleMode,
|
|
1921
|
+
ctx: any,
|
|
1922
|
+
compactStyle: CompactStyleHooks,
|
|
1923
|
+
toolGrouping?: ToolGroupingHooks,
|
|
1924
|
+
): void {
|
|
1887
1925
|
config.mode = mode;
|
|
1888
1926
|
saveConfig();
|
|
1889
|
-
refreshCurrentTranscript(compactStyle, ctx);
|
|
1927
|
+
refreshCurrentTranscript(compactStyle, ctx, toolGrouping);
|
|
1890
1928
|
ctx.ui.notify(`Claude Code style: ${mode}`, "info");
|
|
1891
1929
|
}
|
|
1892
1930
|
|
|
@@ -2011,7 +2049,11 @@ function renderSectionTabBar(
|
|
|
2011
2049
|
return truncateToWidth(pieces.join(""), Math.max(0, width));
|
|
2012
2050
|
}
|
|
2013
2051
|
|
|
2014
|
-
async function showCcstylePanel(
|
|
2052
|
+
async function showCcstylePanel(
|
|
2053
|
+
ctx: any,
|
|
2054
|
+
compactStyle: CompactStyleHooks,
|
|
2055
|
+
toolGrouping?: ToolGroupingHooks,
|
|
2056
|
+
): Promise<void> {
|
|
2015
2057
|
if (ctx?.mode !== "tui" || !ctx?.hasUI || typeof ctx.ui?.custom !== "function") {
|
|
2016
2058
|
ctx.ui?.notify?.("/ccstyle requires TUI mode", "warning");
|
|
2017
2059
|
return;
|
|
@@ -2110,7 +2152,7 @@ async function showCcstylePanel(ctx: any, compactStyle: CompactStyleHooks): Prom
|
|
|
2110
2152
|
switch (id) {
|
|
2111
2153
|
case "mode":
|
|
2112
2154
|
modeSetting.description = modeSettingDescription(value as CompactStyleMode);
|
|
2113
|
-
applyStyleMode(value as CompactStyleMode, ctx, compactStyle);
|
|
2155
|
+
applyStyleMode(value as CompactStyleMode, ctx, compactStyle, toolGrouping);
|
|
2114
2156
|
return;
|
|
2115
2157
|
case "fixedEditorFeatures":
|
|
2116
2158
|
config.fixedEditorFeatures = value === "on";
|
|
@@ -2287,21 +2329,101 @@ function singleLine(text: string) {
|
|
|
2287
2329
|
};
|
|
2288
2330
|
}
|
|
2289
2331
|
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
return
|
|
2303
|
-
|
|
2304
|
-
|
|
2332
|
+
function insetComponent(component: any): any {
|
|
2333
|
+
return {
|
|
2334
|
+
render: (width: number) =>
|
|
2335
|
+
component.render(Math.max(1, width - 1)).map((line: string) => {
|
|
2336
|
+
const nestedMarker = line.replace(/^((?:\x1b\[[0-?]*[ -/]*[@-~])*)↳/, "$1 ↳");
|
|
2337
|
+
return ` ${nestedMarker}`;
|
|
2338
|
+
}),
|
|
2339
|
+
invalidate: () => component.invalidate?.(),
|
|
2340
|
+
};
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
function humanizeToolLabel(label: string): string {
|
|
2344
|
+
return label
|
|
2345
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
2346
|
+
.replace(/[_-]+/g, " ")
|
|
2347
|
+
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
function singleToolCallSummary(
|
|
2351
|
+
toolName: string,
|
|
2352
|
+
label: string,
|
|
2353
|
+
args: any,
|
|
2354
|
+
): { main: string; detail: string } {
|
|
2355
|
+
const title = label === toolName ? humanizeToolLabel(label) : label;
|
|
2356
|
+
if (!args || typeof args !== "object") return { main: title, detail: "" };
|
|
2357
|
+
const name = toolName.toLowerCase();
|
|
2358
|
+
const value = (fallback: string, ...keys: string[]) => {
|
|
2359
|
+
const found = keys.map((key) => args[key]).find((item) => typeof item === "string" && item);
|
|
2360
|
+
return `${title} ${oneLine(found || fallback, Infinity)}`;
|
|
2361
|
+
};
|
|
2362
|
+
if (AGENT_FAMILY_TOOL_NAMES.has(toolName) && args.agent_id) {
|
|
2363
|
+
return { main: `${title} ${oneLine(args.agent_id, Infinity)}`, detail: "" };
|
|
2364
|
+
}
|
|
2365
|
+
if (name === "agent" || name === "agents") {
|
|
2366
|
+
return {
|
|
2367
|
+
main: value(name === "agent" ? "launch agent" : "launch agents", "description", "prompt"),
|
|
2368
|
+
detail: "",
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2371
|
+
if (name === "skill") return { main: value("run skill", "name"), detail: "" };
|
|
2372
|
+
if (name === "enterplanmode" || name === "enter_plan_mode") {
|
|
2373
|
+
return { main: `${title} enable read-only planning`, detail: "" };
|
|
2374
|
+
}
|
|
2375
|
+
if (name === "exitplanmode" || name === "exit_plan_mode") {
|
|
2376
|
+
return { main: `${title} present plan`, detail: "" };
|
|
2377
|
+
}
|
|
2378
|
+
if (name === "taskcreate") return { main: value("create task", "subject"), detail: "" };
|
|
2379
|
+
if (name === "tasklist") return { main: `${title} task list`, detail: "" };
|
|
2380
|
+
if (name === "taskget" || name === "taskupdate") {
|
|
2381
|
+
return { main: value("task", "taskId", "task_id"), detail: "" };
|
|
2382
|
+
}
|
|
2383
|
+
if (name === "taskoutput" || name === "taskstop") {
|
|
2384
|
+
return { main: value("background task", "task_id", "taskId"), detail: "" };
|
|
2385
|
+
}
|
|
2386
|
+
if (name === "taskexecute") {
|
|
2387
|
+
const ids = Array.isArray(args.task_ids)
|
|
2388
|
+
? args.task_ids
|
|
2389
|
+
: Array.isArray(args.taskIds)
|
|
2390
|
+
? args.taskIds
|
|
2391
|
+
: [];
|
|
2392
|
+
const summary = ids.length
|
|
2393
|
+
? `${ids[0]}${ids.length > 1 ? ` (+${ids.length - 1} tasks)` : ""}`
|
|
2394
|
+
: "start tasks";
|
|
2395
|
+
return { main: `${title} ${summary}`, detail: "" };
|
|
2396
|
+
}
|
|
2397
|
+
if (toolName === "read") {
|
|
2398
|
+
const details = [
|
|
2399
|
+
args.offset !== undefined ? `offset=${args.offset}` : "",
|
|
2400
|
+
args.limit !== undefined ? `limit=${args.limit}` : "",
|
|
2401
|
+
].filter(Boolean);
|
|
2402
|
+
return {
|
|
2403
|
+
main: `${title}${args.path ? ` ${oneLine(args.path, Infinity)}` : ""}`,
|
|
2404
|
+
detail: details.length ? ` (${details.join(", ")})` : "",
|
|
2405
|
+
};
|
|
2406
|
+
}
|
|
2407
|
+
const preferred =
|
|
2408
|
+
args.path ??
|
|
2409
|
+
args.file_path ??
|
|
2410
|
+
args.command ??
|
|
2411
|
+
args.query ??
|
|
2412
|
+
args.question ??
|
|
2413
|
+
args.pattern ??
|
|
2414
|
+
args.url ??
|
|
2415
|
+
args.name ??
|
|
2416
|
+
args.tool_use_id ??
|
|
2417
|
+
args.toolCallId ??
|
|
2418
|
+
args.id ??
|
|
2419
|
+
args.message;
|
|
2420
|
+
return {
|
|
2421
|
+
main:
|
|
2422
|
+
preferred !== undefined && preferred !== null && typeof preferred !== "object"
|
|
2423
|
+
? `${title} ${oneLine(preferred, Infinity)}`
|
|
2424
|
+
: title,
|
|
2425
|
+
detail: "",
|
|
2426
|
+
};
|
|
2305
2427
|
}
|
|
2306
2428
|
|
|
2307
2429
|
export function middleTruncateToWidth(text: string, width: number): string {
|
|
@@ -2330,6 +2452,74 @@ export function shouldRenderRichDiff(
|
|
|
2330
2452
|
return mode === "on" && !isError && (toolName === "edit" || toolName === "write");
|
|
2331
2453
|
}
|
|
2332
2454
|
|
|
2455
|
+
type ParsedTask = { id: string; status: string; subject: string };
|
|
2456
|
+
|
|
2457
|
+
function parseTaskList(text: string): ParsedTask[] {
|
|
2458
|
+
return text
|
|
2459
|
+
.split("\n")
|
|
2460
|
+
.map((line) => line.match(/^#(\d+) \[([^\]]+)] (.+)$/))
|
|
2461
|
+
.filter((match): match is RegExpMatchArray => Boolean(match))
|
|
2462
|
+
.map((match) => ({ id: match[1]!, status: match[2]!, subject: match[3]! }));
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
function taskListSummary(tasks: ParsedTask[]): string {
|
|
2466
|
+
const counts = { pending: 0, in_progress: 0, completed: 0 };
|
|
2467
|
+
for (const task of tasks) {
|
|
2468
|
+
if (task.status in counts) counts[task.status as keyof typeof counts]++;
|
|
2469
|
+
}
|
|
2470
|
+
return [
|
|
2471
|
+
`${tasks.length} tasks`,
|
|
2472
|
+
counts.in_progress ? `${counts.in_progress} in progress` : "",
|
|
2473
|
+
counts.pending ? `${counts.pending} pending` : "",
|
|
2474
|
+
counts.completed ? `${counts.completed} completed` : "",
|
|
2475
|
+
]
|
|
2476
|
+
.filter(Boolean)
|
|
2477
|
+
.join(" • ");
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
function renderExpandedTaskResult(
|
|
2481
|
+
toolName: string,
|
|
2482
|
+
text: string,
|
|
2483
|
+
theme: any,
|
|
2484
|
+
isError: boolean,
|
|
2485
|
+
): any | undefined {
|
|
2486
|
+
if (isError) return undefined;
|
|
2487
|
+
if (toolName === "TaskList") {
|
|
2488
|
+
const tasks = parseTaskList(text);
|
|
2489
|
+
if (!tasks.length) return undefined;
|
|
2490
|
+
const limit = Math.max(1, config.expandedPreviewMaxLines);
|
|
2491
|
+
const rows = tasks.slice(0, limit).map((task) => {
|
|
2492
|
+
const color =
|
|
2493
|
+
task.status === "completed"
|
|
2494
|
+
? "success"
|
|
2495
|
+
: task.status === "in_progress"
|
|
2496
|
+
? "warning"
|
|
2497
|
+
: "muted";
|
|
2498
|
+
return ` ${theme.fg("accent", `#${task.id}`)} ${theme.fg(color, task.status)} ${theme.fg("dim", task.subject)}`;
|
|
2499
|
+
});
|
|
2500
|
+
if (tasks.length > rows.length)
|
|
2501
|
+
rows.push(theme.fg("muted", ` … ${tasks.length - rows.length} more tasks`));
|
|
2502
|
+
return new Text(` ↳ ${theme.fg("muted", taskListSummary(tasks))}\n${rows.join("\n")}`, 0, 0);
|
|
2503
|
+
}
|
|
2504
|
+
const line = text.trim();
|
|
2505
|
+
if (!line || line.includes("\n")) return undefined;
|
|
2506
|
+
let formatted: string | undefined;
|
|
2507
|
+
let match: RegExpMatchArray | null;
|
|
2508
|
+
if (
|
|
2509
|
+
toolName === "TaskCreate" &&
|
|
2510
|
+
(match = line.match(/^Task #(\d+) created successfully: (.+)$/))
|
|
2511
|
+
) {
|
|
2512
|
+
formatted = `${theme.fg("success", "Created task")} ${theme.fg("accent", `#${match[1]}`)} ${theme.fg("muted", match[2])}`;
|
|
2513
|
+
} else if (toolName === "TaskUpdate" && (match = line.match(/^Updated task #(\d+) (.+)$/))) {
|
|
2514
|
+
formatted = `${theme.fg("success", "Updated task")} ${theme.fg("accent", `#${match[1]}`)} ${theme.fg("muted", match[2])}`;
|
|
2515
|
+
} else if (toolName === "TaskExecute") {
|
|
2516
|
+
formatted = `${theme.fg("success", "Started")} ${theme.fg("muted", line)}`;
|
|
2517
|
+
} else if (toolName === "TaskStop") {
|
|
2518
|
+
formatted = `${theme.fg("success", "Stopped")} ${theme.fg("muted", line)}`;
|
|
2519
|
+
}
|
|
2520
|
+
return formatted ? new Text(` ↳ ${formatted}`, 0, 0) : undefined;
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2333
2523
|
/** Wrap an arbitrary tool definition with ccstyle call/result rendering. */
|
|
2334
2524
|
function createCcstyleTool(
|
|
2335
2525
|
originalTool: any,
|
|
@@ -2337,7 +2527,7 @@ function createCcstyleTool(
|
|
|
2337
2527
|
): any {
|
|
2338
2528
|
const toolName = originalTool.name;
|
|
2339
2529
|
const label = isMcpToolDefinition(originalTool, toolName)
|
|
2340
|
-
? toolName
|
|
2530
|
+
? humanizeMcpToolName(toolName)
|
|
2341
2531
|
: originalTool.label || toolName;
|
|
2342
2532
|
|
|
2343
2533
|
return {
|
|
@@ -2358,20 +2548,18 @@ function createCcstyleTool(
|
|
|
2358
2548
|
visualState === "success"
|
|
2359
2549
|
? `${BRIGHT_GREEN}${rawIcon}${ANSI_FG_RESET}`
|
|
2360
2550
|
: theme.fg(toolIconColor(context), rawIcon);
|
|
2361
|
-
|
|
2362
|
-
const argument = toolCallArgument(args);
|
|
2551
|
+
const summary = singleToolCallSummary(toolName, label, args);
|
|
2363
2552
|
let cachedWidth: number | undefined;
|
|
2364
2553
|
let cachedLine: string | undefined;
|
|
2365
2554
|
return {
|
|
2366
2555
|
render(width: number) {
|
|
2367
2556
|
if (cachedLine !== undefined && cachedWidth === width) return [cachedLine];
|
|
2368
|
-
const
|
|
2369
|
-
const
|
|
2370
|
-
const
|
|
2371
|
-
const call = shown === undefined ? label : `${label}(${shown})`;
|
|
2557
|
+
const viewportWidth = toolViewportWidth(width);
|
|
2558
|
+
const callWidth = Math.max(0, viewportWidth - visibleWidth(icon) - 2);
|
|
2559
|
+
const mainWidth = Math.max(0, callWidth - visibleWidth(summary.detail));
|
|
2372
2560
|
cachedWidth = width;
|
|
2373
|
-
cachedLine =
|
|
2374
|
-
return [cachedLine];
|
|
2561
|
+
cachedLine = ` ${icon} ${theme.fg("toolTitle", middleTruncateToWidth(summary.main, mainWidth))}${theme.fg("muted", summary.detail)}`;
|
|
2562
|
+
return [truncateToWidth(cachedLine, viewportWidth, "")];
|
|
2375
2563
|
},
|
|
2376
2564
|
invalidate() {},
|
|
2377
2565
|
};
|
|
@@ -2387,7 +2575,7 @@ function createCcstyleTool(
|
|
|
2387
2575
|
}
|
|
2388
2576
|
|
|
2389
2577
|
if (options?.isPartial) {
|
|
2390
|
-
return new Text(theme.fg("muted", "
|
|
2578
|
+
return new Text(theme.fg("muted", " ⎿ Pending…"), 0, 0);
|
|
2391
2579
|
}
|
|
2392
2580
|
|
|
2393
2581
|
const isError = options?.isError || context?.isError;
|
|
@@ -2405,17 +2593,31 @@ function createCcstyleTool(
|
|
|
2405
2593
|
writeExecutionMetadata,
|
|
2406
2594
|
getToolDisplayConfig,
|
|
2407
2595
|
);
|
|
2408
|
-
if (richResult) return richResult;
|
|
2596
|
+
if (richResult) return insetComponent(richResult);
|
|
2409
2597
|
}
|
|
2410
2598
|
|
|
2411
|
-
const text = textFromResult(result);
|
|
2599
|
+
const text = textFromResult(result, expanded);
|
|
2412
2600
|
const args = context?.args;
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
const
|
|
2601
|
+
if (expanded) {
|
|
2602
|
+
const taskResult = renderExpandedTaskResult(toolName, text, theme, Boolean(isError));
|
|
2603
|
+
if (taskResult) return taskResult;
|
|
2604
|
+
}
|
|
2605
|
+
const tasks = !isError && toolName === "TaskList" ? parseTaskList(text) : [];
|
|
2606
|
+
const outputLines = outputLineCount(result) || countLines(text);
|
|
2607
|
+
const lineWord = outputLines === 1 ? "line" : "lines";
|
|
2608
|
+
const action = toolName === "read" ? "loaded" : "returned";
|
|
2609
|
+
const rendered = tasks.length
|
|
2610
|
+
? taskListSummary(tasks)
|
|
2611
|
+
: isError
|
|
2612
|
+
? text
|
|
2613
|
+
? oneLine(text)
|
|
2614
|
+
: "Failed"
|
|
2615
|
+
: outputLines
|
|
2616
|
+
? `${outputLines} ${lineWord} ${action}`
|
|
2617
|
+
: "Done";
|
|
2618
|
+
const expandable = !expanded && (tasks.length > 0 || hasExpandableDetail(text, args));
|
|
2619
|
+
const hint = expandable ? theme.fg("muted", " • click to show more") : "";
|
|
2620
|
+
const hoveredHint = expandable ? theme.fg("text", " • click to show more") : "";
|
|
2419
2621
|
if (expanded) {
|
|
2420
2622
|
return renderExpandedToolResult(
|
|
2421
2623
|
text || "",
|
|
@@ -2456,12 +2658,17 @@ function createCcstyleTool(
|
|
|
2456
2658
|
* Apart from the write override used to capture pre-write content, renderers are
|
|
2457
2659
|
* applied through ToolExecutionComponent. Patch its lookup once so tools use the
|
|
2458
2660
|
* same compact fallback shell by default. Tools named in excludeRenderers keep
|
|
2459
|
-
* their original renderer
|
|
2661
|
+
* their original renderer.
|
|
2460
2662
|
*/
|
|
2461
2663
|
const GLOBAL_TOOL_RENDER_PATCH = Symbol.for("pi.ccstyle.global-tool-render-patch");
|
|
2462
2664
|
const COMPONENT_TOOL_RENDER_MODE = Symbol.for("pi.ccstyle.component-tool-render-mode");
|
|
2463
2665
|
const COMPONENT_TOOL_SELF_SHELL_MODE = Symbol.for("pi.ccstyle.component-tool-self-shell-mode");
|
|
2464
|
-
const
|
|
2666
|
+
const AGENT_FAMILY_TOOL_NAMES = new Set([
|
|
2667
|
+
"Agent",
|
|
2668
|
+
"Agents",
|
|
2669
|
+
"get_subagent_result",
|
|
2670
|
+
"steer_subagent",
|
|
2671
|
+
]);
|
|
2465
2672
|
|
|
2466
2673
|
type ToolRenderMethods = {
|
|
2467
2674
|
hasRendererDefinition: (...args: any[]) => boolean;
|
|
@@ -2489,9 +2696,23 @@ type GlobalToolRenderPatch = {
|
|
|
2489
2696
|
originalGetResultRenderer: ToolRenderMethods["getResultRenderer"];
|
|
2490
2697
|
};
|
|
2491
2698
|
|
|
2492
|
-
function isMcpToolDefinition(definition: any, toolName: string): boolean {
|
|
2493
|
-
const label = typeof definition?.label === "string" ? definition.label : "";
|
|
2494
|
-
|
|
2699
|
+
export function isMcpToolDefinition(definition: any, toolName: string): boolean {
|
|
2700
|
+
const label = typeof definition?.label === "string" ? definition.label.trim() : "";
|
|
2701
|
+
if (/^MCP(?::|$)/i.test(label)) return true;
|
|
2702
|
+
if (toolName === "mcp" || /^mcp[_:-]|[_:-]mcp[_:-]/i.test(toolName)) return true;
|
|
2703
|
+
if (label) return false;
|
|
2704
|
+
const description = typeof definition?.description === "string" ? definition.description : "";
|
|
2705
|
+
return /\bModel Context Protocol\b/i.test(description);
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
export function humanizeMcpToolName(toolName: string): string {
|
|
2709
|
+
const words = toolName
|
|
2710
|
+
.replace(/^mcp(?:[_:-]+)+/i, "")
|
|
2711
|
+
.split(/[_:-]+/)
|
|
2712
|
+
.filter(Boolean);
|
|
2713
|
+
return words.length
|
|
2714
|
+
? words.map((word) => word[0]!.toUpperCase() + word.slice(1)).join(" ")
|
|
2715
|
+
: "MCP";
|
|
2495
2716
|
}
|
|
2496
2717
|
|
|
2497
2718
|
/** Return true when this tool must keep its original renderer. */
|
|
@@ -2501,7 +2722,6 @@ export function preservesOriginalRenderer(
|
|
|
2501
2722
|
builtInToolDefinition?: any,
|
|
2502
2723
|
excludeRenderers: readonly string[] = config.excludeRenderers,
|
|
2503
2724
|
): boolean {
|
|
2504
|
-
if (SUBAGENT_TOOL_NAMES.has(toolName)) return true;
|
|
2505
2725
|
if (!excludeRenderers.includes(toolName)) return false;
|
|
2506
2726
|
return [extensionDefinition, builtInToolDefinition].some(
|
|
2507
2727
|
(definition) =>
|
|
@@ -2542,16 +2762,9 @@ function shouldGloballyStyleTool(component: any, patch: GlobalToolRenderPatch):
|
|
|
2542
2762
|
return useCcstyle;
|
|
2543
2763
|
}
|
|
2544
2764
|
|
|
2545
|
-
function shouldUseSelfShell(component: any,
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
const useSelfShell =
|
|
2549
|
-
patch.enabled() &&
|
|
2550
|
-
SUBAGENT_TOOL_NAMES.has(toolName) &&
|
|
2551
|
-
definition != null &&
|
|
2552
|
-
definition.renderShell === undefined;
|
|
2553
|
-
component[COMPONENT_TOOL_SELF_SHELL_MODE] = useSelfShell;
|
|
2554
|
-
return useSelfShell;
|
|
2765
|
+
function shouldUseSelfShell(component: any, _patch: GlobalToolRenderPatch): boolean {
|
|
2766
|
+
component[COMPONENT_TOOL_SELF_SHELL_MODE] = false;
|
|
2767
|
+
return false;
|
|
2555
2768
|
}
|
|
2556
2769
|
|
|
2557
2770
|
function getGloballyStyledTool(component: any, patch: GlobalToolRenderPatch): any {
|
|
@@ -2785,6 +2998,7 @@ export default function (pi: ExtensionAPI, configOverride?: Partial<Config>) {
|
|
|
2785
2998
|
if (configOverride) config = normalizeConfig({ ...config, ...configOverride });
|
|
2786
2999
|
const writeExecutionMetadata = installWriteOverride(pi, new WriteExecutionMetadataStore());
|
|
2787
3000
|
const globalToolRendering = installGlobalToolRendering(writeExecutionMetadata);
|
|
3001
|
+
const toolGrouping = installToolGrouping(() => config.mode === "on");
|
|
2788
3002
|
deactivateLegacyCompactionRendering();
|
|
2789
3003
|
const compactStyle: CompactStyleHooks = installCompactStyle(pi, {
|
|
2790
3004
|
getMode: () => config.mode,
|
|
@@ -2806,11 +3020,11 @@ export default function (pi: ExtensionAPI, configOverride?: Partial<Config>) {
|
|
|
2806
3020
|
handler: async (args, ctx) => {
|
|
2807
3021
|
const arg = args.trim().toLowerCase();
|
|
2808
3022
|
if (!arg || arg === "panel") {
|
|
2809
|
-
await showCcstylePanel(ctx, compactStyle);
|
|
3023
|
+
await showCcstylePanel(ctx, compactStyle, toolGrouping);
|
|
2810
3024
|
return;
|
|
2811
3025
|
}
|
|
2812
3026
|
if (arg === "on" || arg === "off" || arg === "compact") {
|
|
2813
|
-
applyStyleMode(arg, ctx, compactStyle);
|
|
3027
|
+
applyStyleMode(arg, ctx, compactStyle, toolGrouping);
|
|
2814
3028
|
return;
|
|
2815
3029
|
}
|
|
2816
3030
|
if (arg === "status") {
|
|
@@ -2822,6 +3036,7 @@ export default function (pi: ExtensionAPI, configOverride?: Partial<Config>) {
|
|
|
2822
3036
|
});
|
|
2823
3037
|
|
|
2824
3038
|
pi.on("session_start", async (event, ctx) => {
|
|
3039
|
+
toolGrouping.setTheme(ctx.ui.theme);
|
|
2825
3040
|
compactStyle?.onSessionStart(event, ctx);
|
|
2826
3041
|
pendingScrollMessages = 0;
|
|
2827
3042
|
assistantMessageActive = false;
|
|
@@ -2831,6 +3046,7 @@ export default function (pi: ExtensionAPI, configOverride?: Partial<Config>) {
|
|
|
2831
3046
|
});
|
|
2832
3047
|
|
|
2833
3048
|
pi.on("session_compact", async (event, ctx) => {
|
|
3049
|
+
toolGrouping.setTheme(ctx.ui.theme);
|
|
2834
3050
|
compactStyle?.onSessionCompact(event, ctx);
|
|
2835
3051
|
// Compaction rebuilds the transcript without session_start. Rebind after
|
|
2836
3052
|
// other TUI extensions may have replaced the root input dispatcher.
|
|
@@ -2864,6 +3080,7 @@ export default function (pi: ExtensionAPI, configOverride?: Partial<Config>) {
|
|
|
2864
3080
|
});
|
|
2865
3081
|
|
|
2866
3082
|
pi.on("tool_execution_start", async (event, ctx) => {
|
|
3083
|
+
toolGrouping.setTheme(ctx.ui.theme);
|
|
2867
3084
|
compactStyle?.onToolExecutionStart(event, ctx);
|
|
2868
3085
|
});
|
|
2869
3086
|
|
|
@@ -2880,6 +3097,7 @@ export default function (pi: ExtensionAPI, configOverride?: Partial<Config>) {
|
|
|
2880
3097
|
writeExecutionMetadata.clear();
|
|
2881
3098
|
teardownToolMouseInteraction();
|
|
2882
3099
|
deactivateGlobalToolRendering(globalToolRendering);
|
|
3100
|
+
toolGrouping.shutdown();
|
|
2883
3101
|
deactivateLegacyCompactionRendering();
|
|
2884
3102
|
clearAllAnimations();
|
|
2885
3103
|
});
|