@pi-kaush/pi-tool-call-markers 0.3.4 → 0.3.6

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,62 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - Read colors from the live palette instead of caching them against the
6
+ theme object. Pi swaps the palette behind a stable theme Proxy, so every
7
+ identity-keyed cache survived a theme switch: collapsed rows, the `+ Thought`
8
+ label, and user `!` blocks kept the old colors until their content changed.
9
+ Probes are map lookups, so the render caches now key on the resolved colors
10
+ (`paletteSample`, including the bash-block line cache) and the label
11
+ resolves its style per call.
12
+
13
+ - Stop grouping from resurrecting a tool that opted out of the transcript. A
14
+ self-rendered tool that draws no lines stays hidden whether it renders alone
15
+ or beside another call: the singleton and grouped paths share one
16
+ draws-nothing rule.
17
+
18
+ - Go inert at the final owner's shutdown. Every prototype wrapper — tool
19
+ presentation, container grouping, user bash blocks, thinking — delegates to
20
+ the original when another extension's wrapper has buried it and removal is
21
+ impossible, so collapsed rows and restyled labels cannot outlive their owner.
22
+ A later install re-enables the same wrappers.
23
+
24
+ - Roll back a container hook that mutates the child list and then throws.
25
+ Its edit used to stick, dropping those rows from every later render. Both
26
+ copies of the shared hook contract changed together.
27
+
28
+ - Own and release `/toggle-info` with the rest of the extension: the
29
+ process-global filter hook and the hidden flag now leave with the last owner
30
+ instead of filtering a transcript this package no longer renders.
31
+
32
+ - Track thinking rows for the `/toggle-info` replay instead of every assistant
33
+ row, capped at 400 entries, so a long session or an abandoned branch cannot
34
+ pin every message the transcript ever showed.
35
+
36
+ - Delete state nothing reads (the row→group map) and the duplicate
37
+ `session_start` handler that applied the collapsed default twice.
38
+
39
+ - Drop the colon after every collapsed-row anchor: glyph rows read
40
+ `│ ● src/a.ts` and bash rows `│ $ npm test`. The anchor runs bold with a
41
+ single space before the call text, so the anchor alone separates the two.
42
+ The whole seam — glyph map, `$` prompt, subagent fallback heading, and the
43
+ tool-token drop — now lives in one function that both the singleton and
44
+ grouped render paths call, and self-rendered labels no longer leak their
45
+ tool name into the row under themes that tag instead of color.
46
+
47
+ - Drop the italics Pi puts on thinking. The hidden `+ Thought` label renders
48
+ plain in every theme — the live spinner keeps its level tint, the settled
49
+ row its muted tone, and a theme that resolves no color still loses the
50
+ italics — and the visible trace stays italic only while it streams, then
51
+ renders as ordinary transcript text once the row settles. The live/settled
52
+ rule is shared with the label, so un-flagged row rebuilds (resize, theme
53
+ switch) keep a live trace italic and a finished one plain.
54
+ `PI_TOOL_CALL_MARKERS_THOUGHT_COLOR=inherit` remains the only opt-out that
55
+ keeps Pi's native italic styling.
56
+
57
+ - Fail open when a theme rejects a thinking token. Pi's Theme throws on
58
+ tokens it does not know, so the label probe now falls back to the plain
59
+ treatment instead of throwing out of the row's update pass.
60
+
5
61
  - Restore Pi 0.85's click-to-expand under the custom collapsed rows. Pi routes
6
62
  mouse clicks by per-child rendered line heights, and the grouping render
7
63
  bypassed the native render that refreshes that cache, so clicks on anything
package/README.md CHANGED
@@ -15,19 +15,16 @@ Restart Pi or run `/reload`.
15
15
  Collapsed tool rows use semantic theme colors with no gear, background fill, box padding, or filled blank rows:
16
16
 
17
17
  ```text
18
- │ Read
19
- │ src/a.ts 42 lines
20
- │ src/b.ts 18 lines
21
- │
22
- │ Bash
23
- │ $: npm test → done
18
+ │ ● src/a.ts 42 lines
19
+ │ ● src/b.ts 18 lines
20
+ │ $ npm test → done
24
21
  ```
25
22
 
26
23
  - **Two-column outer inset.** Tool markers and image output align with an inset conversation surface. Very narrow terminals reduce the decoration before useful content.
