pi-claude-code-ui 1.0.73 → 1.0.75
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 +12 -0
- package/extensions/index.ts +52 -11
- package/extensions/spinner.ts +204 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
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.75 — 2026-07-29
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **Herdr progress with spinner verbs** — themed working messages (`Cooking…`, `Syncing…`, …) no longer break [herdr](https://herdr.dev) agent progress. Stock herdr pi screen detection only matches literal `Working...`; when `HERDR_ENV=1` the spinner now reports `working` / `idle` over herdr’s socket (same `herdr:pi` lifecycle authority as the official pi integration). Spinner UI and verbs are unchanged. Outside herdr the extension is a no-op for this path.
|
|
11
|
+
|
|
12
|
+
## 1.0.74 — 2026-07-18
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **Grouped tool boxes keep Agent breathe aligned** — group rows strip all breathe glyphs (including `·` and the blank off-phase) before re-prefixing a fresh light, so titles no longer walk sideways. Group header/child lights also follow the shared blink phase (and Agent breathe) instead of wall-clock `isBlinkOn()`.
|
|
17
|
+
|
|
6
18
|
## 1.0.73 — 2026-07-18
|
|
7
19
|
|
|
8
20
|
### Fixed
|
package/extensions/index.ts
CHANGED
|
@@ -559,20 +559,33 @@ function themeStatusDot(theme: Theme, colorKey: "success" | "error" | "dim" | "m
|
|
|
559
559
|
return theme.fg(colorKey, `${STATUS_DOT_BOLD}${STATUS_DOT_FILLED}`);
|
|
560
560
|
}
|
|
561
561
|
|
|
562
|
-
function
|
|
562
|
+
function agentBreatheGlyphRaw(): string {
|
|
563
563
|
// Always exactly one display cell — matches ordinary tool dots, keeps titles aligned.
|
|
564
|
-
|
|
564
|
+
return AGENT_BREATHE_GLYPHS[_globalBlinkPhaseIndex % AGENT_BREATHE_LEN];
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function paintAgentBreatheDot(colorAnsi: string = TOOL_STATUS_SUCCESS): string {
|
|
568
|
+
const glyph = agentBreatheGlyphRaw();
|
|
565
569
|
if (glyph === " ") return " ";
|
|
566
570
|
// Bold only on the largest frame so weight changes without shifting the cell.
|
|
567
571
|
const bold = glyph === "●" ? STATUS_DOT_BOLD : "";
|
|
572
|
+
return `${colorAnsi}${bold}${glyph}${TRANSPARENT_RESET}`;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function agentBreatheDot(theme: Theme): string {
|
|
576
|
+
const glyph = agentBreatheGlyphRaw();
|
|
577
|
+
if (glyph === " ") return " ";
|
|
578
|
+
const bold = glyph === "●" ? STATUS_DOT_BOLD : "";
|
|
568
579
|
return theme.fg("success", `${bold}${glyph}`);
|
|
569
580
|
}
|
|
570
581
|
|
|
571
|
-
function groupStatusLight(status: ToolStatus): string {
|
|
582
|
+
function groupStatusLight(status: ToolStatus, options?: { agentBreathe?: boolean }): string {
|
|
572
583
|
const color = status === "success" ? TOOL_STATUS_SUCCESS : status === "error" ? TOOL_STATUS_ERROR : TOOL_STATUS_PENDING;
|
|
573
584
|
if (status === "pending") {
|
|
574
|
-
//
|
|
575
|
-
|
|
585
|
+
// Prefer the shared blink phase over wall-clock so group lights stay in sync
|
|
586
|
+
// with the global timer (and Agent breathe). Space keeps column alignment.
|
|
587
|
+
if (options?.agentBreathe) return paintAgentBreatheDot(TOOL_STATUS_SUCCESS);
|
|
588
|
+
return _globalBlinkPhase ? paintStatusDot(TOOL_STATUS_SUCCESS) : " ";
|
|
576
589
|
}
|
|
577
590
|
return paintStatusDot(color);
|
|
578
591
|
}
|
|
@@ -610,7 +623,13 @@ function stripToolChrome(lines: string[]): string[] {
|
|
|
610
623
|
}
|
|
611
624
|
|
|
612
625
|
function stripLeadingToolStatus(line: string): string {
|
|
613
|
-
|
|
626
|
+
// Drop the single-cell status marker so group rows can re-prefix a fresh light.
|
|
627
|
+
// Include Agent breathe glyphs (·) and the blank off-phase (space) so the title
|
|
628
|
+
// never keeps a leftover marker that shifts when size changes.
|
|
629
|
+
return line.replace(
|
|
630
|
+
/^((?:\x1b\[[0-9;]*m|[ \t]|[├└│─])*)(?:\x1b\[[0-9;]*m)*(?:[●○✗■⬤•·]| )(?:\x1b\[[0-9;]*m)*\s+/,
|
|
631
|
+
"$1",
|
|
632
|
+
);
|
|
614
633
|
}
|
|
615
634
|
|
|
616
635
|
function trimAnsiLeft(text: string): string {
|
|
@@ -684,19 +703,30 @@ function branchContinuation(index: number, total: number, theme?: Theme): string
|
|
|
684
703
|
return index === total - 1 ? " " : ` ${rule}│${TRANSPARENT_RESET} `;
|
|
685
704
|
}
|
|
686
705
|
|
|
687
|
-
function formatBranchedToolLines(
|
|
706
|
+
function formatBranchedToolLines(
|
|
707
|
+
lines: string[],
|
|
708
|
+
index: number,
|
|
709
|
+
total: number,
|
|
710
|
+
width: number,
|
|
711
|
+
status: ToolStatus,
|
|
712
|
+
options?: { agentBreathe?: boolean },
|
|
713
|
+
): string[] {
|
|
688
714
|
const output: string[] = [];
|
|
689
715
|
const content = lines.filter((line) => isTerminalImageLine(line) || stripAnsi(line).trim().length > 0);
|
|
690
716
|
const safeContent = content.length > 0 ? content : [""];
|
|
691
|
-
const light = groupStatusLight(status);
|
|
717
|
+
const light = groupStatusLight(status, options);
|
|
692
718
|
for (let lineIndex = 0; lineIndex < safeContent.length; lineIndex++) {
|
|
693
719
|
const line = safeContent[lineIndex];
|
|
694
720
|
if (isTerminalImageLine(line)) {
|
|
695
721
|
output.push(line);
|
|
696
722
|
continue;
|
|
697
723
|
}
|
|
724
|
+
// Always strip any leftover status marker from the child call line before
|
|
725
|
+
// re-prefixing. Agent breathe used · which the old stripper missed, so the
|
|
726
|
+
// title walked sideways as size changed inside groups.
|
|
727
|
+
const body = lineIndex === 0 ? removeGroupedToolPrefix(line) : trimAnsiLeft(line);
|
|
698
728
|
const prefix = lineIndex === 0 ? `${branchPrefix(index, total)}${light} ` : `${branchContinuation(index, total)} `;
|
|
699
|
-
output.push(clampLineWidth(`${prefix}${
|
|
729
|
+
output.push(clampLineWidth(`${prefix}${body}`, width));
|
|
700
730
|
}
|
|
701
731
|
return output;
|
|
702
732
|
}
|
|
@@ -818,7 +848,11 @@ class ToolGroupComponent extends Container {
|
|
|
818
848
|
const label = getToolGroupLabel(this.tools);
|
|
819
849
|
const names = groupedName ? "" : formatToolNameList(this.tools);
|
|
820
850
|
const overall: ToolStatus = status.error > 0 ? "error" : status.pending > 0 ? "pending" : "success";
|
|
821
|
-
|
|
851
|
+
// Group header breathes only when every pending member is Agent-family;
|
|
852
|
+
// mixed groups keep the ordinary on/off light.
|
|
853
|
+
const pendingTools = this.tools.filter((tool) => getToolStatusForGroup(tool) === "pending");
|
|
854
|
+
const headerBreathe = pendingTools.length > 0 && pendingTools.every((tool) => isAgentFamilyToolName(getToolName(tool)));
|
|
855
|
+
const light = groupStatusLight(overall, { agentBreathe: headerBreathe });
|
|
822
856
|
const summaryLabel = `${label}:`;
|
|
823
857
|
const countParts: string[] = [];
|
|
824
858
|
if (status.pending) countParts.push(statusText("pending", status.pending));
|
|
@@ -835,7 +869,14 @@ class ToolGroupComponent extends Container {
|
|
|
835
869
|
const rawLines = this.expanded
|
|
836
870
|
? getExpandedToolGroupLines(tool, childWidth, groupedName ? label : undefined)
|
|
837
871
|
: [getCompactToolLine(tool, childWidth, groupedName ? label : undefined)];
|
|
838
|
-
const branched = formatBranchedToolLines(
|
|
872
|
+
const branched = formatBranchedToolLines(
|
|
873
|
+
rawLines,
|
|
874
|
+
index,
|
|
875
|
+
total,
|
|
876
|
+
safeWidth,
|
|
877
|
+
getToolStatusForGroup(tool),
|
|
878
|
+
{ agentBreathe: isAgentFamilyToolName(getToolName(tool)) },
|
|
879
|
+
);
|
|
839
880
|
for (let i = 0; i < branched.length; i++) {
|
|
840
881
|
lines.push(clampLineWidth(branched[i], safeWidth));
|
|
841
882
|
}
|
package/extensions/spinner.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import net from "node:net";
|
|
2
3
|
|
|
3
4
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
5
|
import { Loader } from "@earendil-works/pi-tui";
|
|
@@ -509,6 +510,162 @@ function statusText(text: string): string {
|
|
|
509
510
|
return `${STATUS_DIM}${text}${RESET}`;
|
|
510
511
|
}
|
|
511
512
|
|
|
513
|
+
// ---------------------------------------------------------------------------
|
|
514
|
+
// Herdr lifecycle reporting
|
|
515
|
+
//
|
|
516
|
+
// Stock herdr pi screen detection only matches the literal "Working...".
|
|
517
|
+
// This extension replaces that text with themed spinner verbs (Cooking…,
|
|
518
|
+
// Syncing…, …), so herdr sidebars go idle/stale while pi is actually busy.
|
|
519
|
+
// When running inside herdr (HERDR_ENV=1), report working/idle over the
|
|
520
|
+
// socket the same way herdr's official pi integration does. UI is unchanged.
|
|
521
|
+
// ---------------------------------------------------------------------------
|
|
522
|
+
|
|
523
|
+
type HerdrAgentState = "working" | "idle";
|
|
524
|
+
|
|
525
|
+
const HERDR_ENV = process.env.HERDR_ENV;
|
|
526
|
+
const HERDR_SOCKET_PATH = process.env.HERDR_SOCKET_PATH;
|
|
527
|
+
const HERDR_SOCKET_ENDPOINT =
|
|
528
|
+
process.platform === "win32" && HERDR_SOCKET_PATH
|
|
529
|
+
? `\\\\.\\pipe\\${HERDR_SOCKET_PATH}`
|
|
530
|
+
: HERDR_SOCKET_PATH;
|
|
531
|
+
const HERDR_PANE_ID = process.env.HERDR_PANE_ID;
|
|
532
|
+
// Must be exactly "herdr:pi" — herdr only grants full lifecycle authority
|
|
533
|
+
// (skip screen-manifest fallback) to that official source pair with agent "pi".
|
|
534
|
+
const HERDR_SOURCE = "herdr:pi";
|
|
535
|
+
|
|
536
|
+
function herdrEnabled(): boolean {
|
|
537
|
+
return HERDR_ENV === "1" && !!HERDR_SOCKET_PATH && !!HERDR_PANE_ID;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function herdrSendAttempt(request: unknown, timeoutMs: number): Promise<boolean> {
|
|
541
|
+
if (!herdrEnabled()) return Promise.resolve(true);
|
|
542
|
+
|
|
543
|
+
return new Promise((resolve) => {
|
|
544
|
+
let done = false;
|
|
545
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
546
|
+
const finish = (delivered: boolean) => {
|
|
547
|
+
if (done) return;
|
|
548
|
+
done = true;
|
|
549
|
+
if (timeout) clearTimeout(timeout);
|
|
550
|
+
socket.destroy();
|
|
551
|
+
resolve(delivered);
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
const socket = net.createConnection(HERDR_SOCKET_ENDPOINT!);
|
|
555
|
+
socket.on("error", () => finish(false));
|
|
556
|
+
socket.on("connect", () => socket.write(`${JSON.stringify(request)}\n`));
|
|
557
|
+
socket.on("data", () => finish(true));
|
|
558
|
+
socket.on("end", () => finish(false));
|
|
559
|
+
timeout = setTimeout(() => finish(false), timeoutMs);
|
|
560
|
+
unrefTimer(timeout);
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
async function herdrSend(request: unknown): Promise<void> {
|
|
565
|
+
if (await herdrSendAttempt(request, 500)) return;
|
|
566
|
+
await herdrSendAttempt(request, 1500);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
let herdrReportSeq = Date.now() * 1000;
|
|
570
|
+
let herdrSessionId: string | undefined;
|
|
571
|
+
let herdrSessionPath: string | undefined;
|
|
572
|
+
let herdrSendInFlight = false;
|
|
573
|
+
let herdrQueued: { state: HerdrAgentState; seq: number } | undefined;
|
|
574
|
+
let herdrLastState: HerdrAgentState | undefined;
|
|
575
|
+
|
|
576
|
+
function herdrNextSeq(): number {
|
|
577
|
+
herdrReportSeq += 1;
|
|
578
|
+
return herdrReportSeq;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function herdrUpdateSessionRef(ctx: any): void {
|
|
582
|
+
try {
|
|
583
|
+
const file = ctx?.sessionManager?.getSessionFile?.();
|
|
584
|
+
herdrSessionPath =
|
|
585
|
+
typeof file === "string" && file.startsWith("/") ? file : undefined;
|
|
586
|
+
} catch {
|
|
587
|
+
herdrSessionPath = undefined;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
try {
|
|
591
|
+
const id = ctx?.sessionManager?.getSessionId?.();
|
|
592
|
+
herdrSessionId = typeof id === "string" && id.length > 0 ? id : undefined;
|
|
593
|
+
} catch {
|
|
594
|
+
herdrSessionId = undefined;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function herdrWithSessionRef(params: Record<string, unknown>): Record<string, unknown> {
|
|
599
|
+
if (herdrSessionPath) return { ...params, agent_session_path: herdrSessionPath };
|
|
600
|
+
if (herdrSessionId) return { ...params, agent_session_id: herdrSessionId };
|
|
601
|
+
return params;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function herdrCurrentSessionRef(): Record<string, unknown> | undefined {
|
|
605
|
+
if (herdrSessionPath) return { agent_session_path: herdrSessionPath };
|
|
606
|
+
if (herdrSessionId) return { agent_session_id: herdrSessionId };
|
|
607
|
+
return undefined;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function herdrReportSession(sessionStartSource?: string): Promise<void> {
|
|
611
|
+
const sessionRef = herdrCurrentSessionRef();
|
|
612
|
+
if (!sessionRef) return Promise.resolve();
|
|
613
|
+
|
|
614
|
+
return herdrSend({
|
|
615
|
+
id: `${HERDR_SOURCE}:session:${Date.now()}:${Math.random().toString(36).slice(2)}`,
|
|
616
|
+
method: "pane.report_agent_session",
|
|
617
|
+
params: {
|
|
618
|
+
pane_id: HERDR_PANE_ID,
|
|
619
|
+
source: HERDR_SOURCE,
|
|
620
|
+
agent: "pi",
|
|
621
|
+
seq: herdrNextSeq(),
|
|
622
|
+
session_start_source: sessionStartSource,
|
|
623
|
+
...sessionRef,
|
|
624
|
+
},
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function herdrSendState(state: HerdrAgentState, seq = herdrNextSeq()): Promise<void> {
|
|
629
|
+
return herdrSend({
|
|
630
|
+
id: `${HERDR_SOURCE}:${Date.now()}:${Math.random().toString(36).slice(2)}`,
|
|
631
|
+
method: "pane.report_agent",
|
|
632
|
+
params: herdrWithSessionRef({
|
|
633
|
+
pane_id: HERDR_PANE_ID,
|
|
634
|
+
source: HERDR_SOURCE,
|
|
635
|
+
agent: "pi",
|
|
636
|
+
state,
|
|
637
|
+
seq,
|
|
638
|
+
}),
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function herdrQueueState(state: HerdrAgentState): void {
|
|
643
|
+
herdrQueued = { state, seq: herdrNextSeq() };
|
|
644
|
+
if (!herdrSendInFlight) void herdrDrainQueue();
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
async function herdrDrainQueue(): Promise<void> {
|
|
648
|
+
if (herdrSendInFlight) return;
|
|
649
|
+
herdrSendInFlight = true;
|
|
650
|
+
try {
|
|
651
|
+
while (herdrQueued) {
|
|
652
|
+
const next = herdrQueued;
|
|
653
|
+
herdrQueued = undefined;
|
|
654
|
+
await herdrSendState(next.state, next.seq);
|
|
655
|
+
}
|
|
656
|
+
} finally {
|
|
657
|
+
herdrSendInFlight = false;
|
|
658
|
+
if (herdrQueued) void herdrDrainQueue();
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function herdrPublishState(state: HerdrAgentState, force = false): void {
|
|
663
|
+
if (!herdrEnabled()) return;
|
|
664
|
+
if (!force && state === herdrLastState) return;
|
|
665
|
+
herdrLastState = state;
|
|
666
|
+
herdrQueueState(state);
|
|
667
|
+
}
|
|
668
|
+
|
|
512
669
|
// ---------------------------------------------------------------------------
|
|
513
670
|
// Extension
|
|
514
671
|
// ---------------------------------------------------------------------------
|
|
@@ -545,6 +702,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
545
702
|
let turnActive = false;
|
|
546
703
|
let lastWorkingMessage: string | null = null;
|
|
547
704
|
let activeCtx: { ui: any; hasUI: boolean } | null = null;
|
|
705
|
+
// Root interactive session only — nested/non-UI contexts must not fight
|
|
706
|
+
// herdr state authority for this pane.
|
|
707
|
+
let herdrRootSession = false;
|
|
548
708
|
|
|
549
709
|
function getEffortSuffix(): string {
|
|
550
710
|
try {
|
|
@@ -709,6 +869,31 @@ export default function (pi: ExtensionAPI) {
|
|
|
709
869
|
restoreDefaultWorkingMessage();
|
|
710
870
|
}
|
|
711
871
|
|
|
872
|
+
function herdrMarkWorking(ctx?: any): void {
|
|
873
|
+
if (!herdrRootSession) return;
|
|
874
|
+
if (ctx) herdrUpdateSessionRef(ctx);
|
|
875
|
+
void herdrReportSession();
|
|
876
|
+
herdrPublishState("working");
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function herdrMarkIdle(ctx?: any, force = false): void {
|
|
880
|
+
if (!herdrRootSession) return;
|
|
881
|
+
if (ctx) herdrUpdateSessionRef(ctx);
|
|
882
|
+
herdrPublishState("idle", force);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// session_start: claim root UI session + announce native session identity.
|
|
886
|
+
pi.on("session_start", async (event, ctx) => {
|
|
887
|
+
if (!herdrEnabled()) return;
|
|
888
|
+
if (ctx?.hasUI !== true) return;
|
|
889
|
+
herdrRootSession = true;
|
|
890
|
+
herdrUpdateSessionRef(ctx);
|
|
891
|
+
await herdrReportSession((event as any)?.reason);
|
|
892
|
+
// Reload mid-run: isIdle() false means work is still in flight.
|
|
893
|
+
const working = ctx?.isIdle?.() === false;
|
|
894
|
+
herdrPublishState(working ? "working" : "idle", true);
|
|
895
|
+
});
|
|
896
|
+
|
|
712
897
|
function onThinkingEnd(): void {
|
|
713
898
|
if (thinkingStatus !== "thinking") return;
|
|
714
899
|
const duration = Date.now() - thinkingStartTime;
|
|
@@ -728,8 +913,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
728
913
|
if (!agentStartTime) agentStartTime = Date.now();
|
|
729
914
|
});
|
|
730
915
|
|
|
731
|
-
pi.on("agent_start", async () => {
|
|
916
|
+
pi.on("agent_start", async (_event, ctx) => {
|
|
732
917
|
if (!agentStartTime) agentStartTime = Date.now();
|
|
918
|
+
herdrMarkWorking(ctx);
|
|
733
919
|
});
|
|
734
920
|
|
|
735
921
|
pi.on("turn_start", async (_event, ctx) => {
|
|
@@ -749,6 +935,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
749
935
|
scheduleThoughtStatusClear();
|
|
750
936
|
}
|
|
751
937
|
startRefreshLoop();
|
|
938
|
+
// turn_start is the earliest UI-visible busy signal; cover cases where
|
|
939
|
+
// agent_start already fired or is ordering-sensitive across reloads.
|
|
940
|
+
herdrMarkWorking(ctx);
|
|
752
941
|
});
|
|
753
942
|
|
|
754
943
|
pi.on("message_update", async (event, ctx) => {
|
|
@@ -843,9 +1032,23 @@ export default function (pi: ExtensionAPI) {
|
|
|
843
1032
|
clearDisplay();
|
|
844
1033
|
});
|
|
845
1034
|
|
|
1035
|
+
// Prefer agent_settled over agent_end: pi may still auto-retry, compact, or
|
|
1036
|
+
// drain a follow-up queue after agent_end. Settled means truly idle.
|
|
1037
|
+
// Cast: agent_settled landed after some older @earendil-works/pi-coding-agent
|
|
1038
|
+
// type packages still used for local typecheck; runtime pi has the event.
|
|
1039
|
+
(pi as ExtensionAPI & {
|
|
1040
|
+
on(event: "agent_settled", handler: (event: unknown, ctx: any) => void | Promise<void>): void;
|
|
1041
|
+
}).on("agent_settled", async (_event, ctx) => {
|
|
1042
|
+
if (!herdrRootSession) return;
|
|
1043
|
+
if (ctx?.isIdle?.() !== true) return;
|
|
1044
|
+
herdrMarkIdle(ctx);
|
|
1045
|
+
});
|
|
1046
|
+
|
|
846
1047
|
pi.on("session_shutdown", async () => {
|
|
847
1048
|
turnActive = false;
|
|
848
1049
|
clearDisplay();
|
|
1050
|
+
herdrMarkIdle(undefined, true);
|
|
1051
|
+
herdrRootSession = false;
|
|
849
1052
|
activeCtx = null;
|
|
850
1053
|
});
|
|
851
1054
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-claude-code-ui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.75",
|
|
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",
|