codeam-cli 2.75.7 → 2.75.8

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,13 @@ 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.75.7] — 2026-09-19
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** Strip the retired Headroom proxy from ~/.claude/settings.json on start
12
+ - **cli:** Log.info takes (tag, msg) — caught by CI typecheck
13
+
7
14
  ## [2.75.6] — 2026-09-17
8
15
 
9
16
  ### CI
package/dist/index.js CHANGED
@@ -10344,7 +10344,7 @@ function readAnonId() {
10344
10344
  }
10345
10345
  function superProperties() {
10346
10346
  return {
10347
- cliVersion: true ? "2.75.7" : "0.0.0-dev",
10347
+ cliVersion: true ? "2.75.8" : "0.0.0-dev",
10348
10348
  nodeVersion: process.version,
10349
10349
  platform: process.platform,
10350
10350
  arch: process.arch,
@@ -10586,7 +10586,7 @@ var http = __toESM(require("http"));
10586
10586
  // package.json
10587
10587
  var package_default = {
10588
10588
  name: "codeam-cli",
10589
- version: "2.75.7",
10589
+ version: "2.75.8",
10590
10590
  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.",
10591
10591
  type: "commonjs",
10592
10592
  main: "dist/index.js",
@@ -11384,6 +11384,9 @@ var streamHost = new StreamHostSelector(
11384
11384
  );
11385
11385
 
11386
11386
  // src/services/command-relay.service.ts
11387
+ function isRecord2(v) {
11388
+ return typeof v === "object" && v !== null;
11389
+ }
11387
11390
  function httpStatusOf(err) {
11388
11391
  if (typeof err === "object" && err !== null && "statusCode" in err) {
11389
11392
  const status2 = err.statusCode;
@@ -11394,6 +11397,8 @@ function httpStatusOf(err) {
11394
11397
  var API_BASE2 = resolveApiBaseUrl();
11395
11398
  var PENDING_STREAM_PATH = "/api/commands/pending/stream";
11396
11399
  var SSE_LIVENESS_TIMEOUT_MS = 45e3;
11400
+ var HEARTBEAT_RETRY_MS = 3e3;
11401
+ var HEARTBEAT_WARN_INTERVAL_MS = 6e4;
11397
11402
  var SSE_WATCHDOG_INTERVAL_MS = 1e4;
11398
11403
  var CommandRelayService = class _CommandRelayService {
11399
11404
  constructor(pluginId, onCommand, agentMeta, agentsOverride, explicitPollSecret, onHeartbeat) {
@@ -11412,6 +11417,18 @@ var CommandRelayService = class _CommandRelayService {
11412
11417
  onHeartbeat;
11413
11418
  _running = false;
11414
11419
  pairingInvalid = false;
11420
+ /**
11421
+ * Heartbeat health. Before 2026-09-23 a failed heartbeat was a `log.trace`,
11422
+ * i.e. INVISIBLE in a normal `~/.codeam/debug-*.log` — a user whose beats
11423
+ * never reached the origin (every other request did) spent a day on "Server
11424
+ * Failed to Start" with nothing in his log to show for it. Failures are now
11425
+ * WARN (rate-limited per streak) + a PostHog event, and the process exits
11426
+ * when the backend answers `paired:false`.
11427
+ */
11428
+ heartbeatFailStreak = 0;
11429
+ heartbeatLastWarnAt = 0;
11430
+ sessionGone = false;
11431
+ onSessionGone;
11415
11432
  heartbeatTimer = null;
11416
11433
  agentsTimer = null;
11417
11434
  /** Injectable for tests; defaults to real fs/fetch/spawn wiring. */
@@ -11530,6 +11547,17 @@ var CommandRelayService = class _CommandRelayService {
11530
11547
  stop() {
11531
11548
  void this.stopAndFlush();
11532
11549
  }
11550
+ /**
11551
+ * Called ONCE when a heartbeat answers `paired:false` — the backend no longer
11552
+ * has a live session for this pluginId (deleted / disconnected from the
11553
+ * app). The relay has already stopped itself by then; the owner should exit
11554
+ * the process like a Ctrl-C would. Backstop for a process whose
11555
+ * `session_terminated` command was consumed by a sibling on the same
11556
+ * pluginId (two `codeam start` on one saved session, 2026-09-23).
11557
+ */
11558
+ setOnSessionGone(cb) {
11559
+ this.onSessionGone = cb;
11560
+ }
11533
11561
  /**
11534
11562
  * Same teardown as {@link stop}, but resolves only once the `online:false`
11535
11563
  * heartbeat POST has settled — so a caller can `await` it before exiting.
@@ -11877,8 +11905,9 @@ var CommandRelayService = class _CommandRelayService {
11877
11905
  });
11878
11906
  }
11879
11907
  // ─── Heartbeat + agents ──────────────────────────────────────────
11880
- async sendHeartbeat(online) {
11881
- await _postJson(`${API_BASE2}/api/plugin/heartbeat`, {
11908
+ async sendHeartbeat(online, attempt = 0) {
11909
+ if (this.sessionGone && online) return;
11910
+ const body = {
11882
11911
  pluginId: this.pluginId,
11883
11912
  online,
11884
11913
  agentId: this.agentMeta.id,
@@ -11887,8 +11916,58 @@ var CommandRelayService = class _CommandRelayService {
11887
11916
  // fresh + clear the "CLI update available" banner after a self-update
11888
11917
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
11889
11918
  // pair/reconnect). Older backends ignore the extra field.
11890
- ..."2.75.7" ? { ideVersion: "2.75.7" } : {}
11891
- }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
11919
+ ..."2.75.8" ? { ideVersion: "2.75.8" } : {}
11920
+ };
11921
+ try {
11922
+ const res = await _postJson(`${API_BASE2}/api/plugin/heartbeat`, body);
11923
+ this.heartbeatFailStreak = 0;
11924
+ log.trace("relay", `heartbeat ok online=${online}`);
11925
+ const data = res?.data;
11926
+ if (online && isRecord2(data) && data.paired === false) this.handleSessionGone();
11927
+ } catch (err) {
11928
+ if (!online) {
11929
+ log.trace("relay", "goodbye heartbeat failed", err);
11930
+ return;
11931
+ }
11932
+ this.heartbeatFailStreak += 1;
11933
+ this.noteHeartbeatFailure(err);
11934
+ if (attempt === 0 && this._running) {
11935
+ const t2 = setTimeout(() => {
11936
+ void this.sendHeartbeat(true, 1);
11937
+ }, HEARTBEAT_RETRY_MS);
11938
+ t2.unref?.();
11939
+ }
11940
+ }
11941
+ }
11942
+ noteHeartbeatFailure(err) {
11943
+ const status2 = httpStatusOf(err);
11944
+ const code = err?.code;
11945
+ const reason = status2 ? `status_${status2}` : typeof code === "string" ? code : (err instanceof Error ? err.message : String(err)).slice(0, 80);
11946
+ const now = Date.now();
11947
+ const firstOfStreak = this.heartbeatFailStreak === 1;
11948
+ if (!firstOfStreak && now - this.heartbeatLastWarnAt < HEARTBEAT_WARN_INTERVAL_MS) return;
11949
+ this.heartbeatLastWarnAt = now;
11950
+ log.warn(
11951
+ "relay",
11952
+ `heartbeat failed (${reason}, streak=${this.heartbeatFailStreak}) \u2014 two misses in a row and the app shows this session OFFLINE even though it is running`
11953
+ );
11954
+ capture("heartbeat_failed", {
11955
+ pluginId: this.pluginId,
11956
+ agentId: this.agentMeta.id,
11957
+ reason,
11958
+ streak: this.heartbeatFailStreak
11959
+ });
11960
+ }
11961
+ handleSessionGone() {
11962
+ if (this.sessionGone) return;
11963
+ log.warn("relay", "backend reports this session no longer exists (paired:false) \u2014 stopping");
11964
+ capture("session_gone_detected", { pluginId: this.pluginId, agentId: this.agentMeta.id });
11965
+ process.stderr.write(
11966
+ "[codeam] This session was deleted or disconnected from the app \u2014 exiting. Run `codeam pair` to start a new one.\n"
11967
+ );
11968
+ this.stop();
11969
+ this.sessionGone = true;
11970
+ this.onSessionGone?.();
11892
11971
  }
11893
11972
  /**
11894
11973
  * Refresh {@link cachedBranch} without blocking the event loop.
@@ -31218,14 +31297,14 @@ var zGuardCreateElicitationResponseCancel = z7.object({
31218
31297
 
31219
31298
  // ../../node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js
31220
31299
  var CANCEL_REQUEST_METHOD = "$/cancel_request";
31221
- function isRecord2(value) {
31300
+ function isRecord3(value) {
31222
31301
  return typeof value === "object" && value !== null;
31223
31302
  }
31224
31303
  function isJsonRpcId(value) {
31225
31304
  return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
31226
31305
  }
31227
31306
  function cancelRequestId(params) {
31228
- if (!isRecord2(params) || !isJsonRpcId(params["requestId"])) {
31307
+ if (!isRecord3(params) || !isJsonRpcId(params["requestId"])) {
31229
31308
  return void 0;
31230
31309
  }
31231
31310
  return params["requestId"];
@@ -31657,7 +31736,7 @@ var Connection = class {
31657
31736
  if (this.abortController.signal.aborted) {
31658
31737
  return;
31659
31738
  }
31660
- if (!isRecord2(message)) {
31739
+ if (!isRecord3(message)) {
31661
31740
  console.error("Invalid message", { message });
31662
31741
  return;
31663
31742
  }
@@ -31750,7 +31829,7 @@ var Connection = class {
31750
31829
  pendingResponse.cleanup?.();
31751
31830
  if ("result" in response) {
31752
31831
  pendingResponse.resolve(response.result);
31753
- } else if ("error" in response && isRecord2(response.error)) {
31832
+ } else if ("error" in response && isRecord3(response.error)) {
31754
31833
  const { code, message, data } = response.error;
31755
31834
  pendingResponse.reject(new RequestError(code, message, data));
31756
31835
  } else {
@@ -32023,7 +32102,7 @@ function ndJsonStream(output, input) {
32023
32102
  if (trimmedLine) {
32024
32103
  try {
32025
32104
  const message = JSON.parse(trimmedLine);
32026
- if (isRecord2(message)) {
32105
+ if (isRecord3(message)) {
32027
32106
  controller.enqueue(message);
32028
32107
  } else {
32029
32108
  console.warn("Skipping JSON line that is not an object:", trimmedLine);
@@ -40600,6 +40679,7 @@ async function runAcpSession(opts) {
40600
40679
  };
40601
40680
  process.once("SIGINT", () => void shutdown("SIGINT"));
40602
40681
  process.once("SIGTERM", () => void shutdown("SIGTERM"));
40682
+ relay.setOnSessionGone(() => void shutdown("SIGTERM"));
40603
40683
  process.once("SIGHUP", () => void shutdown("SIGHUP"));
40604
40684
  await new Promise(() => {
40605
40685
  });
@@ -42730,6 +42810,7 @@ async function runBatonSession(opts) {
42730
42810
  process.once("SIGINT", onSignal);
42731
42811
  process.once("SIGTERM", onSignal);
42732
42812
  process.once("SIGHUP", onSignal);
42813
+ relay.setOnSessionGone(onSignal);
42733
42814
  showInfo(`Starting ${opts.agent} baton (local) \u2014 native TUI + mobile take-control\u2026`);
42734
42815
  await controller.begin();
42735
42816
  relay.start();
@@ -43170,6 +43251,9 @@ async function start(requestedAgent, presetSession) {
43170
43251
  })
43171
43252
  );
43172
43253
  ctx.relay = relay;
43254
+ relay.setOnSessionGone(() => {
43255
+ void sigintHandler();
43256
+ });
43173
43257
  void beadsReady.then((started) => {
43174
43258
  ctx.beads = started;
43175
43259
  });
@@ -46422,7 +46506,7 @@ async function autoUpgradeBeforeCriticalCommand() {
46422
46506
  if (process.env.NODE_ENV === "test") return;
46423
46507
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
46424
46508
  if (process.env.CI) return;
46425
- const current2 = true ? "2.75.7" : null;
46509
+ const current2 = true ? "2.75.8" : null;
46426
46510
  if (!current2) return;
46427
46511
  const cache = readCache();
46428
46512
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -46439,7 +46523,7 @@ function checkForUpdates() {
46439
46523
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
46440
46524
  if (process.env.CI) return;
46441
46525
  if (!process.stdout.isTTY) return;
46442
- const current2 = true ? "2.75.7" : null;
46526
+ const current2 = true ? "2.75.8" : null;
46443
46527
  if (!current2) return;
46444
46528
  const cache = readCache();
46445
46529
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -46462,7 +46546,7 @@ var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
46462
46546
  var SELF_UPDATE_LS_TIMEOUT_MS = 15e3;
46463
46547
  var SUDO_PREFLIGHT_TIMEOUT_MS = 5e3;
46464
46548
  function currentCliVersion() {
46465
- return true ? "2.75.7" : null;
46549
+ return true ? "2.75.8" : null;
46466
46550
  }
46467
46551
  async function installedVersion(deps) {
46468
46552
  const ls = await deps.run(
@@ -48202,7 +48286,7 @@ function checkChokidar() {
48202
48286
  }
48203
48287
  async function doctor(args2 = []) {
48204
48288
  const json = args2.includes("--json");
48205
- const cliVersion = true ? "2.75.7" : "0.0.0-dev";
48289
+ const cliVersion = true ? "2.75.8" : "0.0.0-dev";
48206
48290
  const apiBase2 = resolveApiBaseUrl();
48207
48291
  const diagnosticId = (0, import_node_crypto12.randomUUID)();
48208
48292
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -48726,7 +48810,7 @@ async function mcpWarm(argv = [], opts = {}) {
48726
48810
  // src/commands/version.ts
48727
48811
  var import_picocolors15 = __toESM(require("picocolors"));
48728
48812
  function version2() {
48729
- const v = true ? "2.75.7" : "unknown";
48813
+ const v = true ? "2.75.8" : "unknown";
48730
48814
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
48731
48815
  }
48732
48816
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.75.7",
3
+ "version": "2.75.8",
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",