27
- - **`│` tool-call rail.** Single calls use a rail beside the summary. Multi-call groups extend it through bold tool-name headings, indented call summaries, and separators between tool types.
28
- - **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.
24
+ - **`│` tool-call rail.** Every collapsed call anchors on a bold glyph instead of its tool name — `●` read, `+` write, `±` edit, `○` local search, `≡` ls, `↗` web search and fetch, and `$` bash; unmapped tools fall back to `*` — with a single space between the anchor and the call text, no joining colon. Grouped rows flow directly across tool types.
25
+ - **Semantic, low-contrast status.** The anchor and the call content share the collapsed mute, pending state is warning-colored, and failures remain error-colored. Ordinary tool states have no background.
29
26
  - **Width-safe outcome tails.** Long summaries truncate before useful tails such as `→ done`, `→ 42 lines`, `→ +2/-1`, or a `bash` duration.
30
- - **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.
27
+ - **Stable running groups.** Adjacent calls group as they appear, including settled failures, which stay error-colored inside the group. Pending state and elapsed `bash` time settle into the final outcome without changing the row count.
31
28
  - **Quiet-turn grouping.** Sequential calls can join across an assistant row with no visible prose or thinking. Visible assistant content remains a boundary.
32
29
  - **MCP and self-rendered tools.** Their stable call labels, compact arguments, pending state, success, and first error line use the same collapsed shell. Native self-rendered details return when expanded.
33
30
  - **Images remain visible.** Image fallback text and terminal image components render below the corresponding marker with the same inset.
@@ -36,7 +33,7 @@ Collapsed tool rows use semantic theme colors with no gear, background fill, box
36
33
 
37
34
  ## Subagent plans
38
35
 
39
- A recognized `subagent` call renders as an unboxed plan in the shared tool aesthetic, marked with `↪` instead of the ordinary `%` tool marker:
36
+ A recognized `subagent` call renders as an unboxed plan in the shared tool aesthetic, marked with `↪` instead of the ordinary `│` tool-call rail:
40
37
 
41
38
  ```text
42
39
  ↪ subagent chain (3 steps) [repo-review]
@@ -52,7 +49,7 @@ Malformed, ambiguous, future, or too-narrow shapes fall back to the generic `↪
52
49
 
53
50
  ## Edit diffs
54
51
 
55
- Settled `edit` calls keep their change visible without expanding: the call line gains a `+added/-removed` outcome stat, and the hunk renders as a bounded diff block underneath (`+` lines in the added tone, `-` in the removed tone, context muted, folded regions as `...`, capped at 12 lines with a count tail). The full native diff — line numbers, intra-line word highlights — still returns with `Ctrl+O`.
52
+ Settled `edit` calls collapse like every other row: the call line keeps its path and a `+added/-removed` outcome stat, and the hunk itself stays out of the collapsed row. `Ctrl+O` restores Pi's full native diff, including line numbers and intra-line word highlights. To keep edits expanded without pressing `Ctrl+O`, list `edit` in `PI_ALWAYS_EXPANDED_TOOL_CALL_MARKERS` (see Configuration).
56
53
 
57
54
  Files and paths in collapsed rows are preserved as displayed by Pi: hyperlink-wrapped paths (Pi wraps `read` call paths in OSC 8 hyperlinks) keep their visible text when sanitized for one-line rows.
58
55
 
@@ -81,7 +78,9 @@ When Pi exposes its per-row hidden-thinking and streaming fields, hidden reasoni
81
78
  + Thought · 2.5s
