@pi-kaush/pi-tool-call-markers 0.3.1 → 0.3.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/CHANGELOG.md +8 -0
- package/README.md +7 -4
- package/package.json +1 -1
- package/src/index.ts +117 -17
- package/src/muted.ts +14 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Restore Pi 0.85's click-to-expand under the custom collapsed rows. Pi routes
|
|
6
|
+
mouse clicks by per-child rendered line heights, and the grouping render
|
|
7
|
+
bypassed the native render that refreshes that cache, so clicks on anything
|
|
8
|
+
drawn after the first tool call landed nowhere (or one line off after a
|
|
9
|
+
resize). The grouped render now publishes the drawn accounting, and a
|
|
10
|
+
collapsed tool row toggles expansion on left-click like Pi's native result
|
|
11
|
+
region.
|
|
12
|
+
|
|
5
13
|
- Restyle subagent rows: the subagent marker is now `↪` (was `&`), and single
|
|
6
14
|
calls drop the `subagent` tool label — `↪ 🤖 [coder][c3po] Implement the…` —
|
|
7
15
|
with chain/parallel step lines reordered to emoji, profile badge, name badge
|
package/README.md
CHANGED
|
@@ -16,13 +16,16 @@ Collapsed tool rows use semantic theme colors with no gear, background fill, box
|
|
|
16
16
|
|
|
17
17
|
```text
|
|
18
18
|
% Read
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
│ src/a.ts 42 lines
|
|
20
|
+
│ src/b.ts 18 lines
|
|
21
|
+
|
|
22
|
+
% Bash
|
|
23
|
+
│ $: npm test → done
|
|
21
24
|
```
|
|
22
25
|
|
|
23
26
|
- **Two-column outer inset.** Tool markers and image output align with an inset conversation surface. Very narrow terminals reduce the decoration before useful content.
|
|
24
|
-
- **`%` tool headings.** A singleton stays on one line when its summary and outcome fit. A multi-call group has one `%` heading per contiguous tool type.
|
|
25
|
-
-
|
|
27
|
+
- **`%` tool headings.** A singleton stays on one line when its summary and outcome fit. A multi-call group has one `%` heading per contiguous tool type, with a blank line before each subsequent heading.
|
|
28
|
+
- **`│` grouped children.** A vertical rail — fainter than the call text — spans the members of a multi-call group; singletons carry no rail.
|
|
26
29
|
- **Semantic, low-contrast status.** Tool names are emphasized, summaries and settled metadata are muted, pending state is warning-colored, and failures remain error-colored. Ordinary tool states have no background.
|
|
27
30
|
- **Width-safe outcome tails.** Long summaries truncate before useful tails such as `→ done`, `→ 42 lines`, `→ +2/-1`, or a `bash` duration.
|
|
28
31
|
- **Stable running groups.** Adjacent calls group as they appear. Pending state and elapsed `bash` time settle into the final outcome without changing the row count.
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -16,11 +16,12 @@ import {
|
|
|
16
16
|
} from "./bash-block.ts";
|
|
17
17
|
import { runChatContainerHooks } from "./container-hooks.ts";
|
|
18
18
|
import { installInfoVisibility } from "./info-visibility.ts";
|
|
19
|
-
import { fgCollapsed } from "./muted.ts";
|
|
19
|
+
import { fgCollapsed, fgCollapsedRail } from "./muted.ts";
|
|
20
20
|
|
|
21
21
|
const OUTER_INSET = 2;
|
|
22
22
|
const GROUP_MARKER = "%";
|
|
23
23
|
const SUBAGENT_MARKER = "↪";
|
|
24
|
+
const GROUP_CALL_MARKER = "│";
|
|
24
25
|
const PRESENTATION_PATCHED = Symbol.for("kg.pi.toolPresentation.v3");
|
|
25
26
|
const LEGACY_PRESENTATION_PATCHED = Symbol.for("kg.pi.toolPresentation.v2");
|
|
26
27
|
const GROUPING_PATCHED = Symbol.for("kg.pi.toolGrouping.v1");
|
|
@@ -64,6 +65,8 @@ type ToolExecutionRow = {
|
|
|
64
65
|
imageSpacers?: unknown[];
|
|
65
66
|
getRenderShell?(): "default" | "self";
|
|
66
67
|
getTextOutput?(): string;
|
|
68
|
+
setExpanded?(expanded: boolean): void;
|
|
69
|
+
handleMouse?(event: unknown): unknown;
|
|
67
70
|
};
|
|
68
71
|
|
|
69
72
|
type PresentationPatchState = {
|
|
@@ -76,8 +79,10 @@ type PresentationPatchState = {
|
|
|
76
79
|
rowGroups: WeakMap<ToolExecutionRow, ToolExecutionRow[]>;
|
|
77
80
|
originalRender: (width: number) => string[];
|
|
78
81
|
originalUpdateDisplay: () => void;
|
|
82
|
+
originalHandleMouse?: (this: ToolExecutionRow, event: unknown) => unknown;
|
|
79
83
|
patchedRender?: (width: number) => string[];
|
|
80
84
|
patchedUpdateDisplay?: () => void;
|
|
85
|
+
patchedHandleMouse?: (this: ToolExecutionRow, event: unknown) => unknown;
|
|
81
86
|
};
|
|
82
87
|
|
|
83
88
|
type GroupingPatchState = {
|
|
@@ -90,6 +95,10 @@ type GroupingPatchState = {
|
|
|
90
95
|
|
|
91
96
|
type GroupRenderCache = {
|
|
92
97
|
lines: string[];
|
|
98
|
+
// Per-member click-routing accounting for the group block: the first
|
|
99
|
+
// member absorbs the leading blank and the tool-name header, later
|
|
100
|
+
// members carry their own line (plus edit diffs).
|
|
101
|
+
heights: Array<{ component: ToolExecutionRow; height: number }>;
|
|
93
102
|
members: ToolExecutionRow[];
|
|
94
103
|
memberVersions: number[];
|
|
95
104
|
themeSample: string;
|
|
@@ -1278,12 +1287,18 @@ function renderGroupedCallLines(
|
|
|
1278
1287
|
rows: ToolExecutionRow[],
|
|
1279
1288
|
width: number,
|
|
1280
1289
|
theme: ThemeLike,
|
|
1281
|
-
): string[] {
|
|
1282
|
-
const lines: string[] = [];
|
|
1290
|
+
): Array<{ row: ToolExecutionRow; lines: string[] }> {
|
|
1291
|
+
const segments: Array<{ row: ToolExecutionRow; lines: string[] }> = [];
|
|
1283
1292
|
let previousToolName: string | undefined;
|
|
1284
1293
|
for (const row of rows) {
|
|
1294
|
+
const lines: string[] = [];
|
|
1285
1295
|
const toolName = row.toolName ?? "tool";
|
|
1286
1296
|
if (toolName !== previousToolName) {
|
|
1297
|
+
// One blank line between tool-name subgroups, mirroring the block's
|
|
1298
|
+
// leading blank so each `%` heading starts its own visual run. The
|
|
1299
|
+
// blank belongs to the new run's first row, keeping the per-member
|
|
1300
|
+
// click-routing heights aligned with the drawn lines.
|
|
1301
|
+
if (previousToolName !== undefined) lines.push("");
|
|
1287
1302
|
lines.push(
|
|
1288
1303
|
`${fgCollapsed(theme, "muted", GROUP_MARKER)} ${fgCollapsed(theme, "toolTitle", toolName, true)}`,
|
|
1289
1304
|
);
|
|
@@ -1291,7 +1306,7 @@ function renderGroupedCallLines(
|
|
|
1291
1306
|
}
|
|
1292
1307
|
|
|
1293
1308
|
const color = rowHasFailed(row) ? "error" : "muted";
|
|
1294
|
-
const prefix = ` ${
|
|
1309
|
+
const prefix = ` ${fgCollapsedRail(theme, color, GROUP_CALL_MARKER)} `;
|
|
1295
1310
|
const budget = Math.max(1, width - visibleWidth(prefix));
|
|
1296
1311
|
const label = groupedChildLabel(row, budget, theme, color);
|
|
1297
1312
|
const outcome = collapsedOutcome(row, budget, theme);
|
|
@@ -1304,8 +1319,9 @@ function renderGroupedCallLines(
|
|
|
1304
1319
|
);
|
|
1305
1320
|
if (row.toolName === "edit")
|
|
1306
1321
|
lines.push(...renderedEditDiffLines(row, theme));
|
|
1322
|
+
segments.push({ row, lines });
|
|
1307
1323
|
}
|
|
1308
|
-
return
|
|
1324
|
+
return segments;
|
|
1309
1325
|
}
|
|
1310
1326
|
|
|
1311
1327
|
function sameMembers(
|
|
@@ -1331,14 +1347,24 @@ function sameMemberVersions(
|
|
|
1331
1347
|
);
|
|
1332
1348
|
}
|
|
1333
1349
|
|
|
1350
|
+
type GroupBlock = {
|
|
1351
|
+
lines: string[];
|
|
1352
|
+
// Click-routing accounting aligned with `lines`: one entry per drawn row
|
|
1353
|
+
// segment, so mouse events land on the member that owns the line.
|
|
1354
|
+
heights: Array<{ component: ToolExecutionRow; height: number }>;
|
|
1355
|
+
};
|
|
1356
|
+
|
|
1334
1357
|
function renderGroupedToolRows(
|
|
1335
1358
|
row: ToolExecutionRow,
|
|
1336
1359
|
rows: ToolExecutionRow[],
|
|
1337
1360
|
width: number,
|
|
1338
1361
|
state: PresentationPatchState,
|
|
1339
|
-
):
|
|
1362
|
+
): GroupBlock {
|
|
1340
1363
|
const theme = state.theme;
|
|
1341
|
-
if (!theme)
|
|
1364
|
+
if (!theme) {
|
|
1365
|
+
const lines = state.originalRender.call(row, width);
|
|
1366
|
+
return { lines, heights: [{ component: row, height: lines.length }] };
|
|
1367
|
+
}
|
|
1342
1368
|
const themeSample = themeSampleFor(theme);
|
|
1343
1369
|
const cached = state.groupCache.get(row);
|
|
1344
1370
|
const hasLiveMembers = rows.some(isLiveRow);
|
|
@@ -1350,32 +1376,52 @@ function renderGroupedToolRows(
|
|
|
1350
1376
|
sameMembers(cached.members, rows) &&
|
|
1351
1377
|
sameMemberVersions(rows, cached.memberVersions, state)
|
|
1352
1378
|
) {
|
|
1353
|
-
return cached.lines;
|
|
1379
|
+
return { lines: cached.lines, heights: cached.heights };
|
|
1354
1380
|
}
|
|
1355
1381
|
|
|
1356
1382
|
const layout = insetLayout(width);
|
|
1357
|
-
const
|
|
1358
|
-
const lines = [
|
|
1383
|
+
const segments = renderGroupedCallLines(rows, layout.contentWidth, theme);
|
|
1384
|
+
const lines = [
|
|
1385
|
+
"",
|
|
1386
|
+
...insetLines(
|
|
1387
|
+
segments.flatMap((s) => s.lines),
|
|
1388
|
+
width,
|
|
1389
|
+
),
|
|
1390
|
+
];
|
|
1391
|
+
// The leading blank and the first segment (tool-name header + call line)
|
|
1392
|
+
// both belong to the first member; every later member owns its lines.
|
|
1393
|
+
const heights = segments.map((segment, index) => ({
|
|
1394
|
+
component: segment.row,
|
|
1395
|
+
height: index === 0 ? segment.lines.length + 1 : segment.lines.length,
|
|
1396
|
+
}));
|
|
1359
1397
|
if (hasLiveMembers) {
|
|
1360
1398
|
state.groupCache.delete(row);
|
|
1361
1399
|
} else {
|
|
1362
1400
|
state.groupCache.set(row, {
|
|
1363
1401
|
lines,
|
|
1402
|
+
heights,
|
|
1364
1403
|
members: [...rows],
|
|
1365
1404
|
memberVersions: rows.map((member) => state.rowVersions.get(member) ?? 0),
|
|
1366
1405
|
themeSample,
|
|
1367
1406
|
width,
|
|
1368
1407
|
});
|
|
1369
1408
|
}
|
|
1370
|
-
return lines;
|
|
1409
|
+
return { lines, heights };
|
|
1371
1410
|
}
|
|
1372
1411
|
|
|
1412
|
+
type ContainerRender = {
|
|
1413
|
+
lines: string[];
|
|
1414
|
+
// Click-routing accounting aligned with `lines`.
|
|
1415
|
+
heights: Array<{ component: unknown; height: number }>;
|
|
1416
|
+
};
|
|
1417
|
+
|
|
1373
1418
|
function renderContainerWithToolGroups(
|
|
1374
1419
|
children: unknown[],
|
|
1375
1420
|
width: number,
|
|
1376
1421
|
presentation: PresentationPatchState,
|
|
1377
|
-
):
|
|
1422
|
+
): ContainerRender {
|
|
1378
1423
|
const lines: string[] = [];
|
|
1424
|
+
const heights: Array<{ component: unknown; height: number }> = [];
|
|
1379
1425
|
const rendered = new Map<number, string[]>();
|
|
1380
1426
|
const renderAt = (index: number): string[] => {
|
|
1381
1427
|
const cached = rendered.get(index);
|
|
@@ -1391,7 +1437,9 @@ function renderContainerWithToolGroups(
|
|
|
1391
1437
|
!isToolExecutionRow(child) ||
|
|
1392
1438
|
!isGroupableToolRow(child, children, index, renderAt, presentation)
|
|
1393
1439
|
) {
|
|
1394
|
-
|
|
1440
|
+
const drawn = renderAt(index);
|
|
1441
|
+
lines.push(...drawn);
|
|
1442
|
+
heights.push({ component: child, height: drawn.length });
|
|
1395
1443
|
continue;
|
|
1396
1444
|
}
|
|
1397
1445
|
|
|
@@ -1426,7 +1474,9 @@ function renderContainerWithToolGroups(
|
|
|
1426
1474
|
}
|
|
1427
1475
|
|
|
1428
1476
|
if (group.length === 1) {
|
|
1429
|
-
|
|
1477
|
+
const drawn = renderAt(index);
|
|
1478
|
+
lines.push(...drawn);
|
|
1479
|
+
heights.push({ component: child, height: drawn.length });
|
|
1430
1480
|
continue;
|
|
1431
1481
|
}
|
|
1432
1482
|
|
|
@@ -1434,11 +1484,13 @@ function renderContainerWithToolGroups(
|
|
|
1434
1484
|
// Use a live member while any call is pending, then the first member once
|
|
1435
1485
|
// settled. Either way, the grouped row keeps the same number of lines.
|
|
1436
1486
|
const shellRow = group.find(isLiveRow) ?? child;
|
|
1437
|
-
|
|
1487
|
+
const block = renderGroupedToolRows(shellRow, group, width, presentation);
|
|
1488
|
+
lines.push(...block.lines);
|
|
1489
|
+
heights.push(...block.heights);
|
|
1438
1490
|
index = lastMemberIndex;
|
|
1439
1491
|
}
|
|
1440
1492
|
|
|
1441
|
-
return lines;
|
|
1493
|
+
return { lines, heights };
|
|
1442
1494
|
}
|
|
1443
1495
|
|
|
1444
1496
|
// TODO: Replace prototype patching with a public Pi tool/transcript rendering API when available.
|
|
@@ -1483,11 +1535,20 @@ function installGroupingPatch(
|
|
|
1483
1535
|
// inset) decorate children through the shared hooks before grouping
|
|
1484
1536
|
// rewrites the rows; delegation alone cannot reach them from here.
|
|
1485
1537
|
restoreHooks = runChatContainerHooks(this, children, width);
|
|
1486
|
-
|
|
1538
|
+
const drawn = renderContainerWithToolGroups(
|
|
1487
1539
|
children,
|
|
1488
1540
|
width,
|
|
1489
1541
|
state.presentation,
|
|
1490
1542
|
);
|
|
1543
|
+
// The grouping path bypasses the native render that refreshes the
|
|
1544
|
+
// container's mouse-layout cache. Publish the drawn accounting so
|
|
1545
|
+
// click-to-expand (Pi routes by per-child line heights) keeps landing
|
|
1546
|
+
// on the row that actually owns the line, including grouped members.
|
|
1547
|
+
(this as { mouseLayout?: unknown }).mouseLayout = {
|
|
1548
|
+
width,
|
|
1549
|
+
children: drawn.heights,
|
|
1550
|
+
};
|
|
1551
|
+
return drawn.lines;
|
|
1491
1552
|
} catch {
|
|
1492
1553
|
return state.originalRender.call(this, width);
|
|
1493
1554
|
} finally {
|
|
@@ -1574,6 +1635,34 @@ function installPresentationPatch(): PresentationPatchState | undefined {
|
|
|
1574
1635
|
state.collapsedCache.delete(this);
|
|
1575
1636
|
state.originalUpdateDisplay.call(this);
|
|
1576
1637
|
};
|
|
1638
|
+
// Pi 0.85 wraps settled tool results in a click-to-expand MouseRegion.
|
|
1639
|
+
// The custom collapsed row replaces those lines with a single summary, so
|
|
1640
|
+
// the region never receives the click; toggle expansion here instead.
|
|
1641
|
+
let patchedHandleMouse:
|
|
1642
|
+
| ((this: ToolExecutionRow, event: unknown) => unknown)
|
|
1643
|
+
| undefined;
|
|
1644
|
+
if (typeof proto.handleMouse === "function") {
|
|
1645
|
+
state.originalHandleMouse = proto.handleMouse;
|
|
1646
|
+
patchedHandleMouse = function handleMouseWithCollapsedToggle(
|
|
1647
|
+
this: ToolExecutionRow,
|
|
1648
|
+
event: unknown,
|
|
1649
|
+
): unknown {
|
|
1650
|
+
const mouseEvent = event as { type?: unknown; button?: unknown };
|
|
1651
|
+
if (
|
|
1652
|
+
state.theme &&
|
|
1653
|
+
this.expanded === false &&
|
|
1654
|
+
!!this.result &&
|
|
1655
|
+
mouseEvent?.type === "click" &&
|
|
1656
|
+
mouseEvent.button === "left" &&
|
|
1657
|
+
typeof this.setExpanded === "function"
|
|
1658
|
+
) {
|
|
1659
|
+
this.setExpanded(true);
|
|
1660
|
+
return { handled: true };
|
|
1661
|
+
}
|
|
1662
|
+
return state.originalHandleMouse?.call(this, event);
|
|
1663
|
+
};
|
|
1664
|
+
proto.handleMouse = patchedHandleMouse;
|
|
1665
|
+
}
|
|
1577
1666
|
const patchedRender = function renderWithToolPresentation(
|
|
1578
1667
|
this: ToolExecutionRow,
|
|
1579
1668
|
width: number,
|
|
@@ -1680,6 +1769,7 @@ function uninstallPresentationPatch(
|
|
|
1680
1769
|
[PRESENTATION_PATCHED]?: PresentationPatchState;
|
|
1681
1770
|
render?: (width: number) => string[];
|
|
1682
1771
|
updateDisplay?: () => void;
|
|
1772
|
+
handleMouse?: (event: unknown) => unknown;
|
|
1683
1773
|
};
|
|
1684
1774
|
if (
|
|
1685
1775
|
proto[PRESENTATION_PATCHED] !== state ||
|
|
@@ -1690,6 +1780,16 @@ function uninstallPresentationPatch(
|
|
|
1690
1780
|
}
|
|
1691
1781
|
proto.render = state.originalRender;
|
|
1692
1782
|
proto.updateDisplay = state.originalUpdateDisplay;
|
|
1783
|
+
if (
|
|
1784
|
+
state.patchedHandleMouse &&
|
|
1785
|
+
proto.handleMouse === state.patchedHandleMouse
|
|
1786
|
+
) {
|
|
1787
|
+
if (state.originalHandleMouse) {
|
|
1788
|
+
proto.handleMouse = state.originalHandleMouse;
|
|
1789
|
+
} else {
|
|
1790
|
+
delete proto.handleMouse;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1693
1793
|
delete proto[PRESENTATION_PATCHED];
|
|
1694
1794
|
}
|
|
1695
1795
|
|
package/src/muted.ts
CHANGED
|
@@ -78,3 +78,17 @@ export function fgCollapsed(
|
|
|
78
78
|
}
|
|
79
79
|
return theme.fg(color, bold && theme.bold ? theme.bold(text) : text);
|
|
80
80
|
}
|
|
81
|
+
|
|
82
|
+
// Decoration rails recede further than collapsed text. No palette token is
|
|
83
|
+
// guaranteed lighter than the collapsed color (e.g. catppuccin-latte's `dim`
|
|
84
|
+
// resolves to the same overlay0 as syntaxComment), so apply the terminal's
|
|
85
|
+
// faint attribute: it lightens toward the background on light themes and
|
|
86
|
+
// darkens on dark ones. Error rails keep their full-strength semantic red.
|
|
87
|
+
export function fgCollapsedRail(
|
|
88
|
+
theme: CollapsedTheme,
|
|
89
|
+
color: string,
|
|
90
|
+
text: string,
|
|
91
|
+
): string {
|
|
92
|
+
const styled = fgCollapsed(theme, color, text);
|
|
93
|
+
return color === "error" ? styled : `\x1b[2m${styled}\x1b[22m`;
|
|
94
|
+
}
|