@pi-kaush/pi-tool-call-markers 0.1.0 → 0.1.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
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.2
4
+
5
+ - Keep grouping within one assistant tool batch, so a later pending batch does not reopen completed groups.
6
+ - Wait for every tool in a batch to settle, including tools beyond failed or expanded siblings.
7
+ - Preserve partial and image-bearing results instead of collapsing them.
8
+ - Treat self-rendered tools as owning their full shell and use only their stable header line in grouped summaries.
9
+
10
+ ## 0.1.1
11
+
12
+ - Wait for every call in a contiguous parallel run to settle before grouping successful calls, which prevents completed prefixes and suffixes from repeatedly collapsing around active calls.
13
+
3
14
  ## 0.1.0
4
15
 
5
16
  - Collapse adjacent successful tool calls into one compact block per tool type, each with a gear header and bulleted call summaries.
package/README.md CHANGED
@@ -10,7 +10,9 @@ When several tool calls of the same type succeed in a row, Pi normally renders e
10
10
  - **Bulleted call summaries.** Each call in a group becomes one bullet with a short summary (the tool name is stripped from the bullet since the header already names the tool).
11
11
  - **Vertical spacing between tool types.** A blank line separates one tool group from the next.
12
12
  - **Hanging indent for wrapped bullets.** When a summary wraps, continuation lines align under the bullet text rather than under the gear.
13
- - **Boundaries stay separate.** Visible thinking or text, still-running (active) calls, and failed calls split groups, so they never get silently merged.
13
+ - **Tool batches settle before grouping.** A tool batch stays ungrouped while any sibling call is active. Completed batches remain stable when a later assistant turn starts, and successful calls group around failed-call boundaries after the batch settles.
14
+ - **Self-rendered tools keep their shell.** Tools that own their framing keep singleton previews intact. Grouped summaries use only their stable header instead of scraping preview or diff lines.
15
+ - **Partial and image results stay visible.** Streaming progress and image-bearing results are not collapsed into text-only groups.
14
16
  - **Errors expand in place.** A failed call keeps its own block and shows its full detail.
15
17
  - **Ctrl+O restores full blocks.** Expanding tools (`setToolsExpanded(true)`) brings back Pi's individual full blocks, results and all.
16
18
  - **Adjacent thinking blocks combine.** Only directly adjacent `thinking` blocks merge into one; a non-thinking block between them keeps them separate. Malformed thinking content safely falls back to Pi's renderer exactly once, so the display never breaks.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-kaush/pi-tool-call-markers",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Collapse adjacent successful tool calls into one compact, gear-headed block per tool type in Pi's transcript.",
5
5
  "license": "MIT",
6
6
  "author": "Kaushik Gopal",
