codeam-cli 2.65.7 → 2.65.9

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.9" : "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.9",
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.9" ? { ideVersion: "2.65.9" } : {}
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.9" : 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.9" : 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.9" : null;
21708
21731
  }
21709
21732
  function runCmd(cmd, args2, timeoutMs) {
21710
21733
  return new Promise((resolve9) => {
@@ -36690,6 +36713,11 @@ ${tail}` : detail].join(
36690
36713
  "\n"
36691
36714
  );
36692
36715
  }
36716
+ function startupCredentialInvalidReason(agent, detail, recentStderr) {
36717
+ const haystack2 = `${detail}
36718
+ ${recentStderr}`;
36719
+ return agentHooks(agent)?.classifyStartupFailure?.(haystack2) === "ineligible_tier" ? "ineligible_tier" : null;
36720
+ }
36693
36721
  function budgetBubbleMessage(agent, period) {
36694
36722
  return `\u{1F4B8} **Headroom budget reached for the ${period} period.**
36695
36723
 
@@ -36729,7 +36757,11 @@ function adapterExitMessage(opts) {
36729
36757
  // src/agents/acp/backend-reports.ts
36730
36758
  async function reportCredentialInvalid(opts, fetchImpl = fetch) {
36731
36759
  const url2 = `${resolveApiBaseUrl()}/api/plugin/agents/${encodeURIComponent(opts.agent)}/credential-invalid`;
36732
- const body = JSON.stringify({ sessionId: opts.sessionId, pluginId: opts.pluginId });
36760
+ const body = JSON.stringify({
36761
+ sessionId: opts.sessionId,
36762
+ pluginId: opts.pluginId,
36763
+ ...opts.reason ? { reason: opts.reason } : {}
36764
+ });
36733
36765
  try {
36734
36766
  const makeHeaders = (token) => ({
36735
36767
  "Content-Type": "application/json",
@@ -39308,8 +39340,21 @@ var AcpHistory = class {
39308
39340
  ]);
39309
39341
  }
39310
39342
  };
39343
+ var _startupCredentialReportGuard = { reported: false };
39311
39344
  async function surfaceStartupFailure(opts) {
39312
39345
  const msg = startupFailureMessage(opts.agent, opts.detail, opts.recentStderr);
39346
+ const reason = startupCredentialInvalidReason(opts.agent, opts.detail, opts.recentStderr);
39347
+ if (reason !== null && !_startupCredentialReportGuard.reported && opts.sessionId && opts.pluginAuthToken) {
39348
+ _startupCredentialReportGuard.reported = true;
39349
+ void reportCredentialInvalid({
39350
+ agent: opts.agent,
39351
+ sessionId: opts.sessionId,
39352
+ pluginId: opts.pluginId,
39353
+ pluginAuthToken: opts.pluginAuthToken,
39354
+ pollSecret: opts.pollSecret,
39355
+ reason
39356
+ });
39357
+ }
39313
39358
  const publish = async () => {
39314
39359
  try {
39315
39360
  await opts.publisher.publishOutput({ type: "new_turn", done: false });
@@ -39514,7 +39559,10 @@ async function runAcpSession(opts) {
39514
39559
  pluginId: opts.pluginId,
39515
39560
  detail,
39516
39561
  recentStderr: recentStderr.join("\n"),
39517
- publisher
39562
+ publisher,
39563
+ sessionId: opts.sessionId,
39564
+ pluginAuthToken: opts.pluginAuthToken,
39565
+ pollSecret: opts.pollSecret
39518
39566
  });
39519
39567
  return;
39520
39568
  }
@@ -41009,6 +41057,17 @@ var BatonController = class {
41009
41057
  get conversationId() {
41010
41058
  return this._conversationId;
41011
41059
  }
41060
+ /** The exact triple the last `publishState` emitted — the CLI's current view
41061
+ * of who holds the baton. Read by the heartbeat re-affirmation rider
41062
+ * ({@link makeBatonHeartbeatReaffirm}), which re-posts it periodically so the
41063
+ * backend's 1 h Redis snapshot never expires under a live session. */
41064
+ currentState() {
41065
+ return {
41066
+ state: this._state,
41067
+ driver: this._active,
41068
+ conversationId: this._conversationId
41069
+ };
41070
+ }
41012
41071
  async begin() {
41013
41072
  this._conversationId = await this.deps.local.start(void 0);
41014
41073
  this._active = "local_tui";
@@ -41484,6 +41543,21 @@ function makeSerializedBatonPoster(post2) {
41484
41543
  void chain;
41485
41544
  };
41486
41545
  }
41546
+ var BATON_REAFFIRM_INTERVAL_MS = 5 * 6e4;
41547
+ function makeBatonHeartbeatReaffirm(deps) {
41548
+ const now = deps.now ?? Date.now;
41549
+ const intervalMs = deps.intervalMs ?? BATON_REAFFIRM_INTERVAL_MS;
41550
+ let lastAffirmedAt = null;
41551
+ return ({ firstAfterConnect }) => {
41552
+ const current2 = deps.currentState();
41553
+ if (!current2) return;
41554
+ if (current2.state === "SWITCHING") return;
41555
+ const at3 = now();
41556
+ if (!firstAfterConnect && lastAffirmedAt !== null && at3 - lastAffirmedAt < intervalMs) return;
41557
+ lastAffirmedAt = at3;
41558
+ deps.publish(current2.state, current2.driver, current2.conversationId);
41559
+ };
41560
+ }
41487
41561
  async function runBatonSession(opts) {
41488
41562
  const publisher = new AcpPublisher({
41489
41563
  sessionId: opts.sessionId,
@@ -41595,18 +41669,21 @@ async function runBatonSession(opts) {
41595
41669
  mirror.start();
41596
41670
  };
41597
41671
  const postBatonState = makeSerializedBatonPoster(postBatonEvent);
41672
+ const publishBatonState = (state, driver, conversationId) => {
41673
+ postBatonState({
41674
+ sessionId: opts.sessionId,
41675
+ pluginId: opts.pluginId,
41676
+ pluginAuthToken: opts.pluginAuthToken,
41677
+ state,
41678
+ driver,
41679
+ conversationId
41680
+ });
41681
+ };
41598
41682
  const controller = new BatonController({
41599
41683
  local: nativeDriver,
41600
41684
  mobile: mobileDriver,
41601
41685
  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
- });
41686
+ publishBatonState(state, driver, conversationId);
41610
41687
  if (state === "LOCAL_DRIVE" && conversationId) {
41611
41688
  startMirror(conversationId, firstLocalDrive);
41612
41689
  firstLocalDrive = false;
@@ -41616,6 +41693,7 @@ async function runBatonSession(opts) {
41616
41693
  }
41617
41694
  });
41618
41695
  const dispatchActive = (cmd) => controller.activeSessionDriver.dispatch(cmd);
41696
+ let torn = false;
41619
41697
  relay = new CommandRelayService(
41620
41698
  opts.pluginId,
41621
41699
  makeOnCommand({
@@ -41623,9 +41701,15 @@ async function runBatonSession(opts) {
41623
41701
  dispatchActive,
41624
41702
  ack: (id, status2, result) => relay.sendResult(id, status2, result)
41625
41703
  }),
41626
- runtime.meta
41704
+ runtime.meta,
41705
+ void 0,
41706
+ void 0,
41707
+ makeBatonHeartbeatReaffirm({
41708
+ // Nothing to affirm once the session is torn down.
41709
+ currentState: () => torn ? null : controller.currentState(),
41710
+ publish: publishBatonState
41711
+ })
41627
41712
  );
41628
- let torn = false;
41629
41713
  function teardown() {
41630
41714
  if (torn) return;
41631
41715
  torn = true;
@@ -41922,7 +42006,10 @@ async function start(requestedAgent, presetSession) {
41922
42006
  pluginId,
41923
42007
  pluginAuthToken: session.pluginAuthToken,
41924
42008
  refreshAuthToken: () => fetchCurrentPluginAuthToken(session.id, pluginId, session.pollSecret)
41925
- })
42009
+ }),
42010
+ sessionId: session.id,
42011
+ pluginAuthToken: session.pluginAuthToken,
42012
+ pollSecret: session.pollSecret
41926
42013
  });
41927
42014
  return;
41928
42015
  }
@@ -44678,7 +44765,7 @@ function checkChokidar() {
44678
44765
  }
44679
44766
  async function doctor(args2 = []) {
44680
44767
  const json = args2.includes("--json");
44681
- const cliVersion = true ? "2.65.7" : "0.0.0-dev";
44768
+ const cliVersion = true ? "2.65.9" : "0.0.0-dev";
44682
44769
  const apiBase2 = resolveApiBaseUrl();
44683
44770
  const diagnosticId = (0, import_node_crypto13.randomUUID)();
44684
44771
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -45069,7 +45156,7 @@ async function mcpRun(args2) {
45069
45156
  // src/commands/version.ts
45070
45157
  var import_picocolors15 = __toESM(require("picocolors"));
45071
45158
  function version2() {
45072
- const v = true ? "2.65.7" : "unknown";
45159
+ const v = true ? "2.65.9" : "unknown";
45073
45160
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
45074
45161
  }
45075
45162
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.65.7",
3
+ "version": "2.65.9",
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",