codeam-cli 2.65.7 → 2.65.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.65.7] — 2026-08-15
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** Honest credential/empty-turn errors + partial-fence holdback
12
+ - **cli:** Credential error propagation matches the real transport error shape
13
+
7
14
  ## [2.65.6] — 2026-08-15
8
15
 
9
16
  ### Fixed
package/dist/index.js CHANGED
@@ -8079,7 +8079,7 @@ function readAnonId() {
8079
8079
  }
8080
8080
  function superProperties() {
8081
8081
  return {
8082
- cliVersion: true ? "2.65.7" : "0.0.0-dev",
8082
+ cliVersion: true ? "2.65.8" : "0.0.0-dev",
8083
8083
  nodeVersion: process.version,
8084
8084
  platform: process.platform,
8085
8085
  arch: process.arch,
@@ -8260,7 +8260,7 @@ var os4 = __toESM(require("os"));
8260
8260
  // package.json
8261
8261
  var package_default = {
8262
8262
  name: "codeam-cli",
8263
- version: "2.65.7",
8263
+ version: "2.65.8",
8264
8264
  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.",
8265
8265
  type: "commonjs",
8266
8266
  main: "dist/index.js",
@@ -9358,18 +9358,20 @@ var API_BASE2 = resolveApiBaseUrl();
9358
9358
  var SSE_LIVENESS_TIMEOUT_MS = 45e3;
9359
9359
  var SSE_WATCHDOG_INTERVAL_MS = 1e4;
9360
9360
  var CommandRelayService = class _CommandRelayService {
9361
- constructor(pluginId, onCommand, agentMeta, agentsOverride, explicitPollSecret) {
9361
+ constructor(pluginId, onCommand, agentMeta, agentsOverride, explicitPollSecret, onHeartbeat) {
9362
9362
  this.pluginId = pluginId;
9363
9363
  this.onCommand = onCommand;
9364
9364
  this.agentMeta = agentMeta;
9365
9365
  this.agentsOverride = agentsOverride;
9366
9366
  this.explicitPollSecret = explicitPollSecret;
9367
+ this.onHeartbeat = onHeartbeat;
9367
9368
  }
9368
9369
  pluginId;
9369
9370
  onCommand;
9370
9371
  agentMeta;
9371
9372
  agentsOverride;
9372
9373
  explicitPollSecret;
9374
+ onHeartbeat;
9373
9375
  _running = false;
9374
9376
  pairingInvalid = false;
9375
9377
  heartbeatTimer = null;
@@ -9432,16 +9434,35 @@ var CommandRelayService = class _CommandRelayService {
9432
9434
  * freshness, and a punctual heartbeat matters far more.
9433
9435
  */
9434
9436
  cachedBranch = null;
9437
+ /**
9438
+ * Set whenever the command channel is (re-)established; consumed by the
9439
+ * next {@link emitHeartbeatTick}. See the `onHeartbeat` ctor param.
9440
+ */
9441
+ heartbeatFirstAfterConnect = true;
9442
+ /** Invoke the heartbeat rider (if any) exactly once per beat. Never throws. */
9443
+ emitHeartbeatTick() {
9444
+ if (!this.onHeartbeat) return;
9445
+ const firstAfterConnect = this.heartbeatFirstAfterConnect;
9446
+ this.heartbeatFirstAfterConnect = false;
9447
+ try {
9448
+ this.onHeartbeat({ firstAfterConnect });
9449
+ } catch (err) {
9450
+ log.trace("relay", "heartbeat rider threw (ignored)", err);
9451
+ }
9452
+ }
9435
9453
  start() {
9436
9454
  this.cleanup();
9437
9455
  this._running = true;
9456
+ this.heartbeatFirstAfterConnect = true;
9438
9457
  this.agentsRegistered = false;
9439
9458
  log.info("relay", `start pluginId=${this.pluginId.slice(0, 8)} agent=${this.agentMeta.id}`);
9440
9459
  this.cachedBranch = detectCurrentBranch();
9441
9460
  this.sendHeartbeat(true);
9461
+ this.emitHeartbeatTick();
9442
9462
  this.heartbeatTimer = setInterval(() => {
9443
9463
  void this.refreshBranch();
9444
9464
  this.sendHeartbeat(true);
9465
+ this.emitHeartbeatTick();
9445
9466
  }, 2e4);
9446
9467
  this.agentsTimer = setInterval(() => {
9447
9468
  if (this._running && !this.agentsRegistered) this.reportAgents();
@@ -9525,6 +9546,7 @@ var CommandRelayService = class _CommandRelayService {
9525
9546
  }
9526
9547
  log.info("relay", "sse connected");
9527
9548
  this.sseFailures = 0;
9549
+ this.heartbeatFirstAfterConnect = true;
9528
9550
  this.armSseWatchdog();
9529
9551
  let buffer = "";
9530
9552
  res.setEncoding("utf8");
@@ -9642,6 +9664,7 @@ var CommandRelayService = class _CommandRelayService {
9642
9664
  // ─── Polling fallback ────────────────────────────────────────────
9643
9665
  startPollingFallback() {
9644
9666
  if (this.pollTimer) return;
9667
+ this.heartbeatFirstAfterConnect = true;
9645
9668
  void this.pollLoop();
9646
9669
  }
9647
9670
  async pollLoop() {
@@ -9738,7 +9761,7 @@ var CommandRelayService = class _CommandRelayService {
9738
9761
  // fresh + clear the "CLI update available" banner after a self-update
9739
9762
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
9740
9763
  // pair/reconnect). Older backends ignore the extra field.
9741
- ..."2.65.7" ? { ideVersion: "2.65.7" } : {}
9764
+ ..."2.65.8" ? { ideVersion: "2.65.8" } : {}
9742
9765
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
9743
9766
  }
9744
9767
  /**
@@ -21667,7 +21690,7 @@ async function autoUpgradeBeforeCriticalCommand() {
21667
21690
  if (process.env.NODE_ENV === "test") return;
21668
21691
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
21669
21692
  if (process.env.CI) return;
21670
- const current2 = true ? "2.65.7" : null;
21693
+ const current2 = true ? "2.65.8" : null;
21671
21694
  if (!current2) return;
21672
21695
  const cache = readCache();
21673
21696
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -21684,7 +21707,7 @@ function checkForUpdates() {
21684
21707
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
21685
21708
  if (process.env.CI) return;
21686
21709
  if (!process.stdout.isTTY) return;
21687
- const current2 = true ? "2.65.7" : null;
21710
+ const current2 = true ? "2.65.8" : null;
21688
21711
  if (!current2) return;
21689
21712
  const cache = readCache();
21690
21713
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -21704,7 +21727,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
21704
21727
  var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
21705
21728
  var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
21706
21729
  function currentCliVersion() {
21707
- return true ? "2.65.7" : null;
21730
+ return true ? "2.65.8" : null;
21708
21731
  }
21709
21732
  function runCmd(cmd, args2, timeoutMs) {
21710
21733
  return new Promise((resolve9) => {
@@ -41009,6 +41032,17 @@ var BatonController = class {
41009
41032
  get conversationId() {
41010
41033
  return this._conversationId;
41011
41034
  }
41035
+ /** The exact triple the last `publishState` emitted — the CLI's current view
41036
+ * of who holds the baton. Read by the heartbeat re-affirmation rider
41037
+ * ({@link makeBatonHeartbeatReaffirm}), which re-posts it periodically so the
41038
+ * backend's 1 h Redis snapshot never expires under a live session. */
41039
+ currentState() {
41040
+ return {
41041
+ state: this._state,
41042
+ driver: this._active,
41043
+ conversationId: this._conversationId
41044
+ };
41045
+ }
41012
41046
  async begin() {
41013
41047
  this._conversationId = await this.deps.local.start(void 0);
41014
41048
  this._active = "local_tui";
@@ -41484,6 +41518,21 @@ function makeSerializedBatonPoster(post2) {
41484
41518
  void chain;
41485
41519
  };
41486
41520
  }
41521
+ var BATON_REAFFIRM_INTERVAL_MS = 5 * 6e4;
41522
+ function makeBatonHeartbeatReaffirm(deps) {
41523
+ const now = deps.now ?? Date.now;
41524
+ const intervalMs = deps.intervalMs ?? BATON_REAFFIRM_INTERVAL_MS;
41525
+ let lastAffirmedAt = null;
41526
+ return ({ firstAfterConnect }) => {
41527
+ const current2 = deps.currentState();
41528
+ if (!current2) return;
41529
+ if (current2.state === "SWITCHING") return;
41530
+ const at3 = now();
41531
+ if (!firstAfterConnect && lastAffirmedAt !== null && at3 - lastAffirmedAt < intervalMs) return;
41532
+ lastAffirmedAt = at3;
41533
+ deps.publish(current2.state, current2.driver, current2.conversationId);
41534
+ };
41535
+ }
41487
41536
  async function runBatonSession(opts) {
41488
41537
  const publisher = new AcpPublisher({
41489
41538
  sessionId: opts.sessionId,
@@ -41595,18 +41644,21 @@ async function runBatonSession(opts) {
41595
41644
  mirror.start();
41596
41645
  };
41597
41646
  const postBatonState = makeSerializedBatonPoster(postBatonEvent);
41647
+ const publishBatonState = (state, driver, conversationId) => {
41648
+ postBatonState({
41649
+ sessionId: opts.sessionId,
41650
+ pluginId: opts.pluginId,
41651
+ pluginAuthToken: opts.pluginAuthToken,
41652
+ state,
41653
+ driver,
41654
+ conversationId
41655
+ });
41656
+ };
41598
41657
  const controller = new BatonController({
41599
41658
  local: nativeDriver,
41600
41659
  mobile: mobileDriver,
41601
41660
  publishState: (state, driver, conversationId) => {
41602
- postBatonState({
41603
- sessionId: opts.sessionId,
41604
- pluginId: opts.pluginId,
41605
- pluginAuthToken: opts.pluginAuthToken,
41606
- state,
41607
- driver,
41608
- conversationId
41609
- });
41661
+ publishBatonState(state, driver, conversationId);
41610
41662
  if (state === "LOCAL_DRIVE" && conversationId) {
41611
41663
  startMirror(conversationId, firstLocalDrive);
41612
41664
  firstLocalDrive = false;
@@ -41616,6 +41668,7 @@ async function runBatonSession(opts) {
41616
41668
  }
41617
41669
  });
41618
41670
  const dispatchActive = (cmd) => controller.activeSessionDriver.dispatch(cmd);
41671
+ let torn = false;
41619
41672
  relay = new CommandRelayService(
41620
41673
  opts.pluginId,
41621
41674
  makeOnCommand({
@@ -41623,9 +41676,15 @@ async function runBatonSession(opts) {
41623
41676
  dispatchActive,
41624
41677
  ack: (id, status2, result) => relay.sendResult(id, status2, result)
41625
41678
  }),
41626
- runtime.meta
41679
+ runtime.meta,
41680
+ void 0,
41681
+ void 0,
41682
+ makeBatonHeartbeatReaffirm({
41683
+ // Nothing to affirm once the session is torn down.
41684
+ currentState: () => torn ? null : controller.currentState(),
41685
+ publish: publishBatonState
41686
+ })
41627
41687
  );
41628
- let torn = false;
41629
41688
  function teardown() {
41630
41689
  if (torn) return;
41631
41690
  torn = true;
@@ -44678,7 +44737,7 @@ function checkChokidar() {
44678
44737
  }
44679
44738
  async function doctor(args2 = []) {
44680
44739
  const json = args2.includes("--json");
44681
- const cliVersion = true ? "2.65.7" : "0.0.0-dev";
44740
+ const cliVersion = true ? "2.65.8" : "0.0.0-dev";
44682
44741
  const apiBase2 = resolveApiBaseUrl();
44683
44742
  const diagnosticId = (0, import_node_crypto13.randomUUID)();
44684
44743
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -45069,7 +45128,7 @@ async function mcpRun(args2) {
45069
45128
  // src/commands/version.ts
45070
45129
  var import_picocolors15 = __toESM(require("picocolors"));
45071
45130
  function version2() {
45072
- const v = true ? "2.65.7" : "unknown";
45131
+ const v = true ? "2.65.8" : "unknown";
45073
45132
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
45074
45133
  }
45075
45134
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.65.7",
3
+ "version": "2.65.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",