package/src/index.ts CHANGED
@@ -47,7 +47,10 @@ type ToolExecutionRow = {
47
47
  args?: unknown;
48
48
  expanded?: boolean;
49
49
  isPartial?: boolean;
50
- result?: { isError?: boolean };
50
+ result?: {
51
+ isError?: boolean;
52
+ content?: Array<{ type?: unknown }>;
53
+ };
51
54
  contentBox?: ComponentContainer;
52
55
  contentText?: TextComponent;
53
56
  selfRenderContainer?: ComponentContainer;
@@ -204,14 +207,18 @@ function hideResultImages(row: ToolExecutionRow): void {
204
207
  }
205
208
 
206
209
  function collapseSuccessfulResult(row: ToolExecutionRow): void {
207
- if (row.expanded !== false || !row.result || row.result.isError) return;
210
+ if (
211
+ row.expanded !== false ||
212
+ row.isPartial !== false ||
213
+ !row.result ||
214
+ row.result.isError ||
215
+ hasImageResult(row) ||
216
+ row.getRenderShell?.() === "self"
217
+ )
218
+ return;
208
219
 
209
220
  const collapsed = row.hasRendererDefinition?.()
210
- ? removeResultComponent(
211
- row.getRenderShell?.() === "self"
212
- ? row.selfRenderContainer
213
- : row.contentBox,
214
- )
221
+ ? removeResultComponent(row.contentBox)
215
222
  : collapseGenericResult(row);
216
223
  if (collapsed) hideResultImages(row);
217
224
  }
@@ -222,15 +229,55 @@ function isToolExecutionRow(
222
229
  return component instanceof ToolExecutionComponent;
223
230
  }
224
231
 
232
+ function isAssistantMessageRow(component: unknown): boolean {
233
+ return component instanceof AssistantMessageComponent;
234
+ }
235
+
236
+ function hasImageResult(row: ToolExecutionRow): boolean {
237
+ return (
238
+ row.result?.content?.some((content) => content.type === "image") === true ||
239
+ (row.imageComponents?.length ?? 0) > 0 ||
240
+ (row.imageSpacers?.length ?? 0) > 0
241
+ );
242
+ }
243
+
225
244
  function isCollapsibleSuccess(row: ToolExecutionRow): boolean {
226
245
  return (
227
246
  row.expanded === false &&
228
247
  row.isPartial === false &&
229
248
  !!row.result &&
230
- !row.result.isError
249
+ !row.result.isError &&
250
+ !hasImageResult(row)
231
251
  );
232
252
  }
233
253
 
254
+ function isSettledToolRow(row: ToolExecutionRow): boolean {
255
+ return row.isPartial === false && !!row.result;
256
+ }
257
+
258
+ function hasUnsettledToolInBatch(
259
+ children: unknown[],
260
+ index: number,
261
+ renderAt: (index: number) => string[],
262
+ ): boolean {
263
+ for (const direction of [-1, 1] as const) {
264
+ for (
265
+ let candidateIndex = index + direction;
266
+ candidateIndex >= 0 && candidateIndex < children.length;
267
+ candidateIndex += direction
268
+ ) {
269
+ const candidate = children[candidateIndex];
270
+ if (isAssistantMessageRow(candidate)) break;
271
+ if (isToolExecutionRow(candidate)) {
272
+ if (!isSettledToolRow(candidate)) return true;
273
+ continue;
274
+ }
275
+ if (renderAt(candidateIndex).some(hasVisibleContent)) break;
276
+ }
277
+ }
278
+ return false;
279
+ }
280
+
234
281
  function renderComponent(component: unknown, width: number): string[] {
235
282
  if (!component || typeof (component as ComponentLike).render !== "function")
236
283
  return [];
@@ -313,11 +360,12 @@ function renderedCallSummary(
313
360
  component = row.contentBox.children[0] as ComponentLike | undefined;
314
361
  }
315
362
 
363
+ const selfRendered = row.getRenderShell?.() === "self";
316
364
  if (component && typeof component.render === "function") {
317
365
  const visibleLines = component
318
366
  .render(Math.max(1, width))
319
367
  .filter(hasVisibleContent);
320
- const rendered = visibleLines.slice(0, 3);
368
+ const rendered = visibleLines.slice(0, selfRendered ? 1 : 3);
321
369
  const line = rendered[0];
322
370
  if (line) {
323
371
  const first = trimRenderedLine(line);
@@ -353,6 +401,8 @@ function renderedCallSummary(
353
401
  }
354
402
  }
355
403
 
404
+ if (selfRendered) return theme.fg("muted", "(details omitted)");
405
+
356
406
  const fallback = compactArgs(row.args);
357
407
  return fallback
358
408
  ? theme.fg("accent", fallback)
@@ -550,6 +600,10 @@ function renderContainerWithToolGroups(
550
600
  lines.push(...renderAt(index));
551
601
  continue;
552
602
  }
603
+ if (hasUnsettledToolInBatch(children, index, renderAt)) {
604
+ lines.push(...renderAt(index));
605
+ continue;
606
+ }
553
607
 
554
608
  const group: ToolExecutionRow[] = [child];
555
609
  let lastMemberIndex = index;
@@ -559,6 +613,7 @@ function renderContainerWithToolGroups(
559
613
  candidateIndex++
560
614
  ) {
561
615
  const candidate = children[candidateIndex];
616
+ if (isAssistantMessageRow(candidate)) break;
562
617
  if (isToolExecutionRow(candidate)) {
563
618
  if (!isCollapsibleSuccess(candidate)) break;
564
619
  group.push(candidate);