codeam-cli 2.60.50 → 2.60.51

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,12 @@ 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.50] — 2026-07-11
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** Guard kimi ACP login-state slot + real `kimi acp` regression test
12
+
7
13
  ## [2.60.49] — 2026-07-11
8
14
 
9
15
  ### Fixed
package/dist/index.js CHANGED
@@ -5733,7 +5733,7 @@ function readAnonId() {
5733
5733
  }
5734
5734
  function superProperties() {
5735
5735
  return {
5736
- cliVersion: true ? "2.60.50" : "0.0.0-dev",
5736
+ cliVersion: true ? "2.60.51" : "0.0.0-dev",
5737
5737
  nodeVersion: process.version,
5738
5738
  platform: process.platform,
5739
5739
  arch: process.arch,
@@ -5914,7 +5914,7 @@ var os4 = __toESM(require("os"));
5914
5914
  // package.json
5915
5915
  var package_default = {
5916
5916
  name: "codeam-cli",
5917
- version: "2.60.50",
5917
+ version: "2.60.51",
5918
5918
  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.",
5919
5919
  type: "commonjs",
5920
5920
  main: "dist/index.js",
@@ -7023,7 +7023,7 @@ var CommandRelayService = class {
7023
7023
  // fresh + clear the "CLI update available" banner after a self-update
7024
7024
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
7025
7025
  // pair/reconnect). Older backends ignore the extra field.
7026
- ..."2.60.50" ? { ideVersion: "2.60.50" } : {}
7026
+ ..."2.60.51" ? { ideVersion: "2.60.51" } : {}
7027
7027
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
7028
7028
  }
7029
7029
  /**
@@ -17592,7 +17592,7 @@ async function autoUpgradeBeforeCriticalCommand() {
17592
17592
  if (process.env.NODE_ENV === "test") return;
17593
17593
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
17594
17594
  if (process.env.CI) return;
17595
- const current = true ? "2.60.50" : null;
17595
+ const current = true ? "2.60.51" : null;
17596
17596
  if (!current) return;
17597
17597
  const cache = readCache();
17598
17598
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -17609,7 +17609,7 @@ function checkForUpdates() {
17609
17609
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
17610
17610
  if (process.env.CI) return;
17611
17611
  if (!process.stdout.isTTY) return;
17612
- const current = true ? "2.60.50" : null;
17612
+ const current = true ? "2.60.51" : null;
17613
17613
  if (!current) return;
17614
17614
  const cache = readCache();
17615
17615
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -17629,7 +17629,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
17629
17629
  var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
17630
17630
  var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
17631
17631
  function currentCliVersion() {
17632
- return true ? "2.60.50" : null;
17632
+ return true ? "2.60.51" : null;
17633
17633
  }
17634
17634
  function runCmd(cmd, args2, timeoutMs) {
17635
17635
  return new Promise((resolve7) => {
@@ -26736,6 +26736,7 @@ function createIdleTimeout(idleMs, makeError) {
26736
26736
  var TRANSIENT_ADAPTER_SPAWN_RE = /ETXTBSY|ENOENT/;
26737
26737
  var MAX_START_ATTEMPTS = 5;
26738
26738
  var TRANSPORT_DOWN_DURING_START_RE = /connection (is )?closed|stream (is )?closed|ECONNRESET|EPIPE|write after end|premature close/i;
26739
+ var SESSION_CLOSED_RE = /session is closed/i;
26739
26740
  var AdapterStartCrashError = class extends Error {
26740
26741
  adapterExitedDuringStart = true;
26741
26742
  constructor(message) {
@@ -26763,6 +26764,12 @@ var AcpClient = class {
26763
26764
  connection = null;
26764
26765
  stopping = false;
26765
26766
  sessionId = null;
26767
+ /** Whether the agent advertised `loadSession` on `initialize`. Drives the
26768
+ * post-turn session-closed recovery: an agent that supports resume (kimi,
26769
+ * claude, codex, gemini) is re-established with `session/load` — preserving
26770
+ * the conversation — whereas one without it falls back to a fresh
26771
+ * `session/new`. Set once during {@link startOnce}. */
26772
+ supportsLoadSession = false;
26766
26773
  /** Idle watchdog for the in-flight prompt. The `Client` handlers
26767
26774
  * (`sessionUpdate` / `requestPermission`) reach for this to keep
26768
26775
  * the turn alive while the adapter is demonstrably working. Null
@@ -26946,6 +26953,7 @@ var AcpClient = class {
26946
26953
  "acpClient",
26947
26954
  `initialize \u2190 ok protocolVersion=${initialize.protocolVersion} agentCaps=${JSON.stringify(initialize.agentCapabilities ?? {}).slice(0, 200)}`
26948
26955
  );
26956
+ this.supportsLoadSession = initialize.agentCapabilities?.loadSession === true;
26949
26957
  log.info("acpClient", "newSession \u2192 sending");
26950
26958
  let startupTimer;
26951
26959
  const startupFailure = new Promise((_2, reject) => {
@@ -27033,6 +27041,29 @@ var AcpClient = class {
27033
27041
  throw new Error("AcpClient.prompt called before start()");
27034
27042
  }
27035
27043
  const blocks = typeof input === "string" ? [{ type: "text", text: input }] : input;
27044
+ try {
27045
+ return await this.sendPromptOnce(blocks);
27046
+ } catch (err) {
27047
+ if (this.isSessionClosedError(err)) {
27048
+ log.warn(
27049
+ "acpClient",
27050
+ "session/prompt rejected 'Session is closed' \u2014 re-establishing session and retrying once"
27051
+ );
27052
+ await this.reestablishSession();
27053
+ return await this.sendPromptOnce(blocks);
27054
+ }
27055
+ throw err;
27056
+ }
27057
+ }
27058
+ /**
27059
+ * Send ONE `session/prompt` round-trip with the idle watchdog. Extracted from
27060
+ * {@link runPrompt} so the closed-session recovery can re-invoke it for the
27061
+ * single retry without duplicating the watchdog / tool-ledger setup.
27062
+ */
27063
+ async sendPromptOnce(blocks) {
27064
+ if (!this.connection || !this.sessionId) {
27065
+ throw new Error("AcpClient.sendPromptOnce called before start()");
27066
+ }
27036
27067
  const textLen = blocks.reduce(
27037
27068
  (n, b) => b.type === "text" ? n + b.text.length : n,
27038
27069
  0
@@ -27078,6 +27109,57 @@ var AcpClient = class {
27078
27109
  this.promptIdle = null;
27079
27110
  }
27080
27111
  }
27112
+ /**
27113
+ * True when a `session/prompt` rejection is the "the agent closed its ACP
27114
+ * session between turns" fact — the kimi post-turn/idle close. Keyed on the
27115
+ * `session is closed` phrase (in `message` / `details` / serialised `data`),
27116
+ * NOT on the bare `-32603` code: that generic "Internal error" code is also
27117
+ * used for the adapter's own transient spawn races, so matching it alone would
27118
+ * misfire. See {@link SESSION_CLOSED_RE}.
27119
+ */
27120
+ isSessionClosedError(err) {
27121
+ if (!err || typeof err !== "object") return false;
27122
+ const e = err;
27123
+ const parts = [];
27124
+ if (typeof e.message === "string") parts.push(e.message);
27125
+ if (typeof e.details === "string") parts.push(e.details);
27126
+ if (e.data !== void 0) {
27127
+ try {
27128
+ parts.push(JSON.stringify(e.data));
27129
+ } catch {
27130
+ }
27131
+ }
27132
+ return SESSION_CLOSED_RE.test(parts.join(" "));
27133
+ }
27134
+ /**
27135
+ * Re-establish a session the agent closed out from under us (kimi post-turn),
27136
+ * on the SAME still-running adapter process, so the pending prompt can be
27137
+ * retried. Prefers `session/load` (resume) when the agent advertised
27138
+ * `loadSession` — proven live to re-open kimi's closed session with the
27139
+ * conversation context intact, and kimi's `session/load` emits NO history
27140
+ * replay as `session/update`, so no streaming pollution (unlike the baton's
27141
+ * Claude load, which is why this needs no `beginLoadReplay` bracket). Calls
27142
+ * `connection.loadSession` DIRECTLY — the public {@link loadSession} skips a
27143
+ * same-id load (a Claude relaunch-wedge guard), which is exactly the load we
27144
+ * MUST perform here (the closed id IS the active id). Falls back to a fresh
27145
+ * `session/new` (context lost) only when the agent can't resume.
27146
+ */
27147
+ async reestablishSession() {
27148
+ if (!this.connection) throw new Error("AcpClient.reestablishSession: no connection");
27149
+ const cwd = this.opts.cwd;
27150
+ const mcpServers = this.opts.mcpServers ?? [];
27151
+ const closedSid = this.sessionId;
27152
+ if (this.supportsLoadSession && closedSid) {
27153
+ log.info("acpClient", `reestablish \u2192 loadSession(resume) sid=${closedSid.slice(0, 8)}`);
27154
+ await this.connection.loadSession({ sessionId: closedSid, cwd, mcpServers });
27155
+ log.info("acpClient", "reestablish \u2190 loadSession ok");
27156
+ return;
27157
+ }
27158
+ log.info("acpClient", "reestablish \u2192 newSession (agent has no loadSession capability)");
27159
+ const ns = await this.connection.newSession({ cwd, mcpServers });
27160
+ this.sessionId = ns.sessionId;
27161
+ log.info("acpClient", `reestablish \u2190 newSession ok sid=${ns.sessionId.slice(0, 8)}`);
27162
+ }
27081
27163
  /**
27082
27164
  * Load a previously persisted session by id. Mobile's
27083
27165
  * `resume_session` command flows through here. Requires the
@@ -34846,7 +34928,7 @@ function checkChokidar() {
34846
34928
  }
34847
34929
  async function doctor(args2 = []) {
34848
34930
  const json = args2.includes("--json");
34849
- const cliVersion = true ? "2.60.50" : "0.0.0-dev";
34931
+ const cliVersion = true ? "2.60.51" : "0.0.0-dev";
34850
34932
  const apiBase2 = resolveApiBaseUrl();
34851
34933
  const diagnosticId = (0, import_node_crypto12.randomUUID)();
34852
34934
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -35323,7 +35405,7 @@ async function mcpRun(args2) {
35323
35405
  // src/commands/version.ts
35324
35406
  var import_picocolors15 = __toESM(require("picocolors"));
35325
35407
  function version2() {
35326
- const v = true ? "2.60.50" : "unknown";
35408
+ const v = true ? "2.60.51" : "unknown";
35327
35409
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
35328
35410
  }
35329
35411
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.60.50",
3
+ "version": "2.60.51",
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",