codeam-cli 2.60.6 → 2.60.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,32 @@ 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.60.7] — 2026-07-08
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** Retry ACP start on the adapter's transient in-binary spawn ETXTBSY
12
+
13
+ ## [2.60.6] — 2026-07-08
14
+
15
+ ### Fixed
16
+
17
+ - **cli:** Serialize ACP prompts so a 2nd in-flight turn can't clobber the watchdog
18
+
19
+ ## [2.60.5] — 2026-07-08
20
+
21
+ ### Added
22
+
23
+ - **cli:** Baton support for cursor + gate to agents with a resumable transcript
24
+
25
+ ### Documentation
26
+
27
+ - **cli:** Document the Session Baton (native TUI <-> mobile ACP) architecture + hand-off invariants
28
+
29
+ ### Fixed
30
+
31
+ - **cli:** Serialize baton state POSTs so handback never sticks on "Switching…"
32
+
7
33
  ## [2.60.4] — 2026-07-08
8
34
 
9
35
  ### Fixed
package/dist/index.js CHANGED
@@ -5653,7 +5653,7 @@ function readAnonId() {
5653
5653
  }
5654
5654
  function superProperties() {
5655
5655
  return {
5656
- cliVersion: true ? "2.60.6" : "0.0.0-dev",
5656
+ cliVersion: true ? "2.60.8" : "0.0.0-dev",
5657
5657
  nodeVersion: process.version,
5658
5658
  platform: process.platform,
5659
5659
  arch: process.arch,
@@ -5834,7 +5834,7 @@ var os4 = __toESM(require("os"));
5834
5834
  // package.json
5835
5835
  var package_default = {
5836
5836
  name: "codeam-cli",
5837
- version: "2.60.6",
5837
+ version: "2.60.8",
5838
5838
  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.",
5839
5839
  type: "commonjs",
5840
5840
  main: "dist/index.js",
@@ -6905,7 +6905,7 @@ var CommandRelayService = class {
6905
6905
  // fresh + clear the "CLI update available" banner after a self-update
6906
6906
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
6907
6907
  // pair/reconnect). Older backends ignore the extra field.
6908
- ..."2.60.6" ? { ideVersion: "2.60.6" } : {}
6908
+ ..."2.60.8" ? { ideVersion: "2.60.8" } : {}
6909
6909
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
6910
6910
  }
6911
6911
  /**
@@ -12703,6 +12703,26 @@ function mapRole(codexRole) {
12703
12703
  if (codexRole === "assistant") return "agent";
12704
12704
  return "system";
12705
12705
  }
12706
+ function messageFromPayload(payload) {
12707
+ if (!payload || typeof payload !== "object") return null;
12708
+ const p2 = payload;
12709
+ if (p2.type === "message" && Array.isArray(p2.content)) {
12710
+ return {
12711
+ role: typeof p2.role === "string" ? p2.role : void 0,
12712
+ content: p2.content
12713
+ };
12714
+ }
12715
+ if (p2.Message && typeof p2.Message === "object") return p2.Message;
12716
+ return null;
12717
+ }
12718
+ function isSyntheticCodexTurn(role, text) {
12719
+ if (role === "developer" || role === "system") return true;
12720
+ const trimmed = text.trim();
12721
+ if (trimmed.startsWith("<environment_context>")) return true;
12722
+ if (trimmed.startsWith("<turn_aborted>")) return true;
12723
+ if (/^(<<ccr:[^>]*>>\s*)+$/.test(trimmed)) return true;
12724
+ return false;
12725
+ }
12706
12726
  function parseHistoryFile2(filePath) {
12707
12727
  const raw = import_node_fs3.default.readFileSync(filePath, "utf8");
12708
12728
  const lines = raw.split("\n").filter(Boolean);
@@ -12734,11 +12754,11 @@ function parseHistoryFile2(filePath) {
12734
12754
  const rec = parseLine(line);
12735
12755
  if (!rec) continue;
12736
12756
  if (rec.type !== "response_item") continue;
12737
- const payload = rec.payload;
12738
- const msg = payload?.Message;
12757
+ const msg = messageFromPayload(rec.payload);
12739
12758
  if (!msg) continue;
12740
12759
  const text = extractMessageText(msg.content);
12741
12760
  if (!text) continue;
12761
+ if (isSyntheticCodexTurn(msg.role, text)) continue;
12742
12762
  out2.push({
12743
12763
  id: `rollout:${idx}`,
12744
12764
  role: mapRole(msg.role),
@@ -12801,11 +12821,10 @@ function listResumableSessions2(cwd, homeOverride) {
12801
12821
  continue;
12802
12822
  }
12803
12823
  if (!summary && rec.type === "response_item") {
12804
- const payload = rec.payload;
12805
- const msg = payload?.Message;
12824
+ const msg = messageFromPayload(rec.payload);
12806
12825
  if (msg && msg.role === "user") {
12807
12826
  const text = extractMessageText(msg.content).trim();
12808
- if (text) summary = text.slice(0, 120);
12827
+ if (text && !isSyntheticCodexTurn(msg.role, text)) summary = text.slice(0, 120);
12809
12828
  }
12810
12829
  }
12811
12830
  if (metaCwd !== void 0 && summary) break;
@@ -15986,7 +16005,7 @@ async function autoUpgradeBeforeCriticalCommand() {
15986
16005
  if (process.env.NODE_ENV === "test") return;
15987
16006
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
15988
16007
  if (process.env.CI) return;
15989
- const current = true ? "2.60.6" : null;
16008
+ const current = true ? "2.60.8" : null;
15990
16009
  if (!current) return;
15991
16010
  const cache = readCache();
15992
16011
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -16003,7 +16022,7 @@ function checkForUpdates() {
16003
16022
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
16004
16023
  if (process.env.CI) return;
16005
16024
  if (!process.stdout.isTTY) return;
16006
- const current = true ? "2.60.6" : null;
16025
+ const current = true ? "2.60.8" : null;
16007
16026
  if (!current) return;
16008
16027
  const cache = readCache();
16009
16028
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -16023,7 +16042,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
16023
16042
  var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
16024
16043
  var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
16025
16044
  function currentCliVersion() {
16026
- return true ? "2.60.6" : null;
16045
+ return true ? "2.60.8" : null;
16027
16046
  }
16028
16047
  function runCmd(cmd, args2, timeoutMs) {
16029
16048
  return new Promise((resolve7) => {
@@ -24850,6 +24869,11 @@ function createIdleTimeout(idleMs, makeError) {
24850
24869
  }
24851
24870
 
24852
24871
  // src/agents/acp/client.ts
24872
+ var TRANSIENT_ADAPTER_SPAWN_RE = /ETXTBSY|ENOENT/;
24873
+ var MAX_START_ATTEMPTS = 5;
24874
+ var _acpStartSeam = {
24875
+ sleep: (ms) => new Promise((r) => setTimeout(r, ms))
24876
+ };
24853
24877
  var PROTOCOL_VERSION2 = 1;
24854
24878
  var FATAL_STARTUP_RE = /IneligibleTierError|UNSUPPORTED_CLIENT|no longer supported for Gemini Code Assist|not eligible for Gemini Code Assist|Error authenticating|ProjectIdRequiredError/i;
24855
24879
  var NEWSESSION_TIMEOUT_MS = 12e4;
@@ -24895,15 +24919,57 @@ var AcpClient = class {
24895
24919
  * so without this the session hangs forever ("loading… / offline"). */
24896
24920
  startupFailureReject = null;
24897
24921
  /**
24898
- * Spawn the adapter + perform the initial handshake (initialize
24899
- * → newSession). Returns the ACP-assigned sessionId so the caller
24900
- * can route subsequent prompts, plus optional model + tier that
24901
- * some adapters (codex-acp today) surface on the newSession
24902
- * response used by the runner to enrich the welcome card
24903
- * subtitle without an extra round-trip.
24922
+ * Spawn the adapter + handshake, retrying a TRANSIENT in-adapter spawn race.
24923
+ *
24924
+ * The adapter itself execs the real agent binary (claude-agent-acp spawns the
24925
+ * ~250MB SDK-bundled `claude` during newSession). On a fresh/waking codespace
24926
+ * that binary is momentarily open-for-write the adapter rejects newSession
24927
+ * with `-32603 spawn ETXTBSY` (or ENOENT before it lands). Both clear within
24928
+ * ms, so we respawn the adapter + re-handshake up to {@link MAX_START_ATTEMPTS}
24929
+ * times. `startOnce` fully cleans up on failure (see cleanupAfterFailedStart),
24930
+ * so each retry starts from a clean slate. Non-transient failures throw on the
24931
+ * first attempt — no masking of real auth/outage errors.
24904
24932
  */
24905
24933
  async start() {
24934
+ let lastErr;
24935
+ for (let attempt = 1; attempt <= MAX_START_ATTEMPTS; attempt += 1) {
24936
+ try {
24937
+ return await this.startOnce();
24938
+ } catch (err) {
24939
+ lastErr = err;
24940
+ if (attempt < MAX_START_ATTEMPTS && this.isTransientAdapterSpawn(err)) {
24941
+ log.info(
24942
+ "acpClient",
24943
+ `adapter hit a transient spawn race (binary busy) \u2014 retrying start ${attempt + 1}/${MAX_START_ATTEMPTS}`
24944
+ );
24945
+ await _acpStartSeam.sleep(400 * attempt);
24946
+ continue;
24947
+ }
24948
+ throw err;
24949
+ }
24950
+ }
24951
+ throw lastErr;
24952
+ }
24953
+ /** True when a failed {@link startOnce} was caused by the adapter's OWN
24954
+ * transient spawn of the agent binary (ETXTBSY/ENOENT). Checks the thrown
24955
+ * error's message/data AND the adapter stderr ring — the adapter logs the
24956
+ * real cause to stderr while returning a generic `-32603 Internal error`. */
24957
+ isTransientAdapterSpawn(err) {
24958
+ const parts = [...this.recentStderr];
24959
+ if (err instanceof Error) parts.push(err.message);
24960
+ if (err && typeof err === "object") {
24961
+ const e = err;
24962
+ try {
24963
+ parts.push(JSON.stringify(e.data));
24964
+ } catch {
24965
+ }
24966
+ if (typeof e.details === "string") parts.push(e.details);
24967
+ }
24968
+ return TRANSIENT_ADAPTER_SPAWN_RE.test(parts.join(" "));
24969
+ }
24970
+ async startOnce() {
24906
24971
  if (this.child) throw new Error("AcpClient already started");
24972
+ this.recentStderr.length = 0;
24907
24973
  const { adapter, cwd } = this.opts;
24908
24974
  const augmentedPath = expandPathForAgentBinaries(process.env.PATH ?? "");
24909
24975
  log.info(
@@ -32755,7 +32821,7 @@ function checkChokidar() {
32755
32821
  }
32756
32822
  async function doctor(args2 = []) {
32757
32823
  const json = args2.includes("--json");
32758
- const cliVersion = true ? "2.60.6" : "0.0.0-dev";
32824
+ const cliVersion = true ? "2.60.8" : "0.0.0-dev";
32759
32825
  const apiBase2 = resolveApiBaseUrl();
32760
32826
  const diagnosticId = (0, import_node_crypto9.randomUUID)();
32761
32827
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -32954,7 +33020,7 @@ async function completion(args2) {
32954
33020
  // src/commands/version.ts
32955
33021
  var import_picocolors15 = __toESM(require("picocolors"));
32956
33022
  function version2() {
32957
- const v = true ? "2.60.6" : "unknown";
33023
+ const v = true ? "2.60.8" : "unknown";
32958
33024
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
32959
33025
  }
32960
33026
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.60.6",
3
+ "version": "2.60.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",