@nexus-cortex/cli 4.137.0 → 4.137.2

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/.env.defaults CHANGED
@@ -2061,6 +2061,13 @@ CORTEX_ENDTURN_GATE=true
2061
2061
  # [LEDGER 2026-11-14 @tree] STATUS: built + unit-tested (core/src/system-messages/__tests__/userMd.test.ts 4/4); benches unaffected (no USER.md on
2062
2062
  # bench boxes; set CORTEX_USER_MD=off to be explicit).
2063
2063
  #CORTEX_USER_MD=
2064
+ # CORTEX_STALE_SERVER — what the `cortex` CLI does when the server already on its port is OLDER than the CLI (an update happened) or bound
2065
+ # to a DIFFERENT project directory than the cwd: restart (default) = stop it, wait for the port to free, start it here, verify version +
2066
+ # project | warn = continue on the old server | fail = exit 2. Why: m2 battery 11-14 — `cortex --new` ran on a pre-update server (no USER.md,
2067
+ # no 4.136.1 guard) bound to ~ (memory landed in ~/.cortex), and /health hard-coded version 4.0.0 so nothing could tell.
2068
+ # [LEDGER 2026-11-14 @4.137.1] STATUS: on (default restart); unit tests cli/tests/unit/staleServer.test.ts; e2e on the repl (server in dir A,
2069
+ # CLI in dir B → restarted in B, port 4519 kept). Read by cli/bin/cortex.js (not scanned by check-env-docs).
2070
+ #CORTEX_STALE_SERVER=
2064
2071
  #CORTEX_HERDR_BIN=
2065
2072
  # CORTEX_TERMINAL_BACKEND — R146 HB-HERDR-TERMINAL-BACKEND (4.108.17): the seam under Bash `persistentSession`, TmuxSessionTool and CreateArtifactTool
2066
2073
  # persistent/dev artifacts. auto (default) = herdr pane backend when HERDR_ENV=1 + `herdr` resolvable + `pane list` answers > tmux (TmuxManager)
