codeam-cli 2.60.20 → 2.60.22

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/index.js +150 -28
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,19 @@ 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.21] — 2026-07-09
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** Baton — discover Codex's self-minted session id (first-turn aware)
12
+
13
+ ## [2.60.20] — 2026-07-09
14
+
15
+ ### Fixed
16
+
17
+ - **cli:** Baton — pre-mint Cursor session id via `create-chat`
18
+ - **cli:** Baton Take Control for Cursor — bridge native↔ACP session stores
19
+
7
20
  ## [2.60.19] — 2026-07-09
8
21
 
9
22
  ### Fixed
package/dist/index.js CHANGED
@@ -5680,7 +5680,7 @@ function readAnonId() {
5680
5680
  }
5681
5681
  function superProperties() {
5682
5682
  return {
5683
- cliVersion: true ? "2.60.20" : "0.0.0-dev",
5683
+ cliVersion: true ? "2.60.22" : "0.0.0-dev",
5684
5684
  nodeVersion: process.version,
5685
5685
  platform: process.platform,
5686
5686
  arch: process.arch,
@@ -5861,7 +5861,7 @@ var os4 = __toESM(require("os"));
5861
5861
  // package.json
5862
5862
  var package_default = {
5863
5863
  name: "codeam-cli",
5864
- version: "2.60.20",
5864
+ version: "2.60.22",
5865
5865
  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.",
5866
5866
  type: "commonjs",
5867
5867
  main: "dist/index.js",
@@ -6932,7 +6932,7 @@ var CommandRelayService = class {
6932
6932
  // fresh + clear the "CLI update available" banner after a self-update
6933
6933
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
6934
6934
  // pair/reconnect). Older backends ignore the extra field.
6935
- ..."2.60.20" ? { ideVersion: "2.60.20" } : {}
6935
+ ..."2.60.22" ? { ideVersion: "2.60.22" } : {}
6936
6936
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
6937
6937
  }
6938
6938
  /**
@@ -12931,6 +12931,87 @@ function resolveHistoryFile2(cwd, sessionId, homeOverride) {
12931
12931
  }
12932
12932
  return null;
12933
12933
  }
12934
+ async function discoverSessionId(cwd, opts, homeOverride) {
12935
+ const home = homeOverride ?? import_node_os.default.homedir();
12936
+ const sessionsRoot = import_node_path2.default.join(home, ".codex", "sessions");
12937
+ const floor = opts.sinceMs - 2e3;
12938
+ const deadline = Date.now() + (opts.timeoutMs ?? 24e4);
12939
+ let resolvedCurrent;
12940
+ try {
12941
+ resolvedCurrent = import_node_fs3.default.realpathSync(cwd);
12942
+ } catch {
12943
+ resolvedCurrent = import_node_path2.default.resolve(cwd);
12944
+ }
12945
+ for (; ; ) {
12946
+ const id = newestRolloutIdSince(sessionsRoot, resolvedCurrent, floor);
12947
+ if (id) return id;
12948
+ if (Date.now() >= deadline) return null;
12949
+ await sleep2(500);
12950
+ }
12951
+ }
12952
+ function newestRolloutIdSince(sessionsRoot, resolvedCwd, floorMs) {
12953
+ if (!import_node_fs3.default.existsSync(sessionsRoot)) return null;
12954
+ const now = /* @__PURE__ */ new Date();
12955
+ let bestId = null;
12956
+ let bestMtime = -1;
12957
+ for (let dayOffset = 0; dayOffset < 2; dayOffset += 1) {
12958
+ const d3 = new Date(now.getTime() - dayOffset * 24 * 60 * 60 * 1e3);
12959
+ const yyyy = String(d3.getUTCFullYear());
12960
+ const mm = String(d3.getUTCMonth() + 1).padStart(2, "0");
12961
+ const dd = String(d3.getUTCDate()).padStart(2, "0");
12962
+ const dayDir = import_node_path2.default.join(sessionsRoot, yyyy, mm, dd);
12963
+ let dayFiles;
12964
+ try {
12965
+ dayFiles = import_node_fs3.default.readdirSync(dayDir, { withFileTypes: true });
12966
+ } catch {
12967
+ continue;
12968
+ }
12969
+ for (const entry of dayFiles) {
12970
+ if (!entry.isFile()) continue;
12971
+ if (!entry.name.startsWith("rollout-") || !entry.name.endsWith(".jsonl")) continue;
12972
+ const filePath = import_node_path2.default.join(dayDir, entry.name);
12973
+ let mtime;
12974
+ try {
12975
+ mtime = import_node_fs3.default.statSync(filePath).mtimeMs;
12976
+ } catch {
12977
+ continue;
12978
+ }
12979
+ if (mtime < floorMs || mtime <= bestMtime) continue;
12980
+ let metaCwd;
12981
+ let metaId;
12982
+ try {
12983
+ const raw = import_node_fs3.default.readFileSync(filePath, "utf8");
12984
+ for (const line of raw.split("\n")) {
12985
+ if (!line.trim()) continue;
12986
+ const rec = parseLine(line);
12987
+ if (!rec) continue;
12988
+ if (rec.type === "session_meta") {
12989
+ const meta = rec.payload;
12990
+ metaCwd = typeof meta?.cwd === "string" ? meta.cwd : void 0;
12991
+ metaId = typeof meta?.id === "string" ? meta.id : void 0;
12992
+ }
12993
+ break;
12994
+ }
12995
+ } catch {
12996
+ continue;
12997
+ }
12998
+ if (!metaId || !metaCwd) continue;
12999
+ let resolvedMeta;
13000
+ try {
13001
+ resolvedMeta = import_node_fs3.default.realpathSync(metaCwd);
13002
+ } catch {
13003
+ resolvedMeta = import_node_path2.default.resolve(metaCwd);
13004
+ }
13005
+ if (resolvedMeta !== resolvedCwd) continue;
13006
+ bestMtime = mtime;
13007
+ bestId = metaId;
13008
+ }
13009
+ }
13010
+ return bestId;
13011
+ }
13012
+ function sleep2(ms) {
13013
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
13014
+ }
12934
13015
  function getCurrentUsage2(historyDir) {
12935
13016
  if (!import_node_fs3.default.existsSync(historyDir)) return null;
12936
13017
  const files = import_node_fs3.default.readdirSync(historyDir).filter((f) => f.startsWith("rollout-") && f.endsWith(".jsonl")).map((f) => ({ name: f, full: import_node_path2.default.join(historyDir, f) })).map((e) => ({ ...e, mtime: import_node_fs3.default.statSync(e.full).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
@@ -13098,6 +13179,16 @@ var CodexRuntimeStrategy = class {
13098
13179
  resolveHistoryFile(cwd, sessionId) {
13099
13180
  return resolveHistoryFile2(cwd, sessionId);
13100
13181
  }
13182
+ /**
13183
+ * Codex mints its own session id (into a `rollout-*.jsonl` at TUI boot) and
13184
+ * neither accepts a pre-set one nor prints it — so, like Kimi, the baton's
13185
+ * NativeTuiDriver discovers it post-spawn. Codex shares that rollout store with
13186
+ * its ACP adapter, so discovery alone is enough (no cross-store bridge). The
13187
+ * TUI boots slowly, hence the generous poll budget inside.
13188
+ */
13189
+ discoverSessionId(cwd, opts) {
13190
+ return discoverSessionId(cwd, opts);
13191
+ }
13101
13192
  getCurrentUsage(historyDir) {
13102
13193
  return getCurrentUsage2(historyDir);
13103
13194
  }
@@ -14473,7 +14564,7 @@ function scanBucketsForSession(sessionId) {
14473
14564
  }
14474
14565
  return null;
14475
14566
  }
14476
- async function discoverSessionId(cwd, opts) {
14567
+ async function discoverSessionId2(cwd, opts) {
14477
14568
  const bucket = path33.join(kimiHome(), "sessions", workDirKey(cwd));
14478
14569
  const floor = opts.sinceMs - 2e3;
14479
14570
  const deadline = Date.now() + (opts.timeoutMs ?? 15e3);
@@ -14481,7 +14572,7 @@ async function discoverSessionId(cwd, opts) {
14481
14572
  const id = newestSessionSince(bucket, floor);
14482
14573
  if (id) return id;
14483
14574
  if (Date.now() >= deadline) return null;
14484
- await sleep2(250);
14575
+ await sleep3(250);
14485
14576
  }
14486
14577
  }
14487
14578
  function newestSessionSince(bucket, floorMs) {
@@ -14508,7 +14599,7 @@ function newestSessionSince(bucket, floorMs) {
14508
14599
  }
14509
14600
  return bestId;
14510
14601
  }
14511
- function sleep2(ms) {
14602
+ function sleep3(ms) {
14512
14603
  return new Promise((resolve7) => setTimeout(resolve7, ms));
14513
14604
  }
14514
14605
  function joinTextParts(parts) {
@@ -14631,7 +14722,7 @@ var KimiRuntimeStrategy = class {
14631
14722
  * have no baton), so the driver's optional call is inert for them.
14632
14723
  */
14633
14724
  discoverSessionId(cwd, opts) {
14634
- return discoverSessionId(cwd, opts);
14725
+ return discoverSessionId2(cwd, opts);
14635
14726
  }
14636
14727
  getCurrentUsage(historyDir) {
14637
14728
  return getCurrentUsage5(historyDir);
@@ -16642,7 +16733,7 @@ async function autoUpgradeBeforeCriticalCommand() {
16642
16733
  if (process.env.NODE_ENV === "test") return;
16643
16734
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
16644
16735
  if (process.env.CI) return;
16645
- const current = true ? "2.60.20" : null;
16736
+ const current = true ? "2.60.22" : null;
16646
16737
  if (!current) return;
16647
16738
  const cache = readCache();
16648
16739
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -16659,7 +16750,7 @@ function checkForUpdates() {
16659
16750
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
16660
16751
  if (process.env.CI) return;
16661
16752
  if (!process.stdout.isTTY) return;
16662
- const current = true ? "2.60.20" : null;
16753
+ const current = true ? "2.60.22" : null;
16663
16754
  if (!current) return;
16664
16755
  const cache = readCache();
16665
16756
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -16679,7 +16770,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
16679
16770
  var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
16680
16771
  var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
16681
16772
  function currentCliVersion() {
16682
- return true ? "2.60.20" : null;
16773
+ return true ? "2.60.22" : null;
16683
16774
  }
16684
16775
  function runCmd(cmd, args2, timeoutMs) {
16685
16776
  return new Promise((resolve7) => {
@@ -22211,7 +22302,7 @@ async function waitForClaudeNativeBinary(opts = {}) {
22211
22302
  const timeoutMs = opts.timeoutMs ?? 18e4;
22212
22303
  const pollMs = opts.pollMs ?? 500;
22213
22304
  const now = opts.now ?? Date.now;
22214
- const sleep4 = opts.sleep ?? realSleep;
22305
+ const sleep5 = opts.sleep ?? realSleep;
22215
22306
  const deps = {
22216
22307
  sdkDir: opts.sdkDir,
22217
22308
  platformKey: opts.platformKey,
@@ -22221,7 +22312,7 @@ async function waitForClaudeNativeBinary(opts = {}) {
22221
22312
  let found = resolveClaudeNativeBinary(deps);
22222
22313
  if (found) return found;
22223
22314
  while (now() < deadline) {
22224
- await sleep4(pollMs);
22315
+ await sleep5(pollMs);
22225
22316
  found = resolveClaudeNativeBinary(deps);
22226
22317
  if (found) return found;
22227
22318
  }
@@ -22243,13 +22334,13 @@ async function waitForCommandOnPath(cmd, opts = {}) {
22243
22334
  const timeoutMs = opts.timeoutMs ?? 18e4;
22244
22335
  const pollMs = opts.pollMs ?? 500;
22245
22336
  const now = opts.now ?? Date.now;
22246
- const sleep4 = opts.sleep ?? realSleep;
22337
+ const sleep5 = opts.sleep ?? realSleep;
22247
22338
  const probe = opts.probe;
22248
22339
  const check = () => isCommandOnPath(cmd, probe);
22249
22340
  const deadline = now() + timeoutMs;
22250
22341
  if (check()) return true;
22251
22342
  while (now() < deadline) {
22252
- await sleep4(pollMs);
22343
+ await sleep5(pollMs);
22253
22344
  if (check()) return true;
22254
22345
  }
22255
22346
  return check();
@@ -22272,12 +22363,12 @@ async function waitForCursorAgent(opts = {}) {
22272
22363
  const timeoutMs = opts.timeoutMs ?? 18e4;
22273
22364
  const pollMs = opts.pollMs ?? 500;
22274
22365
  const now = opts.now ?? Date.now;
22275
- const sleep4 = opts.sleep ?? realSleep;
22366
+ const sleep5 = opts.sleep ?? realSleep;
22276
22367
  const check = () => resolveCursorAgentBinary(opts) !== null || isCommandOnPath("cursor-agent", opts.probe);
22277
22368
  const deadline = now() + timeoutMs;
22278
22369
  if (check()) return true;
22279
22370
  while (now() < deadline) {
22280
- await sleep4(pollMs);
22371
+ await sleep5(pollMs);
22281
22372
  if (check()) return true;
22282
22373
  }
22283
22374
  return check();
@@ -22326,12 +22417,12 @@ async function waitForAdapterModuleGraph(command2, args2, opts = {}) {
22326
22417
  const timeoutMs = opts.timeoutMs ?? 18e4;
22327
22418
  const pollMs = opts.pollMs ?? 500;
22328
22419
  const now = opts.now ?? Date.now;
22329
- const sleep4 = opts.sleep ?? realSleep;
22420
+ const sleep5 = opts.sleep ?? realSleep;
22330
22421
  const probeOpts = { livenessMs: opts.livenessMs, spawnFn: opts.spawnFn };
22331
22422
  const deadline = now() + timeoutMs;
22332
22423
  if (await probeAdapterModuleGraph(command2, args2, probeOpts) === "ok") return true;
22333
22424
  while (now() < deadline) {
22334
- await sleep4(pollMs);
22425
+ await sleep5(pollMs);
22335
22426
  if (await probeAdapterModuleGraph(command2, args2, probeOpts) === "ok") return true;
22336
22427
  }
22337
22428
  return false;
@@ -29939,12 +30030,12 @@ var StreamingEmitterService = class {
29939
30030
  log.warn("streamingEmitter", `post error url=${url} attempt=${attempt + 1}`, err);
29940
30031
  }
29941
30032
  if (attempt < MAX_RETRIES2) {
29942
- await sleep3(RETRY_BACKOFF_MS3 * (attempt + 1));
30033
+ await sleep4(RETRY_BACKOFF_MS3 * (attempt + 1));
29943
30034
  }
29944
30035
  }
29945
30036
  }
29946
30037
  };
29947
- function sleep3(ms) {
30038
+ function sleep4(ms) {
29948
30039
  return new Promise((r) => setTimeout(r, ms));
29949
30040
  }
29950
30041
  var TREE_CONTINUATION_RE = /^\s*└/;
@@ -30027,6 +30118,20 @@ var BatonController = class {
30027
30118
  this._active = "local_tui";
30028
30119
  this.setState("LOCAL_DRIVE");
30029
30120
  }
30121
+ /**
30122
+ * Late-bind the conversation id for an agent (Codex) that minted it only on the
30123
+ * user's first turn, so `begin()` returned null. Fires from the native driver's
30124
+ * background discovery. Idempotent + guarded: only binds while we're still in
30125
+ * the INITIAL, id-less LOCAL_DRIVE — if the user already took control (which
30126
+ * started its own session) or an id is already set, it's a no-op, so a stray
30127
+ * native id can never clobber the live conversation. Re-publishes LOCAL_DRIVE so
30128
+ * the read-only mirror arms on the now-known id.
30129
+ */
30130
+ rebindConversation(conversationId) {
30131
+ if (this._conversationId !== null || this._state !== "LOCAL_DRIVE") return;
30132
+ this._conversationId = conversationId;
30133
+ this.setState("LOCAL_DRIVE");
30134
+ }
30030
30135
  async takeControl() {
30031
30136
  await this.switchDriver(
30032
30137
  "LOCAL_DRIVE",
@@ -30088,7 +30193,7 @@ function parkTerminalForReadonly(term = {}) {
30088
30193
  }
30089
30194
 
30090
30195
  // src/baton/native-tui-driver.ts
30091
- var NativeTuiDriver = class {
30196
+ var NativeTuiDriver = class _NativeTuiDriver {
30092
30197
  constructor(deps) {
30093
30198
  this.deps = deps;
30094
30199
  this.agent = deps.agent;
@@ -30117,6 +30222,9 @@ var NativeTuiDriver = class {
30117
30222
  idleMs;
30118
30223
  now;
30119
30224
  lastOutput;
30225
+ /** How long `start()` waits for a boot-time session store (Kimi) before
30226
+ * falling back to background/late-bind discovery (Codex, first-turn store). */
30227
+ static QUICK_DISCOVER_MS = 8e3;
30120
30228
  outputSvc;
30121
30229
  historySvc;
30122
30230
  setKeepAlive;
@@ -30131,11 +30239,20 @@ var NativeTuiDriver = class {
30131
30239
  await this.agent.spawn();
30132
30240
  const preMinted = this.agent.spawnedSessionId;
30133
30241
  if (preMinted) return preMinted;
30134
- const discovered = await this.deps.runtime.discoverSessionId?.(this.deps.opts.cwd, {
30135
- sinceMs: spawnedAt
30242
+ const discover = this.deps.runtime.discoverSessionId;
30243
+ if (!discover) {
30244
+ throw new Error("NativeTuiDriver: agent did not expose a session id after spawn");
30245
+ }
30246
+ const quick = await discover(this.deps.opts.cwd, {
30247
+ sinceMs: spawnedAt,
30248
+ timeoutMs: _NativeTuiDriver.QUICK_DISCOVER_MS
30136
30249
  });
30137
- if (discovered) return discovered;
30138
- throw new Error("NativeTuiDriver: agent did not expose a session id after spawn");
30250
+ if (quick) return quick;
30251
+ void discover(this.deps.opts.cwd, { sinceMs: spawnedAt }).then((id) => {
30252
+ if (id) this.deps.onLateBind?.(id);
30253
+ }).catch(() => {
30254
+ });
30255
+ return null;
30139
30256
  }
30140
30257
  async stop() {
30141
30258
  this.agent.kill();
@@ -30549,7 +30666,12 @@ async function runBatonSession(opts) {
30549
30666
  cwd: opts.cwd
30550
30667
  },
30551
30668
  getRelay: () => relay,
30552
- getBeads: opts.getBeads ?? (() => null)
30669
+ getBeads: opts.getBeads ?? (() => null),
30670
+ // Late-bind for first-turn-minted ids (Codex): the background discovery
30671
+ // calls this once the user's first terminal turn creates the transcript.
30672
+ // Safe forward-ref — `controller` is initialised long before `begin()` runs,
30673
+ // and onLateBind only fires from inside `begin()`'s spawn.
30674
+ onLateBind: (id) => controller.rebindConversation(id)
30553
30675
  });
30554
30676
  let mirror = null;
30555
30677
  let firstLocalDrive = true;
@@ -33541,7 +33663,7 @@ function checkChokidar() {
33541
33663
  }
33542
33664
  async function doctor(args2 = []) {
33543
33665
  const json = args2.includes("--json");
33544
- const cliVersion = true ? "2.60.20" : "0.0.0-dev";
33666
+ const cliVersion = true ? "2.60.22" : "0.0.0-dev";
33545
33667
  const apiBase2 = resolveApiBaseUrl();
33546
33668
  const diagnosticId = (0, import_node_crypto12.randomUUID)();
33547
33669
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -33740,7 +33862,7 @@ async function completion(args2) {
33740
33862
  // src/commands/version.ts
33741
33863
  var import_picocolors15 = __toESM(require("picocolors"));
33742
33864
  function version2() {
33743
- const v = true ? "2.60.20" : "unknown";
33865
+ const v = true ? "2.60.22" : "unknown";
33744
33866
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
33745
33867
  }
33746
33868
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.60.20",
3
+ "version": "2.60.22",
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",