@f5-sales-demo/xcsh 19.62.0 → 19.62.1

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5-sales-demo/xcsh",
4
- "version": "19.62.0",
4
+ "version": "19.62.1",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/f5-sales-demo/xcsh",
7
7
  "author": "Can Boluk",
@@ -56,13 +56,13 @@
56
56
  "dependencies": {
57
57
  "@agentclientprotocol/sdk": "0.16.1",
58
58
  "@mozilla/readability": "^0.6",
59
- "@f5-sales-demo/xcsh-stats": "19.62.0",
60
- "@f5-sales-demo/pi-agent-core": "19.62.0",
61
- "@f5-sales-demo/pi-ai": "19.62.0",
62
- "@f5-sales-demo/pi-natives": "19.62.0",
63
- "@f5-sales-demo/pi-resource-management": "19.62.0",
64
- "@f5-sales-demo/pi-tui": "19.62.0",
65
- "@f5-sales-demo/pi-utils": "19.62.0",
59
+ "@f5-sales-demo/xcsh-stats": "19.62.1",
60
+ "@f5-sales-demo/pi-agent-core": "19.62.1",
61
+ "@f5-sales-demo/pi-ai": "19.62.1",
62
+ "@f5-sales-demo/pi-natives": "19.62.1",
63
+ "@f5-sales-demo/pi-resource-management": "19.62.1",
64
+ "@f5-sales-demo/pi-tui": "19.62.1",
65
+ "@f5-sales-demo/pi-utils": "19.62.1",
66
66
  "@sinclair/typebox": "^0.34",
67
67
  "@xterm/headless": "^6.0",
68
68
  "ajv": "^8.20",
@@ -131,6 +131,25 @@ export function touchLastSeen(reg: Registry, sessionId: string | undefined, now:
131
131
  return true;
132
132
  }
133
133
 
134
+ /** True iff this manager should self-recycle because it is running a STALE on-disk
135
+ * binary. Only for a COMPILED install: after `brew upgrade` + `brew cleanup`, the old
136
+ * versioned binary the manager was launched from is DELETED, yet the process lingers
137
+ * on the freed inode — and can no longer spawn workers/spares (spawn ENOENT), which
138
+ * surfaces as the extension's "xcsh didn't start". Detecting the deleted binary lets
139
+ * the manager step down so a fresh one (launched from the version-stable PATH wrapper)
140
+ * takes over. Dev (`bun src/cli.ts`) has a live interpreter execPath → never stale.
141
+ * Fail-closed: any error from `exists` → NOT stale, so uncertainty never triggers a
142
+ * recycle loop. Only the unambiguous deleted-binary signal is used (a version/realpath
143
+ * drift signal was deliberately NOT added — false positives would flap-recycle). */
144
+ export function binaryIsStale(opts: { compiled: boolean; execPath: string; exists: (p: string) => boolean }): boolean {
145
+ if (!opts.compiled) return false;
146
+ try {
147
+ return !opts.exists(opts.execPath);
148
+ } catch {
149
+ return false;
150
+ }
151
+ }
152
+
134
153
  /** The NDJSON keepalive an actively-chatting worker writes to the manager control
135
154
  * socket to refresh its `lastSeen` (consumed by `touchLastSeen`). Null for the
136
155
  * unbound `spare` sentinel or an empty id — a spare has no session to keep alive. */
@@ -26,8 +26,11 @@ import { Command } from "@f5-sales-demo/pi-utils/cli";
26
26
  // slows the manager's cold start — keep the daemon's module graph minimal).
27
27
  import { VERSION } from "@f5-sales-demo/pi-utils/dirs";
28
28
  import { portCandidates } from "../browser/extension-bridge";
29
+ // Lean standalone fn (compiled-runtime detection) — no heavy graph, safe for the daemon.
30
+ import { detectCompiledRuntime } from "../internal-urls/build-info-runtime";
29
31
  import { removeManagerState, writeManagerState } from "../services/manager-state";
30
32
  import {
33
+ binaryIsStale,
31
34
  needsProvision,
32
35
  parseControlMsg,
33
36
  pickPort,
@@ -209,6 +212,17 @@ export default class Manager extends Command {
209
212
  // (production always reports the real binary VERSION).
210
213
  const selfVersion = process.env.XCSH_MANAGER_VERSION || VERSION;
211
214
 
215
+ // Durable-upgrade self-recycle (#upgrade-recycle): a compiled manager whose
216
+ // on-disk binary was removed by `brew upgrade`+`brew cleanup` lingers on the
217
+ // freed inode but can no longer spawn workers (ENOENT) → it must step down so a
218
+ // fresh manager (from the version-stable PATH wrapper) takes over. Dev is never
219
+ // compiled → never stale. `XCSH_FORCE_STALE=1` lets the dev-mode integration test
220
+ // exercise the recycle path (same env-override convention as XCSH_MANAGER_VERSION).
221
+ const compiled = detectCompiledRuntime(import.meta.url, Bun.env);
222
+ const isSelfStale = (): boolean =>
223
+ process.env.XCSH_FORCE_STALE === "1" ||
224
+ binaryIsStale({ compiled, execPath: process.execPath, exists: fs.existsSync });
225
+
212
226
  // Lifecycle gate (#1874): once we begin graceful shutdown we stop accepting
213
227
  // provisions (the host retries the successor manager) and never double-run
214
228
  // the teardown.
@@ -430,13 +444,22 @@ export default class Manager extends Command {
430
444
  const msg = parseControlMsg(raw);
431
445
  if (!msg) return; // fail closed on unknown/invalid frames
432
446
  if (msg.type === "provision") {
433
- // Draining: refuse new work so the host retries the successor manager.
434
- if (!accepting) {
447
+ // Draining, OR self-stale (our binary was removed by an upgrade): refuse
448
+ // this provision so the host retries the successor manager — spawning now
449
+ // would ENOENT on the deleted binary. If stale-but-still-accepting, step
450
+ // down immediately so the fresh manager takes over in seconds (not on the
451
+ // ≤60s sweep). Reuses the existing draining reply/contract.
452
+ const stale = accepting && isSelfStale();
453
+ if (!accepting || stale) {
435
454
  try {
436
455
  socket.write(`${JSON.stringify({ type: "draining" })}\n`);
437
456
  } catch {
438
457
  /* client hung up */
439
458
  }
459
+ if (stale) {
460
+ console.error("[xcsh manager] binary is stale (upgraded/removed) — recycling on provision");
461
+ gracefulShutdown("updated");
462
+ }
440
463
  return;
441
464
  }
442
465
  const provisionReceivedAt = Date.now(); // TTFT Phase 2: start of manager_provision
@@ -537,8 +560,15 @@ export default class Manager extends Command {
537
560
  // Pre-warm the spare pool so provisions can adopt instead of cold-spawn.
538
561
  if (poolTarget > 0) maintainPool();
539
562
 
540
- // Idle sweep: reap workers untouched for longer than the TTL.
563
+ // Idle sweep: reap workers untouched for longer than the TTL. Also self-recycle
564
+ // if our own binary went stale (upgrade removed it) — a lingering deleted-binary
565
+ // manager can't spawn workers, so step down for a fresh one (#upgrade-recycle).
541
566
  setInterval(() => {
567
+ if (isSelfStale()) {
568
+ console.error("[xcsh manager] binary is stale (upgraded/removed) — recycling on sweep");
569
+ gracefulShutdown("updated");
570
+ return;
571
+ }
542
572
  for (const key of staleKeys(reg, Date.now(), IDLE_MS)) reap(key);
543
573
  }, SWEEP_MS);
544
574
 
@@ -17,17 +17,17 @@ export interface BuildInfo {
17
17
  }
18
18
 
19
19
  export const BUILD_INFO: BuildInfo = {
20
- "version": "19.62.0",
21
- "commit": "002bc2b7d7e14a95e282f940730cbdc347197635",
22
- "shortCommit": "002bc2b",
20
+ "version": "19.62.1",
21
+ "commit": "914880a26dc07e8cbfe5f79dcaef4012e559779d",
22
+ "shortCommit": "914880a",
23
23
  "branch": "main",
24
- "tag": "v19.62.0",
25
- "commitDate": "2026-07-08T02:22:10Z",
26
- "buildDate": "2026-07-08T02:45:44.169Z",
24
+ "tag": "v19.62.1",
25
+ "commitDate": "2026-07-08T16:48:22Z",
26
+ "buildDate": "2026-07-08T17:15:30.572Z",
27
27
  "dirty": true,
28
28
  "prNumber": "",
29
29
  "repoUrl": "https://github.com/f5-sales-demo/xcsh",
30
30
  "repoSlug": "f5-sales-demo/xcsh",
31
- "commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/002bc2b7d7e14a95e282f940730cbdc347197635",
32
- "releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.62.0"
31
+ "commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/914880a26dc07e8cbfe5f79dcaef4012e559779d",
32
+ "releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.62.1"
33
33
  };