package/bin/cortex.js CHANGED
@@ -29,6 +29,7 @@ import { existsSync, readFileSync, realpathSync, mkdirSync, openSync, writeFileS
29
29
  import { homedir } from 'os';
30
30
  import { createRequire } from 'module';
31
31
  import { Agent as UndiciAgent } from 'undici';
32
+ import { isStaleServer, staleServerPolicy, wrongProject } from './stale-server.js';
32
33
 
33
34
  // 🔴 ENV BOOTSTRAP FIRST (operator directive: seed+load .env "no matter how
34
35
  // invoked"). This bin historically NEVER ran bootstrapEnv — it read only the raw
@@ -505,10 +506,48 @@ async function ensureKeysOrExit() {
505
506
  process.exit(1);
506
507
  }
507
508
 
509
+ function cliVersion() {
510
+ try { return JSON.parse(readFileSync(join(__cortex_dirname, '..', 'package.json'), 'utf8')).version; } catch { return 'unknown'; }
511
+ }
512
+
508
513
  async function ensureServer() {
509
514
  if (!(await isServerUp())) {
510
515
  await startServer();
516
+ return;
511
517
  }
518
+ // 2026-11-14 (m2 battery C5/C7): a server started BEFORE `cortex update` keeps serving the OLD harness — no USER.md slot, no new guards —
519
+ // and nothing said so. Compare versions; by default restart a stale server (CORTEX_STALE_SERVER=warn|fail to change that).
520
+ let sv, sp;
521
+ try { const h = (await (await fetch(`${BASE_URL}/health`, { signal: AbortSignal.timeout(3000) })).json())?.server; sv = h?.version; sp = h?.projectPath; } catch { /* treat as unknown */ }
522
+ const cv = cliVersion();
523
+ const stale = isStaleServer(sv, cv);
524
+ // a server bound to ANOTHER project would put this turn's memory, files and session there (m2 battery C4) — same policy as a stale one
525
+ const other = !stale && wrongProject(sp, process.env.PROJECT_PATH || process.cwd());
526
+ if (!stale && !other) return;
527
+ const policy = staleServerPolicy(process.env);
528
+ const msg = stale
529
+ ? `[cortex] the server on ${BASE_URL} runs ${sv && sv !== '4.0.0' ? sv : 'an older release'}, this CLI is ${cv}`
530
+ : `[cortex] the server on ${BASE_URL} is bound to ${sp}, but you are in ${process.cwd()}`;
531
+ if (policy === 'warn') { process.stderr.write(`${msg} — continuing on the OLD server (CORTEX_STALE_SERVER=warn)\n`); return; }
532
+ if (policy === 'fail') { process.stderr.write(`${msg} — refusing (CORTEX_STALE_SERVER=fail). Run: cortex --shutdown\n`); process.exit(2); }
533
+ process.stderr.write(`${msg} — restarting it ${stale ? 'so this turn runs the installed version' : 'in this directory'}\n`);
534
+ try { await fetch(`${BASE_URL}/shutdown`, { method: 'POST', signal: AbortSignal.timeout(5000) }); } catch { /* it may already be going */ }
535
+ // wait until the PORT is free, not just until /health stops answering: a server started while the old one still holds the socket hops to
536
+ // port+1 (EADDRINUSE fallback) and this CLI would then talk to nothing (e2e 2026-11-14: new server on 4520, CLI said ready on 4519)
537
+ const { createConnection } = await import('net');
538
+ const portOf = () => Number(new URL(BASE_URL).port || 80);
539
+ const portBusy = () => new Promise((res) => { const c = createConnection({ port: portOf(), host: new URL(BASE_URL).hostname }); c.once('connect', () => { c.destroy(); res(true); }); c.once('error', () => res(false)); });
540
+ let free = 0;
541
+ for (let i = 0; i < 80 && free < 3; i++) { free = (await portBusy()) ? 0 : free + 1; await new Promise((r) => setTimeout(r, 250)); }
542
+ if (free < 3) { process.stderr.write(`[cortex] the old server did not release port ${portOf()} — run: cortex --shutdown\n`); process.exit(2); }
543
+ await startServer();
544
+ // verify the server that answers is the one we wanted (version + project)
545
+ try {
546
+ const h = (await (await fetch(`${BASE_URL}/health`, { signal: AbortSignal.timeout(3000) })).json())?.server;
547
+ if (isStaleServer(h?.version, cv) || wrongProject(h?.projectPath, process.env.PROJECT_PATH || process.cwd())) {
548
+ process.stderr.write(`[cortex] after the restart ${BASE_URL} still answers as ${h?.version} in ${h?.projectPath} — stop it by hand: cortex --shutdown\n`); process.exit(2);
549
+ }
550
+ } catch { /* startServer already verified it is up */ }
512
551
  }
513
552
 
514
553
  async function fetchJSON(path, options = {}, timeoutMs = 30000) {
@@ -0,0 +1,28 @@
1
+ import { realpathSync } from 'fs';
2
+ // Stale-server detection (2026-11-14, m2 battery C5/C7): the CLI attached to a server started before `cortex update` and the turn ran
3
+ // OLD code silently. Pure helpers so the policy is unit-tested (tests/unit/staleServer.test.ts); used by bin/cortex.js ensureServer().
4
+ const parse = (v) => String(v ?? '').split('-')[0].split('.').map((n) => Number.parseInt(n, 10));
5
+
6
+ /** True when the running server is older than this CLI (or reports no / the legacy hard-coded '4.0.0' version). */
7
+ export function isStaleServer(serverVersion, cliVersion) {
8
+ const c = parse(cliVersion);
9
+ if (c.length < 3 || c.some(Number.isNaN)) return false; // unknown CLI version: never force a restart
10
+ const s = parse(serverVersion);
11
+ if (s.length < 3 || s.some(Number.isNaN)) return true;
12
+ for (let i = 0; i < 3; i++) { if (s[i] !== c[i]) return s[i] < c[i]; }
13
+ return false;
14
+ }
15
+
16
+ /** CORTEX_STALE_SERVER = restart (default) | warn | fail. */
17
+ export function staleServerPolicy(env = process.env) {
18
+ const v = String(env.CORTEX_STALE_SERVER ?? '').trim().toLowerCase();
19
+ return v === 'warn' || v === 'fail' ? v : 'restart';
20
+ }
21
+
22
+ /** True when the running server is bound to a DIFFERENT project directory than the caller's cwd (memory, files and sessions would land in
23
+ * the server's project — m2 battery C4). Unknown server project → false (an old server is caught by the version check instead). */
24
+ export function wrongProject(serverProject, cwd) {
25
+ if (!serverProject || !cwd) return false;
26
+ const norm = (p) => { let r = String(p); try { r = realpathSync(r); } catch { /* keep as given */ } return r.replace(/\/+$/, '') || '/'; };
27
+ return norm(serverProject) !== norm(cwd);
28
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"AASA,wBAAsB,SAAS,CAAC,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BlG"}
1
+ {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"AASA,wBAAsB,SAAS,CAAC,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAsClG"}
@@ -36,6 +36,17 @@ export async function updateCli(opts = {}) {
36
36
  if (runCortex(args) !== 0)
37
37
  console.error(theme.colors.error(`Setup of ${OPTIONAL_PACKAGES[k]} did not finish — run: cortex ${args.join(' ')}`));
38
38
  }
39
+ // 2026-11-14 (m2: a server started 11-05 survived three updates and kept serving the old harness): stop a running server so the next
40
+ // call starts the NEW one. The CLI's stale-server check (4.137.1) also catches it; this makes the update itself leave nothing old behind.
41
+ const base = process.env.CORTEX_URL || `http://localhost:${process.env.PORT || '4000'}`;
42
+ try {
43
+ const h = await fetch(`${base}/health`, { signal: AbortSignal.timeout(2000) });
44
+ if (h.ok) {
45
+ await fetch(`${base}/shutdown`, { method: 'POST', signal: AbortSignal.timeout(5000) });
46
+ console.log(theme.colors.muted(`Stopped the running server on ${base} — the next cortex call starts the updated one.`));
47
+ }
48
+ }
49
+ catch { /* no server running */ }
39
50
  console.log(theme.colors.success('\n[OK] Updated. Run "cortex --version" to confirm.'));
40
51
  }
41
52
  //# sourceMappingURL=update.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"update.js","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"update.js","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexus-cortex/cli",
3
- "version": "4.137.0",
3
+ "version": "4.137.2",
4
4
  "description": "Nexus Cortex CLI - Terminal interface for multi-provider LLM orchestration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -19,7 +19,7 @@
19
19
  "prepack": "node ../../scripts/copy-pkg-cortex-scaffold.mjs"
20
20
  },
21
21
  "dependencies": {
22
- "@nexus-cortex/core": "4.137.0",
22
+ "@nexus-cortex/core": "4.137.2",
23
23
  "chalk": "^4.1.2",
24
24
  "cli-spinners": "^2.9.0",
25
25
  "commander": "^11.0.0",