codeam-cli 2.23.3 → 2.23.5

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,18 @@ 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.23.4] — 2026-05-25
8
+
9
+ ### Fixed
10
+
11
+ - **cli+workflow:** Unblock prompt flow + restore PostHog telemetry (#192)
12
+
13
+ ## [2.23.3] — 2026-05-25
14
+
15
+ ### Fixed
16
+
17
+ - **cli:** Reap in-flight spawnAndCapture children on sigint/exit (#191)
18
+
7
19
  ## [2.23.2] — 2026-05-25
8
20
 
9
21
  ### Fixed
package/dist/index.js CHANGED
@@ -441,7 +441,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
441
441
  // package.json
442
442
  var package_default = {
443
443
  name: "codeam-cli",
444
- version: "2.23.3",
444
+ version: "2.23.5",
445
445
  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.",
446
446
  type: "commonjs",
447
447
  main: "dist/index.js",
@@ -5768,7 +5768,7 @@ function readAnonId() {
5768
5768
  }
5769
5769
  function superProperties() {
5770
5770
  return {
5771
- cliVersion: true ? "2.23.3" : "0.0.0-dev",
5771
+ cliVersion: true ? "2.23.5" : "0.0.0-dev",
5772
5772
  nodeVersion: process.version,
5773
5773
  platform: process.platform,
5774
5774
  arch: process.arch,
@@ -5781,7 +5781,7 @@ function initTelemetry() {
5781
5781
  log.trace("telemetry", "opted out (CODEAM_TELEMETRY / CI / NODE_ENV=test)");
5782
5782
  return false;
5783
5783
  }
5784
- const apiKey = true ? "" : "";
5784
+ const apiKey = true ? "phc_Otx6LUf0KeabkbTnPuLPLg6d6r8319k8bKDYqoXyeb4" : "";
5785
5785
  if (!apiKey) {
5786
5786
  log.trace("telemetry", "no PostHog API key baked into build \u2014 disabled");
5787
5787
  return false;
@@ -5860,6 +5860,8 @@ function maybePrintFirstRunBanner() {
5860
5860
 
5861
5861
  // src/services/command-relay.service.ts
5862
5862
  var API_BASE2 = resolveApiBaseUrl();
5863
+ var SSE_LIVENESS_TIMEOUT_MS = 45e3;
5864
+ var SSE_WATCHDOG_INTERVAL_MS = 1e4;
5863
5865
  var CommandRelayService = class {
5864
5866
  constructor(pluginId, onCommand, agentMeta) {
5865
5867
  this.pluginId = pluginId;
@@ -5889,10 +5891,25 @@ var CommandRelayService = class {
5889
5891
  /** Reconnect backoff state for the SSE stream. */
5890
5892
  sseFailures = 0;
5891
5893
  sseReconnectTimer = null;
5894
+ /**
5895
+ * Liveness watchdog for the SSE socket. The server pings every 30 s
5896
+ * (`pending-stream.controller.ts` interval(30_000)). If we go more
5897
+ * than `SSE_LIVENESS_TIMEOUT_MS` without ANY bytes, we assume the
5898
+ * TCP peer died half-open (Cloud Run scaled / redeployed mid-stream,
5899
+ * NAT dropped the conntrack entry, laptop slept) and force a
5900
+ * reconnect. Without this the kernel can keep the socket in a
5901
+ * zombie ESTABLISHED state for HOURS before the default TCP
5902
+ * keepalive triggers — observed live in #190 where a 17:18 redeploy
5903
+ * left CLIs subscribed for >30 minutes without ever receiving a
5904
+ * pushed command.
5905
+ */
5906
+ sseWatchdog = null;
5907
+ sseLastByteAt = 0;
5892
5908
  start() {
5893
5909
  this.cleanup();
5894
5910
  this._running = true;
5895
5911
  this.agentsRegistered = false;
5912
+ log.info("relay", `start pluginId=${this.pluginId.slice(0, 8)} agent=${this.agentMeta.id}`);
5896
5913
  this.sendHeartbeat(true);
5897
5914
  this.heartbeatTimer = setInterval(() => this.sendHeartbeat(true), 2e4);
5898
5915
  this.agentsTimer = setInterval(() => {
@@ -5900,6 +5917,7 @@ var CommandRelayService = class {
5900
5917
  }, 5e3);
5901
5918
  this.reportAgents();
5902
5919
  if (process.env.NODE_ENV === "test" || process.env.CODEAM_DISABLE_SSE_PULL === "1") {
5920
+ log.info("relay", "SSE disabled \u2014 using polling fallback");
5903
5921
  this.startPollingFallback();
5904
5922
  } else {
5905
5923
  this.connectSSE();
@@ -5921,7 +5939,7 @@ var CommandRelayService = class {
5921
5939
  const url = new URL(`${API_BASE2}/api/commands/pending/stream`);
5922
5940
  url.searchParams.set("pluginId", this.pluginId);
5923
5941
  const transport = url.protocol === "https:" ? https2 : http2;
5924
- log.trace("relay", `sse connect ${url.pathname}`);
5942
+ log.info("relay", `sse connect pluginId=${this.pluginId.slice(0, 8)}`);
5925
5943
  const req = transport.request(
5926
5944
  {
5927
5945
  hostname: url.hostname,
@@ -5933,11 +5951,11 @@ var CommandRelayService = class {
5933
5951
  },
5934
5952
  (res) => {
5935
5953
  if (res.statusCode !== 200) {
5936
- log.trace("relay", `sse status=${res.statusCode}`);
5954
+ log.info("relay", `sse status=${res.statusCode} \u2014 backing off`);
5937
5955
  res.resume();
5938
5956
  this.sseFailures += 1;
5939
5957
  if (this.sseFailures >= 2) {
5940
- log.trace("relay", "sse unavailable, falling back to polling");
5958
+ log.info("relay", "sse unavailable \u2014 falling back to polling");
5941
5959
  capture("sse_fallback_to_poll", {
5942
5960
  pluginId: this.pluginId,
5943
5961
  agentId: this.agentMeta.id,
@@ -5950,10 +5968,13 @@ var CommandRelayService = class {
5950
5968
  this.scheduleSseReconnect();
5951
5969
  return;
5952
5970
  }
5971
+ log.info("relay", "sse connected");
5953
5972
  this.sseFailures = 0;
5973
+ this.armSseWatchdog();
5954
5974
  let buffer = "";
5955
5975
  res.setEncoding("utf8");
5956
5976
  res.on("data", (chunk) => {
5977
+ this.sseLastByteAt = Date.now();
5957
5978
  buffer += chunk;
5958
5979
  let frameEnd;
5959
5980
  while ((frameEnd = buffer.indexOf("\n\n")) !== -1) {
@@ -5963,15 +5984,26 @@ var CommandRelayService = class {
5963
5984
  }
5964
5985
  });
5965
5986
  res.on("end", () => {
5987
+ log.info("relay", "sse end \u2014 reconnecting");
5988
+ this.disarmSseWatchdog();
5966
5989
  if (this._running) this.scheduleSseReconnect();
5967
5990
  });
5968
- res.on("error", () => {
5991
+ res.on("error", (err) => {
5992
+ log.info("relay", `sse res error \u2014 reconnecting (${err.message})`);
5993
+ this.disarmSseWatchdog();
5969
5994
  if (this._running) this.scheduleSseReconnect();
5970
5995
  });
5971
5996
  }
5972
5997
  );
5998
+ req.on("socket", (socket) => {
5999
+ try {
6000
+ socket.setKeepAlive(true, 3e4);
6001
+ } catch {
6002
+ }
6003
+ });
5973
6004
  req.on("error", (err) => {
5974
- log.trace("relay", "sse req error", err);
6005
+ log.info("relay", `sse req error \u2014 ${err.message}`);
6006
+ this.disarmSseWatchdog();
5975
6007
  this.sseFailures += 1;
5976
6008
  if (this.sseFailures >= 2) {
5977
6009
  capture("sse_fallback_to_poll", {
@@ -5986,11 +6018,43 @@ var CommandRelayService = class {
5986
6018
  this.scheduleSseReconnect();
5987
6019
  });
5988
6020
  req.on("timeout", () => {
6021
+ log.info("relay", "sse req timeout \u2014 destroying + reconnecting");
5989
6022
  req.destroy();
5990
6023
  });
5991
6024
  req.end();
5992
6025
  this.sseRequest = req;
5993
6026
  }
6027
+ // ─── SSE liveness watchdog ───────────────────────────────────────
6028
+ //
6029
+ // The server pings every 30 s; if more than SSE_LIVENESS_TIMEOUT_MS
6030
+ // pass without a byte, we assume the TCP peer is dead-zombie and
6031
+ // force the request to destroy → triggers `error`/`end` → standard
6032
+ // reconnect path. Without this, observed in the field: half-open
6033
+ // socket sits ESTABLISHED for 30 + minutes (until macOS default
6034
+ // 2 h TCP keepalive kicks in) and the CLI silently misses every
6035
+ // pushed command.
6036
+ armSseWatchdog() {
6037
+ this.disarmSseWatchdog();
6038
+ this.sseLastByteAt = Date.now();
6039
+ this.sseWatchdog = setInterval(() => {
6040
+ const idle = Date.now() - this.sseLastByteAt;
6041
+ if (idle > SSE_LIVENESS_TIMEOUT_MS) {
6042
+ log.info("relay", `sse watchdog \u2014 ${idle}ms since last byte, forcing reconnect`);
6043
+ try {
6044
+ this.sseRequest?.destroy();
6045
+ } catch {
6046
+ }
6047
+ this.disarmSseWatchdog();
6048
+ }
6049
+ }, SSE_WATCHDOG_INTERVAL_MS);
6050
+ this.sseWatchdog.unref?.();
6051
+ }
6052
+ disarmSseWatchdog() {
6053
+ if (this.sseWatchdog) {
6054
+ clearInterval(this.sseWatchdog);
6055
+ this.sseWatchdog = null;
6056
+ }
6057
+ }
5994
6058
  handleSseFrame(frame) {
5995
6059
  let event = "message";
5996
6060
  let data = "";
@@ -6003,10 +6067,13 @@ var CommandRelayService = class {
6003
6067
  const parsed = JSON.parse(data);
6004
6068
  const commands = parsed.commands ?? [];
6005
6069
  if (commands.length === 0) return;
6006
- log.trace("relay", `sse received ${commands.length} command(s)`);
6070
+ log.info(
6071
+ "relay",
6072
+ `sse received ${commands.length} command(s) types=[${commands.map((c2) => c2.type).join(",")}]`
6073
+ );
6007
6074
  void this.dispatchCommands(commands);
6008
6075
  } catch (err) {
6009
- log.trace("relay", "sse parse error", err);
6076
+ log.info("relay", "sse parse error", err);
6010
6077
  }
6011
6078
  }
6012
6079
  scheduleSseReconnect() {
@@ -6064,7 +6131,8 @@ var CommandRelayService = class {
6064
6131
  await _postJson(`${API_BASE2}/api/plugin/heartbeat`, {
6065
6132
  pluginId: this.pluginId,
6066
6133
  online,
6067
- agentId: this.agentMeta.id
6134
+ agentId: this.agentMeta.id,
6135
+ branch: detectCurrentBranch()
6068
6136
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
6069
6137
  }
6070
6138
  reportAgents() {
@@ -6101,6 +6169,7 @@ var CommandRelayService = class {
6101
6169
  clearTimeout(this.sseReconnectTimer);
6102
6170
  this.sseReconnectTimer = null;
6103
6171
  }
6172
+ this.disarmSseWatchdog();
6104
6173
  if (this.sseRequest) {
6105
6174
  try {
6106
6175
  this.sseRequest.destroy();
@@ -12601,11 +12670,17 @@ var FileWatcherService = class {
12601
12670
  } : {},
12602
12671
  awaitWriteFinish: {
12603
12672
  // Coalesces rapid sequential writes (npm install spam, build
12604
- // tools emitting bursts). Lower than chokidar's default so
12605
- // the user sees their Files screen update within 0.5 s of
12606
- // saving.
12607
- stabilityThreshold: 150,
12608
- pollInterval: 50
12673
+ // tools emitting bursts). Tuned for monorepo workloads after
12674
+ // observing a CLI process spike to 126% CPU on the codeagent
12675
+ // monorepo: pollInterval=50ms caused thousands of fs.stat
12676
+ // calls per second when many files were being edited
12677
+ // concurrently (agent edits + AI summary subprocess writes +
12678
+ // git status outputs all touching files in the watched tree).
12679
+ // 200ms is still well below the typical user perception
12680
+ // threshold for "the file appeared in the Files screen" while
12681
+ // keeping the polling cost bounded.
12682
+ stabilityThreshold: 300,
12683
+ pollInterval: 200
12609
12684
  }
12610
12685
  });
12611
12686
  watcher.on("add", (filePath) => this.schedule(filePath, "add"));
@@ -18326,7 +18401,7 @@ function checkChokidar() {
18326
18401
  }
18327
18402
  async function doctor(args2 = []) {
18328
18403
  const json = args2.includes("--json");
18329
- const cliVersion = true ? "2.23.3" : "0.0.0-dev";
18404
+ const cliVersion = true ? "2.23.5" : "0.0.0-dev";
18330
18405
  const apiBase = resolveApiBaseUrl();
18331
18406
  const diagnosticId = (0, import_node_crypto5.randomUUID)();
18332
18407
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -18525,7 +18600,7 @@ async function completion(args2) {
18525
18600
  // src/commands/version.ts
18526
18601
  var import_picocolors13 = __toESM(require("picocolors"));
18527
18602
  function version2() {
18528
- const v = true ? "2.23.3" : "unknown";
18603
+ const v = true ? "2.23.5" : "unknown";
18529
18604
  console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
18530
18605
  }
18531
18606
 
@@ -18753,7 +18828,7 @@ function checkForUpdates() {
18753
18828
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
18754
18829
  if (process.env.CI) return;
18755
18830
  if (!process.stdout.isTTY) return;
18756
- const current = true ? "2.23.3" : null;
18831
+ const current = true ? "2.23.5" : null;
18757
18832
  if (!current) return;
18758
18833
  const cache = readCache();
18759
18834
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.23.3",
3
+ "version": "2.23.5",
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",