pi-claude-code-ui 1.0.70 → 1.0.71
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 +6 -0
- package/extensions/index.ts +47 -56
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
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.71 — 2026-07-18
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **Long-running tools no longer freeze their status dots** — the 15s stale watchdog was treating quiet tools (no `tool_execution_update`) as leaked and killing the blink timer mid-run. While an agent is live the timer now heartbeats itself; stale cleanup only runs after the agent finishes. Also stop clearing blink state on `turn_end` (turns end before tools run).
|
|
11
|
+
|
|
6
12
|
## 1.0.70 — 2026-07-17
|
|
7
13
|
|
|
8
14
|
### Fixed
|
package/extensions/index.ts
CHANGED
|
@@ -2590,9 +2590,10 @@ function indentBranchBlock(block: string): string {
|
|
|
2590
2590
|
|
|
2591
2591
|
const MAX_BLINKING_TOOLS = 5;
|
|
2592
2592
|
const BLINK_INTERVAL_MS = 500;
|
|
2593
|
-
//
|
|
2594
|
-
//
|
|
2595
|
-
//
|
|
2593
|
+
// Safety net ONLY for leaked entries after the agent has stopped. Quiet
|
|
2594
|
+
// long-running tools (sleep, sparse builds, waiting on network) legitimately
|
|
2595
|
+
// emit no tool_execution_update for minutes — treating that silence as "stale"
|
|
2596
|
+
// is what made their ● freeze mid-run.
|
|
2596
2597
|
const BLINK_STALE_TIMEOUT_MS = 15000;
|
|
2597
2598
|
let _lastBlinkActivity = 0;
|
|
2598
2599
|
|
|
@@ -2632,6 +2633,18 @@ function updateBlinkActiveStates(): void {
|
|
|
2632
2633
|
}
|
|
2633
2634
|
}
|
|
2634
2635
|
|
|
2636
|
+
function _clearAllBlinkContexts(): void {
|
|
2637
|
+
for (const entry of _blinkContexts.values()) {
|
|
2638
|
+
try { entry.key._blinkActive = false; } catch { /* noop */ }
|
|
2639
|
+
}
|
|
2640
|
+
_blinkContexts.clear();
|
|
2641
|
+
if (_globalBlinkTimer) {
|
|
2642
|
+
clearTimeout(_globalBlinkTimer);
|
|
2643
|
+
_globalBlinkTimer = null;
|
|
2644
|
+
}
|
|
2645
|
+
updateBlinkActiveStates();
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2635
2648
|
function _scheduleGlobalBlinkTimer(): void {
|
|
2636
2649
|
if (_globalBlinkTimer) return;
|
|
2637
2650
|
const intervalMs = getBlinkIntervalMs();
|
|
@@ -2642,15 +2655,13 @@ function _scheduleGlobalBlinkTimer(): void {
|
|
|
2642
2655
|
updateBlinkActiveStates();
|
|
2643
2656
|
return;
|
|
2644
2657
|
}
|
|
2645
|
-
//
|
|
2646
|
-
//
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
_blinkContexts.clear();
|
|
2653
|
-
updateBlinkActiveStates();
|
|
2658
|
+
// While an agent run is live, quiet tools are still in flight — keep blinking.
|
|
2659
|
+
// Heartbeat here so sparse/no-output commands never look "stale".
|
|
2660
|
+
if (currentAgentWorkStartMs !== undefined) {
|
|
2661
|
+
markBlinkActivity();
|
|
2662
|
+
} else if (_lastBlinkActivity && Date.now() - _lastBlinkActivity > BLINK_STALE_TIMEOUT_MS) {
|
|
2663
|
+
// Agent already finished; leftover entries are leaks. Stop the re-render storm.
|
|
2664
|
+
_clearAllBlinkContexts();
|
|
2654
2665
|
return;
|
|
2655
2666
|
}
|
|
2656
2667
|
_globalBlinkPhase = !_globalBlinkPhase;
|
|
@@ -2675,12 +2686,18 @@ function setupBlinkTimer(ctx: any): void {
|
|
|
2675
2686
|
const invalidate = typeof ctx?.invalidate === "function" ? () => safeInvalidate(ctx) : () => {};
|
|
2676
2687
|
const existing = _blinkContexts.get(key);
|
|
2677
2688
|
if (existing) {
|
|
2678
|
-
// Already tracked —
|
|
2689
|
+
// Already tracked — refresh invalidate + ensure the global timer is alive.
|
|
2690
|
+
// If a prior watchdog/pass stopped the timer without removing this entry
|
|
2691
|
+
// (or the timer simply died), a quiet long-running tool would otherwise
|
|
2692
|
+
// stay registered forever with a frozen ●.
|
|
2679
2693
|
existing.invalidate = invalidate;
|
|
2694
|
+
markBlinkActivity();
|
|
2695
|
+
_scheduleGlobalBlinkTimer();
|
|
2680
2696
|
return;
|
|
2681
2697
|
}
|
|
2682
2698
|
_blinkContexts.set(key, { key, order: ++_blinkOrder, invalidate });
|
|
2683
2699
|
key._blinkActive = false;
|
|
2700
|
+
markBlinkActivity();
|
|
2684
2701
|
updateBlinkActiveStates();
|
|
2685
2702
|
_stopGlobalBlinkTimerIfEmpty();
|
|
2686
2703
|
_scheduleGlobalBlinkTimer();
|
|
@@ -6768,66 +6785,40 @@ export default function (pi: ExtensionAPI) {
|
|
|
6768
6785
|
registerMcpToolOverrides();
|
|
6769
6786
|
});
|
|
6770
6787
|
|
|
6771
|
-
// Streaming activity keeps the blink timer alive
|
|
6772
|
-
//
|
|
6788
|
+
// Streaming activity keeps the blink timer alive. Do NOT clear blink contexts
|
|
6789
|
+
// on turn_end — a turn ends when the assistant message finishes, BEFORE its
|
|
6790
|
+
// tools run. agent_end / agent_settled are the real "work finished" signals.
|
|
6773
6791
|
pi.on("turn_start", async () => { markBlinkActivity(); });
|
|
6774
6792
|
pi.on("message_start", async () => { markBlinkActivity(); });
|
|
6775
6793
|
pi.on("message_update", async () => { markBlinkActivity(); });
|
|
6776
6794
|
pi.on("tool_execution_start", async () => { markBlinkActivity(); });
|
|
6777
6795
|
// Partial tool output is the main long-running signal (bash streams for minutes).
|
|
6778
|
-
// Without this, the 15s stale watchdog can kill blink while a quiet command runs.
|
|
6779
6796
|
pi.on("tool_execution_update", async () => { markBlinkActivity(); });
|
|
6780
6797
|
pi.on("tool_execution_end", async () => { markBlinkActivity(); });
|
|
6798
|
+
// agent_end fires when a low-level run finishes (tools for that assistant message
|
|
6799
|
+
// are done). registerThinkingLabels clears currentAgentWorkStartMs on the same
|
|
6800
|
+
// event; defer so we only wipe blink state once the work marker is gone.
|
|
6801
|
+
// Do not use agent_settled here — older peer types don't include it, and the
|
|
6802
|
+
// live-agent heartbeat already keeps quiet long tools blinking until agent_end.
|
|
6781
6803
|
pi.on("agent_end", async () => {
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
_globalBlinkTimer = null;
|
|
6790
|
-
}
|
|
6791
|
-
});
|
|
6792
|
-
|
|
6793
|
-
// Safety net: clear blink timers after a turn's tools finish.
|
|
6794
|
-
// Keep Shiki hlCache across turns — it's an LRU (CACHE_LIMIT=48) and
|
|
6795
|
-
// re-highlighting the same diffs every turn was a free long-chat tax.
|
|
6796
|
-
// Full clear still happens on session_shutdown / theme rebind.
|
|
6797
|
-
pi.on("turn_end", async () => {
|
|
6798
|
-
for (const entry of _blinkContexts.values()) {
|
|
6799
|
-
entry.key._blinkActive = false;
|
|
6800
|
-
}
|
|
6801
|
-
_blinkContexts.clear();
|
|
6802
|
-
if (_globalBlinkTimer) {
|
|
6803
|
-
clearTimeout(_globalBlinkTimer);
|
|
6804
|
-
_globalBlinkTimer = null;
|
|
6805
|
-
}
|
|
6804
|
+
queueMicrotask(() => {
|
|
6805
|
+
if (currentAgentWorkStartMs !== undefined) {
|
|
6806
|
+
markBlinkActivity();
|
|
6807
|
+
return;
|
|
6808
|
+
}
|
|
6809
|
+
_clearAllBlinkContexts();
|
|
6810
|
+
});
|
|
6806
6811
|
});
|
|
6807
6812
|
// Session rebuild (resume/reload/fork) must not leave history partials blinking.
|
|
6808
6813
|
pi.on("session_start", async () => {
|
|
6809
|
-
|
|
6810
|
-
entry.key._blinkActive = false;
|
|
6811
|
-
}
|
|
6812
|
-
_blinkContexts.clear();
|
|
6813
|
-
if (_globalBlinkTimer) {
|
|
6814
|
-
clearTimeout(_globalBlinkTimer);
|
|
6815
|
-
_globalBlinkTimer = null;
|
|
6816
|
-
}
|
|
6814
|
+
_clearAllBlinkContexts();
|
|
6817
6815
|
});
|
|
6818
6816
|
pi.on("session_shutdown", async () => {
|
|
6819
|
-
|
|
6820
|
-
entry.key._blinkActive = false;
|
|
6821
|
-
}
|
|
6822
|
-
_blinkContexts.clear();
|
|
6817
|
+
_clearAllBlinkContexts();
|
|
6823
6818
|
clearRtkRewriteState();
|
|
6824
6819
|
WRITE_EXISTED_BEFORE.clear();
|
|
6825
6820
|
clearHighlightCache();
|
|
6826
6821
|
invalidateThemePaletteCache();
|
|
6827
6822
|
bumpToolBranchVisualEpoch();
|
|
6828
|
-
if (_globalBlinkTimer) {
|
|
6829
|
-
clearTimeout(_globalBlinkTimer);
|
|
6830
|
-
_globalBlinkTimer = null;
|
|
6831
|
-
}
|
|
6832
6823
|
});
|
|
6833
6824
|
}
|
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.71",
|
|
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",
|