82
79
  ```
83
80
 
84
- The live label samples Pi's native braille spinner sequence from the content updates Pi already renders; it does not add a timer. The adapter stores the first local streaming timestamp per assistant row in a `WeakMap`. A restored message or an older runtime with no streaming argument uses `+ Thought`. Visible-thinking mode remains native. There is no interval, timeout, render request, model call, or network work.
81
+ The live label samples Pi's native braille spinner sequence from the content updates Pi already renders; it does not add a timer. The adapter stores the first local streaming timestamp per assistant row in a `WeakMap`. A restored message or an older runtime with no streaming argument uses `+ Thought`. There is no interval, timeout, render request, model call, or network work.
82
+
83
+ Pi renders thinking labels and traces italic. This package drops those italics: both labels read as plain collapsed rows (the live spinner keeps its thinking-level tint, the settled row the muted tone, and a theme that resolves no color still loses the italics), and a visible trace stays italic only while it streams, then settles into ordinary transcript text. `PI_TOOL_CALL_MARKERS_THOUGHT_COLOR=inherit` is the opt-out that keeps Pi's native italic `thinkingText` styling.
85
84
 
86
85
  ## Local development
87
86
 
@@ -102,9 +101,18 @@ pi
102
101
 
103
102
  `0`, `false`, `no`, and `off` disable parallel grouping. `1`, `true`, `yes`, and `on` enable it. The value is read when the extension loads.
104
103
 
104
+ To render chosen tools as Pi's native expanded block by default — full result and diff without `Ctrl+O` — list their names:
105
+
106
+ ```fish
107
+ set -lx PI_ALWAYS_EXPANDED_TOOL_CALL_MARKERS edit,write
108
+ pi
109
+ ```
110
+
111
+ Names are comma-separated and matched exactly. Listed rows never join a group, and the list stays authoritative: collapsing the transcript with `Ctrl+O` re-expands them. The value is read when the extension loads.
112
+
105
113
  ### Collapsed-row colors
106
114
 
107
- Collapsed rows (tool calls, `+ Thought`, subagents) default to the theme's `syntaxComment` color — it ships with every Pi theme and reads as a muted tone — with the bolded tool name and the call content sharing it. Failures stay error-colored and live spinners keep their existing tints.
115
+ Collapsed rows (tool calls, `+ Thought`, subagents) default to the theme's `syntaxComment` color — it ships with every Pi theme and reads as a muted tone — with the row's anchor and the call content sharing it. Failures stay error-colored and live spinners keep their existing tints.
108
116
 
109
117
  A theme can override each collapsed kind independently with optional color tokens:
110
118
 
@@ -125,10 +133,14 @@ Pi has no public hook for native tool rows, transcript grouping, or per-message
125
133
 
126
134
  - `ToolExecutionComponent` for collapsed presentation;
127
135
  - `Container` for adjacent grouping; and
128
- - `AssistantMessageComponent.updateContent` for display-only thinking merging and lifecycle labels.
136
+ - `AssistantMessageComponent.updateContent` for display-only thinking merging, lifecycle labels, and the settled trace's italics.
129
137
 
130
138
  Each adapter feature-detects the fields and methods it needs, keeps the original method, uses an idempotency symbol, catches cosmetic failures, and restores the original on `session_shutdown` when it still owns the patch. Unsupported shapes fail open to Pi's native rendering. The thinking adapter continues adjacent merging even when the private label shape is unavailable.
131
139
 
140
+ Teardown is owner-counted and inert: when another extension's wrapper sits above one of these, removal is impossible, so the buried wrapper delegates to the original instead of leaving collapsed rows or restyled labels behind after its owner shuts down. A later install re-enables it. The shared chat-container hook contract applies the same fail-open rule to hooks: a hook that throws after editing the child list has its edit rolled back, so a failed hook cannot drop rows from the transcript.
141
+
142
+ Colors are read live from the theme, so a mid-session theme switch repaints collapsed rows, labels, and `!` blocks; cached render output keys on the resolved palette rather than on the theme object, which Pi keeps stable across a switch.
143
+
132
144
  Expanded tools always use Pi's native renderer. The collapsed tool shell owns its two-column inset directly; transcript layout extensions should leave tool rows unchanged, preventing load-order-dependent double padding.
133
145
 
134
146
  > TODO: migrate these adapters to public Pi transcript and tool-rendering APIs when available.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-kaush/pi-tool-call-markers",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
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/bash-block.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { BashExecutionComponent } from "@earendil-works/pi-coding-agent";
2
2
  import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
3
+ import { paletteSample } from "./muted.ts";
3
4
 
4
5
  type ThemeLike = {
5
6
  bold(text: string): string;
@@ -199,8 +200,15 @@ type BashRow = { status?: unknown };
199
200
 
200
201
  type BashBlockPatchState = {
201
202
  owners: number;
203
+ // Set at the final owner's shutdown. A wrapper another extension has buried
204
+ // cannot be uninstalled, so it delegates instead of outliving its owner; a
205
+ // later install re-enables it.
206
+ disabled?: boolean;
202
207
  theme?: ThemeLike;
203
- cache: WeakMap<object, { width: number; src: string[]; lines: string[] }>;
208
+ cache: WeakMap<
209
+ object,
210
+ { width: number; src: string[]; lines: string[]; palette: string }
211
+ >;
204
212
  originalRender: (width: number) => string[];
205
213
  patchedRender?: (width: number) => string[];
206
214
  };
@@ -213,6 +221,7 @@ type BashBlockPatchState = {
213
221
  // TODO: Replace prototype patching with a public Pi rendering API when available.
214
222
  function installBashBlockPatch(): BashBlockPatchState | undefined {
215
223
  try {
224
+ console.error("BB new state");
216
225
  const proto = BashExecutionComponent?.prototype as unknown as BashRow & {
217
226
  [BASH_BLOCK_PATCHED]?: BashBlockPatchState;
218
227
  render?: (width: number) => string[];
@@ -222,6 +231,8 @@ function installBashBlockPatch(): BashBlockPatchState | undefined {
222
231
  const existing = proto[BASH_BLOCK_PATCHED];
223
232
  if (existing) {
224
233
  existing.owners++;
234
+ console.error("BB reuse owners", existing.owners);
235
+ existing.disabled = false;
225
236
  return existing;
226
237
  }
227
238
 
@@ -235,17 +246,24 @@ function installBashBlockPatch(): BashBlockPatchState | undefined {
235
246
  width: number,
236
247
  ): string[] {
237
248
  const theme = state.theme;
238
- if (!theme) return state.originalRender.call(this, width);
249
+ if (state.disabled || !theme) {
250
+ return state.originalRender.call(this, width);
251
+ }
239
252
  const inset = bashBlockInset(width);
240
253
  const bodyWidth = width - inset * 2 - visibleWidth(PROMPT_RAIL);
241
254
  if (inset === 0 || bodyWidth < 8) {
242
255
  return state.originalRender.call(this, width);
243
256
  }
244
257
  const lines = state.originalRender.call(this, Math.max(1, bodyWidth));
258
+ // Decorated lines carry resolved colors, so the cache keys on the
259
+ // palette too: Pi swaps the colors behind a stable theme Proxy, which a
260
+ // theme-identity check would never notice.
261
+ const palette = paletteSample(theme);
245
262
  const entry = state.cache.get(this);
246
263
  if (
247
264
  entry &&
248
265
  entry.width === width &&
266
+ entry.palette === palette &&
249
267
  entry.src.length === lines.length &&
250
268
  entry.src.every((line, index) => line === lines[index])
251
269
  ) {
@@ -259,7 +277,7 @@ function installBashBlockPatch(): BashBlockPatchState | undefined {
259
277
  typeof status === "string" ? status : undefined,
260
278
  inset,
261
279
  );
262
- state.cache.set(this, { width, src: lines, lines: decorated });
280
+ state.cache.set(this, { width, src: lines, lines: decorated, palette });
263
281
  return decorated;
264
282
  };
265
283
 
@@ -285,6 +303,7 @@ function uninstallBashBlockPatch(state: BashBlockPatchState | undefined): void {
285
303
  if (!state || state.owners <= 0) return;
286
304
  state.owners--;
287
305
  if (state.owners > 0) return;
306
+ state.disabled = true;
288
307
  const proto = BashExecutionComponent?.prototype as unknown as BashRow & {
289
308
  [BASH_BLOCK_PATCHED]?: BashBlockPatchState;
290
309
  render?: (width: number) => string[];
@@ -37,11 +37,17 @@ export function runChatContainerHooks(
37
37
  const restores: Array<() => void> = [];
38
38
  if (Array.isArray(children)) {
39
39
  for (const hook of chatContainerHooks()) {
40
+ // A hook rewrites `children` and hands back its own undo. Snapshot first:
41
+ // a hook that throws after splicing rows out would otherwise leave them
42
+ // missing from every later render, with nothing left to restore them.
43
+ const before = [...children];
40
44
  try {
41
45
  const restore = hook(container, children, width);
42
46
  if (typeof restore === "function") restores.push(restore);
43
47
  } catch {
44
48
  // Hooks are cosmetic; one failing hook must not break rendering.
49
+ children.length = 0;
50
+ children.push(...before);
45
51
  }
46
52
  }
47
53
  }