pi-claude-code-ui 1.0.71 → 1.0.73

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
@@ -3,6 +3,19 @@
3
3
  > [!IMPORTANT]
4
4
  > **1.0.69 — package rename (permanent).** Canonical npm name is now [`pi-claude-code-ui`](https://www.npmjs.com/package/pi-claude-code-ui). `pi-claude-style-tools` is legacy and will not receive further releases. Install with `pi install npm:pi-claude-code-ui` or `npm i pi-claude-code-ui`.
5
5
 
6
+ ## 1.0.73 — 2026-07-18
7
+
8
+ ### Fixed
9
+
10
+ - **Agent rows align with other tools again** — removed the extra leading indent that only applied to Agent-family tool rows.
11
+ - **Agent breathe stays centered** — drop double-width `⬤` (it walked the baseline). Cycle is now single-cell glyphs `● → • → · → (invisible) → · → •` so the optical center never moves.
12
+
13
+ ## 1.0.72 — 2026-07-18
14
+
15
+ ### Changed
16
+
17
+ - **Agent tools breathe** — `Agent` / subagent tools use a size cycle while pending instead of the ordinary on/off `●` blink, so agent work reads as a different kind of tool.
18
+
6
19
  ## 1.0.71 — 2026-07-18
7
20
 
8
21
  ### Fixed
@@ -434,10 +434,10 @@ function isToolExecutionLike(value: unknown): value is { toolName: string; toolC
434
434
  return typeof candidate.toolName === "string" && typeof candidate.toolCallId === "string";
435
435
  }
436
436
 
437
- function shouldIndentToolExecution(value: unknown): boolean {
438
- if (!value || typeof value !== "object") return false;
439
- const toolName = (value as Record<string, unknown>).toolName;
440
- return typeof toolName === "string" && ["Agent", "Agents", "get_subagent_result", "steer_subagent"].includes(toolName);
437
+ const AGENT_FAMILY_TOOL_NAMES = new Set(["Agent", "Agents", "get_subagent_result", "steer_subagent"]);
438
+
439
+ function isAgentFamilyToolName(name: unknown): boolean {
440
+ return typeof name === "string" && AGENT_FAMILY_TOOL_NAMES.has(name);
441
441
  }
442
442
 
443
443
  function isTerminalImageLine(line: string): boolean {
@@ -541,9 +541,14 @@ function getToolGroupOverallStatus(tools: any[]): ToolStatus {
541
541
 
542
542
  // Claude Code: solid filled circle that is either fully present or fully gone
543
543
  // while pending — never a hollow outlined ○. Classic ● + bold is the sweet
544
- // spot (⬤ was too large in most mono fonts).
544
+ // spot for ordinary tools. Agent-family tools use a breathing size cycle.
545
545
  const STATUS_DOT_FILLED = "●";
546
546
  const STATUS_DOT_BOLD = "\x1b[1m";
547
+ // Single-cell glyphs only (⬤ is often double-width and walks the baseline).
548
+ // Optical sizes share the same cell so the center stays put while breathing:
549
+ // big ● → medium • → small · → invisible → small · → medium •
550
+ const AGENT_BREATHE_GLYPHS = ["●", "•", "·", " ", "·", "•"] as const;
551
+ const AGENT_BREATHE_LEN = AGENT_BREATHE_GLYPHS.length;
547
552
 
548
553
  function paintStatusDot(colorAnsi: string): string {
549
554
  return `${colorAnsi}${STATUS_DOT_BOLD}${STATUS_DOT_FILLED}${TRANSPARENT_RESET}`;
@@ -554,6 +559,15 @@ function themeStatusDot(theme: Theme, colorKey: "success" | "error" | "dim" | "m
554
559
  return theme.fg(colorKey, `${STATUS_DOT_BOLD}${STATUS_DOT_FILLED}`);
555
560
  }
556
561
 
562
+ function agentBreatheDot(theme: Theme): string {
563
+ // Always exactly one display cell — matches ordinary tool dots, keeps titles aligned.
564
+ const glyph = AGENT_BREATHE_GLYPHS[_globalBlinkPhaseIndex % AGENT_BREATHE_LEN];
565
+ if (glyph === " ") return " ";
566
+ // Bold only on the largest frame so weight changes without shifting the cell.
567
+ const bold = glyph === "●" ? STATUS_DOT_BOLD : "";
568
+ return theme.fg("success", `${bold}${glyph}`);
569
+ }
570
+
557
571
  function groupStatusLight(status: ToolStatus): string {
558
572
  const color = status === "success" ? TOOL_STATUS_SUCCESS : status === "error" ? TOOL_STATUS_ERROR : TOOL_STATUS_PENDING;
559
573
  if (status === "pending") {
@@ -1004,10 +1018,11 @@ function patchGlobalToolBorders(): void {
1004
1018
  (this as any)[TOOL_RENDER_CACHE] = { width, mode: toolBackgroundMode, lines: rendered, ...branchCache };
1005
1019
  return rendered;
1006
1020
  }
1007
- const indentTool = shouldIndentToolExecution(this);
1021
+ // Agent-family tools stay column-aligned with every other tool row — no extra
1022
+ // leading indent (the old nested pad made Agent look offset from Read/Bash).
1008
1023
  const core = textLines.map((line) => {
1009
1024
  const normalized = normalizeLeadingCheckGlyph(line);
1010
- return clampLineWidth(stripOuterBackgroundAnsi(indentTool && normalized ? ` ${normalized}` : normalized), width);
1025
+ return clampLineWidth(stripOuterBackgroundAnsi(normalized), width);
1011
1026
  });
1012
1027
  const spacerLine = " ".repeat(width);
1013
1028
  let result: string[];
@@ -2606,6 +2621,9 @@ type BlinkEntry = { key: any; order: number; invalidate: () => void };
2606
2621
  const _blinkContexts = new Map<any, BlinkEntry>();
2607
2622
  let _globalBlinkTimer: ReturnType<typeof setTimeout> | null = null;
2608
2623
  let _blinkOrder = 0;
2624
+ // Shared phase for all blinkers. Ordinary tools use even/odd (on/off ●).
2625
+ // Agent-family tools map the index onto a 6-step size breath cycle.
2626
+ let _globalBlinkPhaseIndex = 0;
2609
2627
  let _globalBlinkPhase = true;
2610
2628
 
2611
2629
  function getBlinkIntervalMs(): number {
@@ -2664,7 +2682,8 @@ function _scheduleGlobalBlinkTimer(): void {
2664
2682
  _clearAllBlinkContexts();
2665
2683
  return;
2666
2684
  }
2667
- _globalBlinkPhase = !_globalBlinkPhase;
2685
+ _globalBlinkPhaseIndex = (_globalBlinkPhaseIndex + 1) % AGENT_BREATHE_LEN;
2686
+ _globalBlinkPhase = _globalBlinkPhaseIndex % 2 === 0;
2668
2687
  for (const entry of getBlinkingEntries()) {
2669
2688
  try { entry.invalidate(); } catch { /* noop */ }
2670
2689
  }
@@ -2726,9 +2745,13 @@ function blinkDot(ctx: any, theme: Theme): string {
2726
2745
  }
2727
2746
  setupBlinkTimer(ctx);
2728
2747
  const key = getBlinkKey(ctx);
2748
+ if (key?._blinkActive !== true) return " ";
2749
+ // Agent-family tools breathe through sizes; ordinary tools still on/off ●.
2750
+ if (ctx?.state?._agentBreathe === true) {
2751
+ return agentBreatheDot(theme);
2752
+ }
2729
2753
  // Claude Code: solid filled circle that either shows or fully disappears —
2730
2754
  // never a hollow outlined ○ in the off phase.
2731
- if (key?._blinkActive !== true) return " ";
2732
2755
  return _globalBlinkPhase ? themeStatusDot(theme, "success") : " ";
2733
2756
  }
2734
2757
 
@@ -4922,6 +4945,8 @@ function genericToolLabel(name: string): string {
4922
4945
  function renderGenericToolCall(name: string, args: any, theme: Theme, ctx: any): Text {
4923
4946
  syncToolCallStatus(ctx);
4924
4947
  ctx.state._openAiPatchFiles = [];
4948
+ // Agent / subagent tools get a size-breathing pending marker, not on/off ●.
4949
+ if (isAgentFamilyToolName(name)) ctx.state._agentBreathe = true;
4925
4950
  const sp = (path: string) => shortPath(ctx.cwd ?? process.cwd(), path);
4926
4951
  const summary = stableCallSummary(ctx, "_callSummary", () => summarizeGenericToolCall(name, args, theme, sp));
4927
4952
  return makeText(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-claude-code-ui",
3
- "version": "1.0.71",
3
+ "version": "1.0.73",
4
4
  "description": "Claude Code-style grouped tool rows for pi with Ctrl+O/Ctrl+Shift+O previews, math rendering, and consistent built-in, MCP, and custom tool rendering",
5
5
  "keywords": [
6
6
  "pi-package",