@pi-kaush/pi-tool-call-markers 0.3.1 → 0.3.2

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 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-kaush/pi-tool-call-markers",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Compact and group Pi tool calls, with a bundled extension for adjacent thinking blocks.",
5
5
  "license": "MIT",
6
6
  "author": "Kaushik Gopal",
package/src/index.ts CHANGED
@@ -64,6 +64,8 @@ type ToolExecutionRow = {
64
64
  imageSpacers?: unknown[];
65
65
  getRenderShell?(): "default" | "self";
66
66
  getTextOutput?(): string;
67
+ setExpanded?(expanded: boolean): void;
68
+ handleMouse?(event: unknown): unknown;
67
69
  };
68
70
 
69
71
  type PresentationPatchState = {
@@ -76,8 +78,10 @@ type PresentationPatchState = {
76
78
  rowGroups: WeakMap<ToolExecutionRow, ToolExecutionRow[]>;
77
79
  originalRender: (width: number) => string[];
78
80
  originalUpdateDisplay: () => void;
81
+ originalHandleMouse?: (this: ToolExecutionRow, event: unknown) => unknown;
79
82
  patchedRender?: (width: number) => string[];
80
83
  patchedUpdateDisplay?: () => void;
84
+ patchedHandleMouse?: (this: ToolExecutionRow, event: unknown) => unknown;
81
85
  };
82
86
 
83
87
  type GroupingPatchState = {
@@ -90,6 +94,10 @@ type GroupingPatchState = {
90
94
 
91
95
  type GroupRenderCache = {
92
96
  lines: string[];
97
+ // Per-member click-routing accounting for the group block: the first
98
+ // member absorbs the leading blank and the tool-name header, later
99
+ // members carry their own line (plus edit diffs).
100
+ heights: Array<{ component: ToolExecutionRow; height: number }>;
93
101
  members: ToolExecutionRow[];
94
102
  memberVersions: number[];
95
103
  themeSample: string;
@@ -1278,10 +1286,11 @@ function renderGroupedCallLines(
1278
1286
  rows: ToolExecutionRow[],
1279
1287
  width: number,
1280
1288
  theme: ThemeLike,
1281
- ): string[] {
1282
- const lines: string[] = [];
1289
+ ): Array<{ row: ToolExecutionRow; lines: string[] }> {
1290
+ const segments: Array<{ row: ToolExecutionRow; lines: string[] }> = [];
1283
1291
  let previousToolName: string | undefined;
1284
1292
  for (const row of rows) {
1293
+ const lines: string[] = [];
1285
1294
  const toolName = row.toolName ?? "tool";
1286
1295
  if (toolName !== previousToolName) {
1287
1296
  lines.push(
@@ -1304,8 +1313,9 @@ function renderGroupedCallLines(
1304
1313
  );
1305
1314
  if (row.toolName === "edit")
1306
1315
  lines.push(...renderedEditDiffLines(row, theme));
1316
+ segments.push({ row, lines });
1307
1317
  }
1308
- return lines;
1318
+ return segments;
1309
1319
  }
1310
1320
 
1311
1321
  function sameMembers(
@@ -1331,14 +1341,24 @@ function sameMemberVersions(
1331
1341
  );
1332
1342
  }
1333
1343
 
1344
+ type GroupBlock = {
1345
+ lines: string[];
1346
+ // Click-routing accounting aligned with `lines`: one entry per drawn row
1347
+ // segment, so mouse events land on the member that owns the line.
1348
+ heights: Array<{ component: ToolExecutionRow; height: number }>;
1349
+ };
1350
+
1334
1351
  function renderGroupedToolRows(
1335
1352
  row: ToolExecutionRow,
1336
1353
  rows: ToolExecutionRow[],
1337
1354
  width: number,
1338
1355
  state: PresentationPatchState,
1339
- ): string[] {
1356
+ ): GroupBlock {
1340
1357
  const theme = state.theme;
1341
- if (!theme) return state.originalRender.call(row, width);
1358
+ if (!theme) {
1359
+ const lines = state.originalRender.call(row, width);
1360
+ return { lines, heights: [{ component: row, height: lines.length }] };
1361
+ }
1342
1362
  const themeSample = themeSampleFor(theme);
1343
1363
  const cached = state.groupCache.get(row);
1344
1364
  const hasLiveMembers = rows.some(isLiveRow);
@@ -1350,32 +1370,52 @@ function renderGroupedToolRows(
1350
1370
  sameMembers(cached.members, rows) &&
1351
1371
  sameMemberVersions(rows, cached.memberVersions, state)
1352
1372
  ) {
1353
- return cached.lines;
1373
+ return { lines: cached.lines, heights: cached.heights };
1354
1374
  }
1355
1375
 
1356
1376
  const layout = insetLayout(width);
1357
- const body = renderGroupedCallLines(rows, layout.contentWidth, theme);
1358
- const lines = ["", ...insetLines(body, width)];
1377
+ const segments = renderGroupedCallLines(rows, layout.contentWidth, theme);
1378
+ const lines = [
1379
+ "",
1380
+ ...insetLines(
1381
+ segments.flatMap((s) => s.lines),
1382
+ width,
1383
+ ),
1384
+ ];
1385
+ // The leading blank and the first segment (tool-name header + call line)
1386
+ // both belong to the first member; every later member owns its lines.
1387
+ const heights = segments.map((segment, index) => ({
1388
+ component: segment.row,
1389
+ height: index === 0 ? segment.lines.length + 1 : segment.lines.length,
1390
+ }));
1359
1391
  if (hasLiveMembers) {
1360
1392
  state.groupCache.delete(row);
1361
1393
  } else {
1362
1394
  state.groupCache.set(row, {
1363
1395
  lines,
1396
+ heights,
1364
1397
  members: [...rows],
1365
1398
  memberVersions: rows.map((member) => state.rowVersions.get(member) ?? 0),
1366
1399
  themeSample,
1367
1400
  width,
1368
1401
  });
1369
1402
  }
1370
- return lines;
1403
+ return { lines, heights };
1371
1404
  }
1372
1405
 
1406
+ type ContainerRender = {
1407
+ lines: string[];
1408
+ // Click-routing accounting aligned with `lines`.
1409
+ heights: Array<{ component: unknown; height: number }>;
1410
+ };
1411
+
1373
1412
  function renderContainerWithToolGroups(
1374
1413
  children: unknown[],
1375
1414
  width: number,
1376
1415
  presentation: PresentationPatchState,
1377
- ): string[] {
1416
+ ): ContainerRender {
1378
1417
  const lines: string[] = [];
1418
+ const heights: Array<{ component: unknown; height: number }> = [];
1379
1419
  const rendered = new Map<number, string[]>();
1380
1420
  const renderAt = (index: number): string[] => {
1381
1421
  const cached = rendered.get(index);
@@ -1391,7 +1431,9 @@ function renderContainerWithToolGroups(
1391
1431
  !isToolExecutionRow(child) ||
1392
1432
  !isGroupableToolRow(child, children, index, renderAt, presentation)
1393
1433
  ) {
1394
- lines.push(...renderAt(index));
1434
+ const drawn = renderAt(index);
1435
+ lines.push(...drawn);
1436
+ heights.push({ component: child, height: drawn.length });
1395
1437
  continue;
1396
1438
  }
1397
1439
 
@@ -1426,7 +1468,9 @@ function renderContainerWithToolGroups(
1426
1468
  }
1427
1469
 
1428
1470
  if (group.length === 1) {
1429
- lines.push(...renderAt(index));
1471
+ const drawn = renderAt(index);
1472
+ lines.push(...drawn);
1473
+ heights.push({ component: child, height: drawn.length });
1430
1474
  continue;
1431
1475
  }
1432
1476
 
@@ -1434,11 +1478,13 @@ function renderContainerWithToolGroups(
1434
1478
  // Use a live member while any call is pending, then the first member once
1435
1479
  // settled. Either way, the grouped row keeps the same number of lines.
1436
1480
  const shellRow = group.find(isLiveRow) ?? child;
1437
- lines.push(...renderGroupedToolRows(shellRow, group, width, presentation));
1481
+ const block = renderGroupedToolRows(shellRow, group, width, presentation);
1482
+ lines.push(...block.lines);
1483
+ heights.push(...block.heights);
1438
1484
  index = lastMemberIndex;
1439
1485
  }
1440
1486
 
1441
- return lines;
1487
+ return { lines, heights };
1442
1488
  }
1443
1489
 
1444
1490
  // TODO: Replace prototype patching with a public Pi tool/transcript rendering API when available.
@@ -1483,11 +1529,20 @@ function installGroupingPatch(
1483
1529
  // inset) decorate children through the shared hooks before grouping
1484
1530
  // rewrites the rows; delegation alone cannot reach them from here.
1485
1531
  restoreHooks = runChatContainerHooks(this, children, width);
1486
- return renderContainerWithToolGroups(
1532
+ const drawn = renderContainerWithToolGroups(
1487
1533
  children,
1488
1534
  width,
1489
1535
  state.presentation,
1490
1536
  );
1537
+ // The grouping path bypasses the native render that refreshes the
1538
+ // container's mouse-layout cache. Publish the drawn accounting so
1539
+ // click-to-expand (Pi routes by per-child line heights) keeps landing
1540
+ // on the row that actually owns the line, including grouped members.
1541
+ (this as { mouseLayout?: unknown }).mouseLayout = {
1542
+ width,
1543
+ children: drawn.heights,
1544
+ };
1545
+ return drawn.lines;
1491
1546
  } catch {
1492
1547
  return state.originalRender.call(this, width);
1493
1548
  } finally {
@@ -1574,6 +1629,34 @@ function installPresentationPatch(): PresentationPatchState | undefined {
1574
1629
  state.collapsedCache.delete(this);
1575
1630
  state.originalUpdateDisplay.call(this);
1576
1631
  };
1632
+ // Pi 0.85 wraps settled tool results in a click-to-expand MouseRegion.
1633
+ // The custom collapsed row replaces those lines with a single summary, so
1634
+ // the region never receives the click; toggle expansion here instead.
1635
+ let patchedHandleMouse:
1636
+ | ((this: ToolExecutionRow, event: unknown) => unknown)
1637
+ | undefined;
1638
+ if (typeof proto.handleMouse === "function") {
1639
+ state.originalHandleMouse = proto.handleMouse;
1640
+ patchedHandleMouse = function handleMouseWithCollapsedToggle(
1641
+ this: ToolExecutionRow,
1642
+ event: unknown,
1643
+ ): unknown {
1644
+ const mouseEvent = event as { type?: unknown; button?: unknown };
1645
+ if (
1646
+ state.theme &&
1647
+ this.expanded === false &&
1648
+ !!this.result &&
1649
+ mouseEvent?.type === "click" &&
1650
+ mouseEvent.button === "left" &&
1651
+ typeof this.setExpanded === "function"
1652
+ ) {
1653
+ this.setExpanded(true);
1654
+ return { handled: true };
1655
+ }
1656
+ return state.originalHandleMouse?.call(this, event);
1657
+ };
1658
+ proto.handleMouse = patchedHandleMouse;
1659
+ }
1577
1660
  const patchedRender = function renderWithToolPresentation(
1578
1661
  this: ToolExecutionRow,
1579
1662
  width: number,
@@ -1680,6 +1763,7 @@ function uninstallPresentationPatch(
1680
1763
  [PRESENTATION_PATCHED]?: PresentationPatchState;
1681
1764
  render?: (width: number) => string[];
1682
1765
  updateDisplay?: () => void;
1766
+ handleMouse?: (event: unknown) => unknown;
1683
1767
  };
1684
1768
  if (
1685
1769
  proto[PRESENTATION_PATCHED] !== state ||
@@ -1690,6 +1774,16 @@ function uninstallPresentationPatch(
1690
1774
  }
1691
1775
  proto.render = state.originalRender;
1692
1776
  proto.updateDisplay = state.originalUpdateDisplay;
1777
+ if (
1778
+ state.patchedHandleMouse &&
1779
+ proto.handleMouse === state.patchedHandleMouse
1780
+ ) {
1781
+ if (state.originalHandleMouse) {
1782
+ proto.handleMouse = state.originalHandleMouse;
1783
+ } else {
1784
+ delete proto.handleMouse;
1785
+ }
1786
+ }
1693
1787
  delete proto[PRESENTATION_PATCHED];
1694
1788
  }
1695
1789