codeam-cli 2.52.4 → 2.52.6

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 CHANGED
@@ -4,6 +4,12 @@ All notable changes to `codeam-cli` are documented here.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [2.52.5] — 2026-07-02
8
+
9
+ ### CI
10
+
11
+ - **workflow:** Free runner disk before the JetBrains publish job
12
+
7
13
  ## [2.52.3] — 2026-06-30
8
14
 
9
15
  ### Added
package/dist/index.js CHANGED
@@ -5397,7 +5397,7 @@ function readAnonId() {
5397
5397
  }
5398
5398
  function superProperties() {
5399
5399
  return {
5400
- cliVersion: true ? "2.52.4" : "0.0.0-dev",
5400
+ cliVersion: true ? "2.52.6" : "0.0.0-dev",
5401
5401
  nodeVersion: process.version,
5402
5402
  platform: process.platform,
5403
5403
  arch: process.arch,
@@ -5578,7 +5578,7 @@ var os4 = __toESM(require("os"));
5578
5578
  // package.json
5579
5579
  var package_default = {
5580
5580
  name: "codeam-cli",
5581
- version: "2.52.4",
5581
+ version: "2.52.6",
5582
5582
  description: "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device \u2014 async. The terminal companion for CodeAgent Mobile.",
5583
5583
  type: "commonjs",
5584
5584
  main: "dist/index.js",
@@ -6105,6 +6105,13 @@ function computePollDelay({ baseMs, failures }) {
6105
6105
  }
6106
6106
 
6107
6107
  // src/services/command-relay.service.ts
6108
+ function httpStatusOf(err) {
6109
+ if (typeof err === "object" && err !== null && "statusCode" in err) {
6110
+ const status2 = err.statusCode;
6111
+ if (typeof status2 === "number") return status2;
6112
+ }
6113
+ return null;
6114
+ }
6108
6115
  var API_BASE2 = resolveApiBaseUrl();
6109
6116
  var SSE_LIVENESS_TIMEOUT_MS = 45e3;
6110
6117
  var SSE_WATCHDOG_INTERVAL_MS = 1e4;
@@ -6120,6 +6127,7 @@ var CommandRelayService = class {
6120
6127
  agentMeta;
6121
6128
  agentsOverride;
6122
6129
  _running = false;
6130
+ pairingInvalid = false;
6123
6131
  heartbeatTimer = null;
6124
6132
  agentsTimer = null;
6125
6133
  /** True once `/api/plugin/agents` has accepted at least one report. */
@@ -6197,7 +6205,22 @@ var CommandRelayService = class {
6197
6205
  });
6198
6206
  }
6199
6207
  async sendResult(commandId, status2, result) {
6200
- await _postJson(`${API_BASE2}/api/commands/result`, { commandId, status: status2, result });
6208
+ if (this.pairingInvalid) return;
6209
+ try {
6210
+ await _postJson(`${API_BASE2}/api/commands/result`, { commandId, status: status2, result });
6211
+ } catch (err) {
6212
+ const statusCode = httpStatusOf(err);
6213
+ if (statusCode === 401 || statusCode === 403) {
6214
+ this.pairingInvalid = true;
6215
+ process.stderr.write(
6216
+ "[codeam] This pairing is no longer valid \u2014 run `codeam pair` again to reconnect this session.\n"
6217
+ );
6218
+ log.warn("relay", `pairing invalid (status=${statusCode}) \u2014 relay stopped`);
6219
+ this.stop();
6220
+ return;
6221
+ }
6222
+ throw err;
6223
+ }
6201
6224
  }
6202
6225
  // ─── SSE pull (primary) ──────────────────────────────────────────
6203
6226
  connectSSE() {
@@ -7340,24 +7363,24 @@ async function refreshAuthToken(sessionId, pluginId) {
7340
7363
  },
7341
7364
  JSON.stringify({ sessionId, pluginId })
7342
7365
  );
7343
- if (statusCode === 404) {
7344
- log.warn("chunkEmitter", "[auth] reconnect 404 \u2014 session gone server-side");
7345
- return null;
7366
+ if (statusCode === 404 || statusCode === 401 || statusCode === 403) {
7367
+ log.warn("chunkEmitter", `[auth] reconnect ${statusCode} \u2014 session gone server-side`);
7368
+ return { kind: "gone" };
7346
7369
  }
7347
7370
  if (statusCode >= 400) {
7348
7371
  log.warn("chunkEmitter", `[auth] reconnect failed status=${statusCode}`);
7349
- return null;
7372
+ return { kind: "transient" };
7350
7373
  }
7351
7374
  const parsed = JSON.parse(body);
7352
7375
  const fresh = parsed.data?.pluginAuthToken;
7353
7376
  if (typeof fresh !== "string" || fresh.length === 0) {
7354
7377
  log.warn("chunkEmitter", "[auth] reconnect response missing pluginAuthToken");
7355
- return null;
7378
+ return { kind: "transient" };
7356
7379
  }
7357
- return fresh;
7380
+ return { kind: "fresh", token: fresh };
7358
7381
  } catch (err) {
7359
7382
  log.warn("chunkEmitter", `[auth] reconnect threw: ${String(err)}`);
7360
- return null;
7383
+ return { kind: "transient" };
7361
7384
  }
7362
7385
  }
7363
7386
  var ChunkEmitter = class {
@@ -7375,6 +7398,11 @@ var ChunkEmitter = class {
7375
7398
  opts;
7376
7399
  url = `${API_BASE4}/api/commands/output`;
7377
7400
  headers;
7401
+ /** Latched when the pairing is unrecoverable (401/403 whose refresh
7402
+ * says the session is gone, or a fresh token still rejected) —
7403
+ * every later send short-circuits (2026-06-28 incident: 401 ×34
7404
+ * with a dead token while the user saw nothing). */
7405
+ pairingInvalid = false;
7378
7406
  /**
7379
7407
  * Send a chunk. `body` is the chunk fields minus `sessionId` /
7380
7408
  * `pluginId` — the emitter splices those in. `critical = true`
@@ -7394,7 +7422,11 @@ var ChunkEmitter = class {
7394
7422
  "chunkEmitter",
7395
7423
  `send type=${body.type ?? "(clear)"} bytes=${payload.length} done=${body.done === true}`
7396
7424
  );
7425
+ if (this.pairingInvalid) {
7426
+ return Promise.resolve({ dead: true });
7427
+ }
7397
7428
  return new Promise((resolve7) => {
7429
+ let refreshedOnce = false;
7398
7430
  const attempt = (attemptsLeft) => {
7399
7431
  _transport3.post(this.url, this.headers, payload).then(({ statusCode, body: resBody }) => {
7400
7432
  const tookMs = Date.now() - t0;
@@ -7404,18 +7436,27 @@ var ChunkEmitter = class {
7404
7436
  resolve7({ dead: true });
7405
7437
  return;
7406
7438
  }
7407
- if (statusCode === 401) {
7408
- log.warn("chunkEmitter", `auth 401 took=${tookMs}ms \u2014 attempting silent refresh`);
7439
+ if (statusCode === 401 || statusCode === 403) {
7440
+ if (refreshedOnce) {
7441
+ this.markPairingInvalid(statusCode, tookMs);
7442
+ resolve7({ dead: true });
7443
+ return;
7444
+ }
7445
+ log.warn("chunkEmitter", `auth ${statusCode} took=${tookMs}ms \u2014 attempting silent refresh`);
7409
7446
  void (async () => {
7410
- const fresh = await refreshAuthToken(this.opts.sessionId, this.opts.pluginId);
7411
- if (fresh) {
7412
- this.headers["X-Plugin-Auth-Token"] = fresh;
7413
- this.opts.pluginAuthToken = fresh;
7447
+ const refresh = await refreshAuthToken(this.opts.sessionId, this.opts.pluginId);
7448
+ if (refresh.kind === "fresh") {
7449
+ this.headers["X-Plugin-Auth-Token"] = refresh.token;
7450
+ this.opts.pluginAuthToken = refresh.token;
7451
+ refreshedOnce = true;
7414
7452
  log.info("chunkEmitter", "auth refreshed silently");
7415
- if (attemptsLeft > 0 || opts.critical) {
7416
- attempt(Math.max(attemptsLeft, 1));
7417
- return;
7418
- }
7453
+ attempt(Math.max(attemptsLeft, 1));
7454
+ return;
7455
+ }
7456
+ if (refresh.kind === "gone") {
7457
+ this.markPairingInvalid(statusCode, tookMs);
7458
+ resolve7({ dead: true });
7459
+ return;
7419
7460
  }
7420
7461
  resolve7({ dead: false });
7421
7462
  })();
@@ -7446,6 +7487,17 @@ var ChunkEmitter = class {
7446
7487
  attempt(maxRetries);
7447
7488
  });
7448
7489
  }
7490
+ markPairingInvalid(statusCode, tookMs) {
7491
+ if (this.pairingInvalid) return;
7492
+ this.pairingInvalid = true;
7493
+ process.stderr.write(
7494
+ "[codeam] This pairing is no longer valid \u2014 run `codeam pair` again to reconnect this session.\n"
7495
+ );
7496
+ log.warn(
7497
+ "chunkEmitter",
7498
+ `pairing invalid (status=${statusCode}) took=${tookMs}ms \u2014 emitter latched, no further posts`
7499
+ );
7500
+ }
7449
7501
  };
7450
7502
  var _transport3 = {
7451
7503
  post: _post2
@@ -14876,7 +14928,7 @@ async function autoUpgradeBeforeCriticalCommand() {
14876
14928
  if (process.env.NODE_ENV === "test") return;
14877
14929
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
14878
14930
  if (process.env.CI) return;
14879
- const current = true ? "2.52.4" : null;
14931
+ const current = true ? "2.52.6" : null;
14880
14932
  if (!current) return;
14881
14933
  const cache = readCache();
14882
14934
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -14893,7 +14945,7 @@ function checkForUpdates() {
14893
14945
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
14894
14946
  if (process.env.CI) return;
14895
14947
  if (!process.stdout.isTTY) return;
14896
- const current = true ? "2.52.4" : null;
14948
+ const current = true ? "2.52.6" : null;
14897
14949
  if (!current) return;
14898
14950
  const cache = readCache();
14899
14951
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -15595,7 +15647,7 @@ var defaultSpawner = (env, cwd, args2 = []) => (0, import_node_child_process15.s
15595
15647
  detached: false
15596
15648
  });
15597
15649
  function currentCliVersion() {
15598
- return true ? "2.52.4" : null;
15650
+ return true ? "2.52.6" : null;
15599
15651
  }
15600
15652
  function runCmd(cmd, args2, timeoutMs) {
15601
15653
  return new Promise((resolve7) => {
@@ -24652,6 +24704,10 @@ var AcpPublisher = class {
24652
24704
  opts;
24653
24705
  apiBase;
24654
24706
  token;
24707
+ /** Latched on an unrecoverable 401/403 — every surface stops posting
24708
+ * (2026-06-28 incident: a dead-token publisher spammed 401 ×34 while
24709
+ * the agent's replies silently never reached the phone). */
24710
+ pairingInvalid = false;
24655
24711
  authHeaders() {
24656
24712
  return {
24657
24713
  "Content-Type": "application/json",
@@ -24667,14 +24723,39 @@ var AcpPublisher = class {
24667
24723
  * response unchanged — callers log non-2xx but never throw.
24668
24724
  */
24669
24725
  async postWithReauth(url, payload) {
24726
+ if (this.pairingInvalid) {
24727
+ return { statusCode: 401, body: "PAIRING_INVALID" };
24728
+ }
24670
24729
  const first = await _transport4.post(url, this.authHeaders(), payload);
24671
24730
  if (first.statusCode !== 401 && first.statusCode !== 403) return first;
24672
- if (!this.opts.refreshAuthToken) return first;
24673
- const fresh = await this.opts.refreshAuthToken();
24674
- if (!fresh) return first;
24675
- this.token = fresh;
24676
- log.info("acpPublisher", `plugin-auth token refreshed after ${first.statusCode}; retrying POST`);
24677
- return _transport4.post(url, this.authHeaders(), payload);
24731
+ if (this.opts.refreshAuthToken) {
24732
+ const fresh = await this.opts.refreshAuthToken();
24733
+ if (fresh) {
24734
+ this.token = fresh;
24735
+ log.info("acpPublisher", `plugin-auth token refreshed after ${first.statusCode}; retrying POST`);
24736
+ const second = await _transport4.post(url, this.authHeaders(), payload);
24737
+ if (second.statusCode !== 401 && second.statusCode !== 403) return second;
24738
+ this.markPairingInvalid(second.statusCode);
24739
+ return second;
24740
+ }
24741
+ }
24742
+ this.markPairingInvalid(first.statusCode);
24743
+ return first;
24744
+ }
24745
+ markPairingInvalid(statusCode) {
24746
+ if (this.pairingInvalid) return;
24747
+ this.pairingInvalid = true;
24748
+ process.stderr.write(
24749
+ "[codeam] This pairing is no longer valid \u2014 run `codeam pair` again to reconnect this session.\n"
24750
+ );
24751
+ log.warn(
24752
+ "acpPublisher",
24753
+ `pairing invalid (status=${statusCode}) \u2014 publisher latched, no further posts`
24754
+ );
24755
+ try {
24756
+ this.opts.onPairingInvalid?.();
24757
+ } catch {
24758
+ }
24678
24759
  }
24679
24760
  /**
24680
24761
  * Wrap the body with `sessionId` + `pluginId` at the top level.
@@ -31207,7 +31288,7 @@ function checkChokidar() {
31207
31288
  }
31208
31289
  async function doctor(args2 = []) {
31209
31290
  const json = args2.includes("--json");
31210
- const cliVersion = true ? "2.52.4" : "0.0.0-dev";
31291
+ const cliVersion = true ? "2.52.6" : "0.0.0-dev";
31211
31292
  const apiBase2 = resolveApiBaseUrl();
31212
31293
  const diagnosticId = (0, import_node_crypto8.randomUUID)();
31213
31294
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -31406,7 +31487,7 @@ async function completion(args2) {
31406
31487
  // src/commands/version.ts
31407
31488
  var import_picocolors15 = __toESM(require("picocolors"));
31408
31489
  function version2() {
31409
- const v = true ? "2.52.4" : "unknown";
31490
+ const v = true ? "2.52.6" : "unknown";
31410
31491
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
31411
31492
  }
31412
31493
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.52.4",
3
+ "version": "2.52.6",
4
4
  "description": "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device — async. The terminal companion for CodeAgent Mobile.",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",