pi-claude-code-ui 1.0.83 → 1.0.85

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.
@@ -0,0 +1,4 @@
1
+ export function getUserSettingsPath(): string {
2
+ const agentDir = process.env.PI_CODING_AGENT_DIR || `${process.env.HOME ?? ""}/.pi`;
3
+ return `${agentDir}/settings.json`;
4
+ }
@@ -1,9 +1,13 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import net from "node:net";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
3
5
 
4
6
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
5
7
  import { Loader } from "@earendil-works/pi-tui";
6
8
 
9
+ import { getUserSettingsPath } from "./settings-path";
10
+
7
11
  // ---------------------------------------------------------------------------
8
12
  // Patch built-in Loader with Claude/OpenBrawd-style glyphs.
9
13
  // Keep animation cadence constant so the spinner doesn't appear to slow down
@@ -44,7 +48,7 @@ function readSpinnerSettings(): { adaptive: boolean; verbColor: string; statusCo
44
48
  // orange as the glyph on themes like openAntigravity-dark.
45
49
  let verbColor = "borderAccent";
46
50
  let statusColor = "muted";
47
- const paths = [`${process.cwd()}/.pi/settings.json`, `${process.env.HOME ?? ""}/.pi/settings.json`];
51
+ const paths = [`${process.cwd()}/.pi/settings.json`, getUserSettingsPath()];
48
52
  for (const p of paths) {
49
53
  try {
50
54
  if (!p || !existsSync(p)) continue;
@@ -517,7 +521,9 @@ function statusText(text: string): string {
517
521
  // This extension replaces that text with themed spinner verbs (Cooking…,
518
522
  // Syncing…, …), so herdr sidebars go idle/stale while pi is actually busy.
519
523
  // 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.
524
+ // socket only when herdr's official pi integration is not installed. The
525
+ // official integration owns the same lifecycle authority and must remain the
526
+ // sole reporter when present. UI is unchanged.
521
527
  // ---------------------------------------------------------------------------
522
528
 
523
529
  type HerdrAgentState = "working" | "idle";
@@ -532,9 +538,33 @@ const HERDR_PANE_ID = process.env.HERDR_PANE_ID;
532
538
  // Must be exactly "herdr:pi" — herdr only grants full lifecycle authority
533
539
  // (skip screen-manifest fallback) to that official source pair with agent "pi".
534
540
  const HERDR_SOURCE = "herdr:pi";
541
+ const HERDR_INTEGRATION_MARKER = "HERDR_INTEGRATION_ID=pi";
542
+ let officialHerdrIntegrationInstalled: boolean | undefined;
543
+
544
+ function hasOfficialHerdrIntegration(): boolean {
545
+ if (officialHerdrIntegrationInstalled !== undefined) {
546
+ return officialHerdrIntegrationInstalled;
547
+ }
548
+
549
+ const agentDir = process.env.PI_CODING_AGENT_DIR
550
+ || join(homedir(), ".pi", "agent");
551
+
552
+ try {
553
+ const integrationPath = join(agentDir, "extensions", "herdr-agent-state.ts");
554
+ officialHerdrIntegrationInstalled =
555
+ existsSync(integrationPath)
556
+ && readFileSync(integrationPath, "utf8").includes(HERDR_INTEGRATION_MARKER);
557
+ } catch {
558
+ officialHerdrIntegrationInstalled = false;
559
+ }
560
+ return officialHerdrIntegrationInstalled;
561
+ }
535
562
 
536
563
  function herdrEnabled(): boolean {
537
- return HERDR_ENV === "1" && !!HERDR_SOCKET_PATH && !!HERDR_PANE_ID;
564
+ return HERDR_ENV === "1"
565
+ && !!HERDR_SOCKET_PATH
566
+ && !!HERDR_PANE_ID
567
+ && !hasOfficialHerdrIntegration();
538
568
  }
539
569
 
540
570
  function herdrSendAttempt(request: unknown, timeoutMs: number): Promise<boolean> {
@@ -754,7 +784,12 @@ export default function (pi: ExtensionAPI) {
754
784
  }
755
785
 
756
786
  function syncWorkingMessage(force = false): void {
757
- if (!activeCtx?.hasUI) return;
787
+ try {
788
+ if (!activeCtx?.hasUI) return;
789
+ } catch {
790
+ // Session replacement/reload invalidated the captured context.
791
+ return;
792
+ }
758
793
  // Re-derive colors on every tick so /cc-spinner verb/status changes
759
794
  // take effect within ~250 ms without waiting for the next pi event.
760
795
  // applyThemeColors is identity-cached on (theme, verbKey, statusKey) so
@@ -770,7 +805,12 @@ export default function (pi: ExtensionAPI) {
770
805
 
771
806
  function restoreDefaultWorkingMessage(): void {
772
807
  lastWorkingMessage = null;
773
- if (!activeCtx?.hasUI) return;
808
+ try {
809
+ if (!activeCtx?.hasUI) return;
810
+ } catch {
811
+ // Session replacement/reload invalidated the captured context.
812
+ return;
813
+ }
774
814
  try {
775
815
  activeCtx.ui.setWorkingMessage();
776
816
  } catch { /* noop */ }
@@ -1002,20 +1042,25 @@ export default function (pi: ExtensionAPI) {
1002
1042
  clearThoughtStatusTimer();
1003
1043
  }
1004
1044
 
1005
- if (activeCtx?.hasUI) {
1006
- const message = `${STATUS_DIM}✻ Turn took ${formatDuration(elapsed)}${RESET}`;
1007
- lastWorkingMessage = message;
1008
- try {
1009
- activeCtx.ui.setWorkingMessage(message);
1010
- } catch { /* noop */ }
1011
- completionTimer = setTimeout(() => {
1012
- completionTimer = null;
1013
- if (activeTurnId !== turnId) return;
1045
+ try {
1046
+ if (activeCtx?.hasUI) {
1047
+ const message = `${STATUS_DIM}✻ Turn took ${formatDuration(elapsed)}${RESET}`;
1048
+ lastWorkingMessage = message;
1049
+ try {
1050
+ activeCtx.ui.setWorkingMessage(message);
1051
+ } catch { /* noop */ }
1052
+ completionTimer = setTimeout(() => {
1053
+ completionTimer = null;
1054
+ if (activeTurnId !== turnId) return;
1055
+ restoreDefaultWorkingMessage();
1056
+ }, TURN_COMPLETION_MS);
1057
+ unrefTimer(completionTimer);
1058
+ } else if (typeof thinkingStatus !== "number") {
1014
1059
  restoreDefaultWorkingMessage();
1015
- }, TURN_COMPLETION_MS);
1016
- unrefTimer(completionTimer);
1017
- } else if (typeof thinkingStatus !== "number") {
1018
- restoreDefaultWorkingMessage();
1060
+ }
1061
+ } catch {
1062
+ // Session replacement/reload invalidated the captured context.
1063
+ return;
1019
1064
  }
1020
1065
 
1021
1066
  responseLength = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-claude-code-ui",
3
- "version": "1.0.83",
3
+ "version": "1.0.85",
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",
@@ -30,13 +30,15 @@
30
30
  "files": [
31
31
  "extensions/index.ts",
32
32
  "extensions/spinner.ts",
33
+ "extensions/settings-path.ts",
33
34
  "extensions/bash-command.ts",
34
35
  "config/config.example.json",
35
36
  "README.md",
36
37
  "CHANGELOG.md"
37
38
  ],
38
39
  "scripts": {
39
- "test": "node --experimental-strip-types --test scripts/bash-command.test.ts",
40
+ "test": "node --experimental-strip-types --test scripts/*.test.ts",
41
+ "test:fullscreen": "node scripts/fullscreen-mouse.test.mjs",
40
42
  "typecheck": "tsc --noEmit --target ES2022 --module NodeNext --moduleResolution NodeNext --skipLibCheck extensions/index.ts extensions/spinner.ts",
41
43
  "pack:dry-run": "npm pack --dry-run",
42
44
  "publish:dry-run": "npm publish --dry-run",