codeam-cli 2.65.11 → 2.65.13

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 +51 -0
  2. package/dist/index.js +276 -28
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,57 @@ 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.12] — 2026-08-19
8
+
9
+ ### CI
10
+
11
+ - Make the real session-baton gate actually run, and catch failed publishes (#637)
12
+ - CI results → Discord #ci_notifications, cross-os PR label, release success notice (#639)
13
+
14
+ ### Fixed
15
+
16
+ - **cli:** Baton follows the native TUI through /clear (+ /rename) (#640)
17
+
18
+ ### Tests
19
+
20
+ - **cli:** Fix cross-OS CI failures in augment-PATH and adapter module-graph tests (#638)
21
+
22
+ ## [2.65.11] — 2026-08-19
23
+
24
+ ### Documentation
25
+
26
+ - **cli:** Record the 2026-08-18 baton hand-off + goodbye-heartbeat invariants
27
+
28
+ ### Fixed
29
+
30
+ - **cli:** Take-control never wedges on "Switching…" again
31
+ - **cli:** Say goodbye before exiting so mobile stops showing ONLINE
32
+ - **cli:** LOCAL_DRIVE mirrors the transcript only — no raw TUI bytes to mobile
33
+
34
+ ### Tests
35
+
36
+ - **cli:** Real baton local integration test (take-control before first turn, handback, goodbye heartbeat)
37
+
38
+ ## [2.65.10] — 2026-08-19
39
+
40
+ ### Fixed
41
+
42
+ - **cli:** Codespace sessions report the user's repo, not the shared wrapper hostname
43
+ - **cli:** Dedupe the native ACP model list and validate model/tier at the wire boundary
44
+ - **cli:** Welcome card greets first-time users correctly and never shows a raw internal path
45
+
46
+ ## [2.65.9] — 2026-08-19
47
+
48
+ ### Fixed
49
+
50
+ - **cli:** Report an ineligible-tier credential to the backend on startup failure
51
+
52
+ ## [2.65.8] — 2026-08-18
53
+
54
+ ### Fixed
55
+
56
+ - **cli:** Baton re-affirms its state on the relay heartbeat so the 1h backend snapshot never expires under a live session
57
+
7
58
  ## [2.65.7] — 2026-08-15
8
59
 
9
60
  ### 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.11" : "0.0.0-dev",
8082
+ cliVersion: true ? "2.65.13" : "0.0.0-dev",
8083
8083
  nodeVersion: process.version,
8084
8084
  platform: process.platform,
8085
8085
  arch: process.arch,
@@ -8321,7 +8321,7 @@ var http = __toESM(require("http"));
8321
8321
  // package.json
8322
8322
  var package_default = {
8323
8323
  name: "codeam-cli",
8324
- version: "2.65.11",
8324
+ version: "2.65.13",
8325
8325
  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.",
8326
8326
  type: "commonjs",
8327
8327
  main: "dist/index.js",
@@ -9842,7 +9842,7 @@ var CommandRelayService = class _CommandRelayService {
9842
9842
  // fresh + clear the "CLI update available" banner after a self-update
9843
9843
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
9844
9844
  // pair/reconnect). Older backends ignore the extra field.
9845
- ..."2.65.11" ? { ideVersion: "2.65.11" } : {}
9845
+ ..."2.65.13" ? { ideVersion: "2.65.13" } : {}
9846
9846
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
9847
9847
  }
9848
9848
  /**
@@ -15283,6 +15283,7 @@ function parseHistoryFile(filePath) {
15283
15283
  if (!msg) continue;
15284
15284
  const text = extractText(msg["content"]).trim();
15285
15285
  if (!text) continue;
15286
+ if (type === "user" && isLocalCommandEcho(text)) continue;
15286
15287
  const ts = r["timestamp"];
15287
15288
  const timestamp = typeof ts === "string" ? ts : typeof ts === "number" ? new Date(ts).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
15288
15289
  const uuid = typeof r["uuid"] === "string" ? r["uuid"] : `${Date.now()}-${Math.random()}`;
@@ -15347,6 +15348,175 @@ function listResumableSessions(cwd) {
15347
15348
  out2.sort((a, b) => b.timestamp - a.timestamp);
15348
15349
  return out2;
15349
15350
  }
15351
+ function isLocalCommandEcho(text) {
15352
+ return /^\s*<(command-name|local-command-[a-z]+)>/.test(text);
15353
+ }
15354
+ var CLEAR_COMMAND_ECHO = "<command-name>/clear</command-name>";
15355
+ var SWITCH_PROBE_BYTES = 256 * 1024;
15356
+ function isClearedConversationFile(filePath) {
15357
+ let fd;
15358
+ try {
15359
+ fd = fs17.openSync(filePath, "r");
15360
+ } catch {
15361
+ return false;
15362
+ }
15363
+ let raw;
15364
+ try {
15365
+ const buf = Buffer.alloc(SWITCH_PROBE_BYTES);
15366
+ const n = fs17.readSync(fd, buf, 0, SWITCH_PROBE_BYTES, 0);
15367
+ raw = buf.toString("utf8", 0, n);
15368
+ } catch {
15369
+ return false;
15370
+ } finally {
15371
+ fs17.closeSync(fd);
15372
+ }
15373
+ if (!raw.includes(CLEAR_COMMAND_ECHO) && !raw.includes("SessionStart:clear")) return false;
15374
+ for (const line of raw.split("\n")) {
15375
+ if (!line.trim()) continue;
15376
+ let r;
15377
+ try {
15378
+ r = JSON.parse(line);
15379
+ } catch {
15380
+ continue;
15381
+ }
15382
+ if (r["type"] === "user" && !r["isMeta"]) {
15383
+ const msg = r["message"];
15384
+ if (extractText(msg?.["content"]).trimStart().startsWith(CLEAR_COMMAND_ECHO)) return true;
15385
+ }
15386
+ if (r["type"] === "attachment") {
15387
+ const att = r["attachment"];
15388
+ if (typeof att?.["hookName"] === "string" && att["hookName"].startsWith("SessionStart:clear")) {
15389
+ return true;
15390
+ }
15391
+ }
15392
+ }
15393
+ return false;
15394
+ }
15395
+ function appendedTailHasResumeMarker(filePath, fromOffset) {
15396
+ let fd;
15397
+ try {
15398
+ fd = fs17.openSync(filePath, "r");
15399
+ } catch {
15400
+ return false;
15401
+ }
15402
+ try {
15403
+ const size = fs17.fstatSync(fd).size;
15404
+ const len = Math.min(Math.max(size - fromOffset, 0), SWITCH_PROBE_BYTES);
15405
+ if (len <= 0) return false;
15406
+ const buf = Buffer.alloc(len);
15407
+ const n = fs17.readSync(fd, buf, 0, len, fromOffset);
15408
+ const raw = buf.toString("utf8", 0, n);
15409
+ if (!raw.includes('"last-prompt"')) return false;
15410
+ for (const line of raw.split("\n")) {
15411
+ if (!line.trim()) continue;
15412
+ try {
15413
+ if (JSON.parse(line)["type"] === "last-prompt") return true;
15414
+ } catch {
15415
+ }
15416
+ }
15417
+ return false;
15418
+ } catch {
15419
+ return false;
15420
+ } finally {
15421
+ fs17.closeSync(fd);
15422
+ }
15423
+ }
15424
+ function watchConversationSwitch(cwd, opts, onSwitch, projectsRoot) {
15425
+ const root = projectsRoot ?? path21.join(os15.homedir(), ".claude", "projects");
15426
+ let currentId = opts.currentId;
15427
+ const baseline = /* @__PURE__ */ new Map();
15428
+ const unclassified = /* @__PURE__ */ new Map();
15429
+ let dirWatcher = null;
15430
+ let rootWatcher = null;
15431
+ let closed = false;
15432
+ const fileSize = (file) => {
15433
+ try {
15434
+ return fs17.statSync(file).size;
15435
+ } catch {
15436
+ return null;
15437
+ }
15438
+ };
15439
+ const switchTo = (dir, id, kind) => {
15440
+ const leaving = fileSize(path21.join(dir, `${currentId}.jsonl`));
15441
+ if (leaving !== null) baseline.set(currentId, leaving);
15442
+ baseline.delete(id);
15443
+ unclassified.delete(id);
15444
+ currentId = id;
15445
+ onSwitch(id, { kind });
15446
+ };
15447
+ const scan = (dir) => {
15448
+ if (closed) return;
15449
+ let entries;
15450
+ try {
15451
+ entries = fs17.readdirSync(dir);
15452
+ } catch {
15453
+ return;
15454
+ }
15455
+ for (const name of entries) {
15456
+ if (!name.endsWith(".jsonl")) continue;
15457
+ const id = name.slice(0, -".jsonl".length);
15458
+ if (id === currentId) continue;
15459
+ const file = path21.join(dir, name);
15460
+ const size = fileSize(file);
15461
+ if (size === null) continue;
15462
+ const known = baseline.get(id);
15463
+ if (known !== void 0) {
15464
+ if (size > known && appendedTailHasResumeMarker(file, known)) {
15465
+ switchTo(dir, id, "resumed");
15466
+ return;
15467
+ }
15468
+ continue;
15469
+ }
15470
+ if (unclassified.get(id) === size) continue;
15471
+ unclassified.set(id, size);
15472
+ if (isClearedConversationFile(file)) {
15473
+ switchTo(dir, id, "new");
15474
+ return;
15475
+ }
15476
+ }
15477
+ };
15478
+ const attachDir = (initial) => {
15479
+ const dir = resolveHistoryDir(cwd, root);
15480
+ if (!dir) return false;
15481
+ rootWatcher?.close();
15482
+ rootWatcher = null;
15483
+ if (initial) {
15484
+ try {
15485
+ for (const name of fs17.readdirSync(dir)) {
15486
+ if (!name.endsWith(".jsonl")) continue;
15487
+ const id = name.slice(0, -".jsonl".length);
15488
+ if (id === currentId) continue;
15489
+ const size = fileSize(path21.join(dir, name));
15490
+ if (size !== null) baseline.set(id, size);
15491
+ }
15492
+ } catch {
15493
+ }
15494
+ }
15495
+ try {
15496
+ dirWatcher = fs17.watch(dir, { persistent: false }, () => scan(dir));
15497
+ } catch {
15498
+ return false;
15499
+ }
15500
+ scan(dir);
15501
+ return true;
15502
+ };
15503
+ if (!attachDir(true)) {
15504
+ try {
15505
+ fs17.mkdirSync(root, { recursive: true });
15506
+ rootWatcher = fs17.watch(root, { persistent: false }, () => {
15507
+ if (!closed && !dirWatcher) attachDir(false);
15508
+ });
15509
+ } catch {
15510
+ }
15511
+ }
15512
+ return () => {
15513
+ closed = true;
15514
+ dirWatcher?.close();
15515
+ rootWatcher?.close();
15516
+ dirWatcher = null;
15517
+ rootWatcher = null;
15518
+ };
15519
+ }
15350
15520
 
15351
15521
  // src/agents/claude/runtime.ts
15352
15522
  var ClaudeRuntimeStrategy = class {
@@ -15436,6 +15606,14 @@ var ClaudeRuntimeStrategy = class {
15436
15606
  parseHistoryFile(filePath) {
15437
15607
  return parseHistoryFile(filePath);
15438
15608
  }
15609
+ /**
15610
+ * Baton: follow the native TUI through `/clear` (new conversation id, new
15611
+ * JSONL) and `/resume` (an existing one) — see
15612
+ * {@link history.watchConversationSwitch}.
15613
+ */
15614
+ watchConversationSwitch(cwd, opts, onSwitch) {
15615
+ return watchConversationSwitch(cwd, opts, onSwitch);
15616
+ }
15439
15617
  getCurrentUsage(historyDir) {
15440
15618
  return getCurrentUsage(historyDir);
15441
15619
  }
@@ -18137,25 +18315,27 @@ async function waitForCommandOnPath(cmd, opts = {}) {
18137
18315
  }
18138
18316
  return check();
18139
18317
  }
18140
- function augmentUserLocalBinPaths() {
18141
- const home = import_os4.default.homedir();
18318
+ function augmentUserLocalBinPaths(deps = {}) {
18319
+ const env = deps.env ?? process.env;
18320
+ const home = deps.homedir ?? import_os4.default.homedir();
18321
+ const p2 = deps.pathApi ?? import_path4.default;
18142
18322
  const candidates = [
18143
18323
  // XDG-style per-user bin — npm's default global-prefix bin dir on most
18144
18324
  // Linux setups (`npm config set prefix ~/.local` or an nvm-less
18145
18325
  // per-user npm), and where curl-based agent installers commonly land.
18146
- import_path4.default.join(home, ".local", "bin"),
18326
+ p2.join(home, ".local", "bin"),
18147
18327
  // Common explicit npm global-prefix conventions seen in the wild
18148
18328
  // (`npm config set prefix ~/.npm-global`, and Debian/Fedora's
18149
18329
  // `~/.local/share/npm` layout for `npm config set prefix
18150
18330
  // ~/.local/share/npm`).
18151
- import_path4.default.join(home, ".npm-global", "bin"),
18152
- import_path4.default.join(home, ".local", "share", "npm", "bin")
18331
+ p2.join(home, ".npm-global", "bin"),
18332
+ p2.join(home, ".local", "share", "npm", "bin")
18153
18333
  ];
18154
- const parts = (process.env.PATH ?? "").split(import_path4.default.delimiter).filter((p2) => p2.length > 0);
18334
+ const parts = (env.PATH ?? "").split(p2.delimiter).filter((s) => s.length > 0);
18155
18335
  const existing = new Set(parts);
18156
18336
  const additions = candidates.filter((dir) => !existing.has(dir));
18157
18337
  if (additions.length === 0) return;
18158
- process.env.PATH = [...additions, ...parts].join(import_path4.default.delimiter);
18338
+ env.PATH = [...additions, ...parts].join(p2.delimiter);
18159
18339
  }
18160
18340
  function resolveCursorAgentBinary(deps = {}) {
18161
18341
  const existsSync29 = deps.existsSync ?? import_fs.default.existsSync;
@@ -21786,7 +21966,7 @@ async function autoUpgradeBeforeCriticalCommand() {
21786
21966
  if (process.env.NODE_ENV === "test") return;
21787
21967
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
21788
21968
  if (process.env.CI) return;
21789
- const current2 = true ? "2.65.11" : null;
21969
+ const current2 = true ? "2.65.13" : null;
21790
21970
  if (!current2) return;
21791
21971
  const cache = readCache();
21792
21972
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -21803,7 +21983,7 @@ function checkForUpdates() {
21803
21983
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
21804
21984
  if (process.env.CI) return;
21805
21985
  if (!process.stdout.isTTY) return;
21806
- const current2 = true ? "2.65.11" : null;
21986
+ const current2 = true ? "2.65.13" : null;
21807
21987
  if (!current2) return;
21808
21988
  const cache = readCache();
21809
21989
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -21823,7 +22003,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
21823
22003
  var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
21824
22004
  var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
21825
22005
  function currentCliVersion() {
21826
- return true ? "2.65.11" : null;
22006
+ return true ? "2.65.13" : null;
21827
22007
  }
21828
22008
  function runCmd(cmd, args2, timeoutMs) {
21829
22009
  return new Promise((resolve10) => {
@@ -34580,14 +34760,17 @@ var AcpClient = class {
34580
34760
  this.modelConfigId = modelOption.id;
34581
34761
  this.currentModelId = nonEmptyString(modelOption.currentValue);
34582
34762
  this.availableModels = dedupeModelOptions(
34583
- flattenSelectOptions(modelOption.options).map((opt) => ({
34584
- id: opt.value,
34585
- label: opt.name,
34586
- // Only when it's a real catalog match — native ids are often opaque
34587
- // aliases ("default"/"opus") or proxied (MiniMax house agent), for which a
34588
- // default 200K is a fake; undefined the UI omits the context sub-label.
34589
- contextWindow: tryGetContextWindow(opt.value)
34590
- }))
34763
+ filterChatModelOptions(
34764
+ flattenSelectOptions(modelOption.options).map((opt) => ({
34765
+ id: opt.value,
34766
+ label: opt.name,
34767
+ // Only when it's a real catalog match native ids are often opaque
34768
+ // aliases ("default"/"opus") or proxied (MiniMax house agent), for which a
34769
+ // default 200K is a fake; undefined → the UI omits the context sub-label.
34770
+ contextWindow: tryGetContextWindow(opt.value)
34771
+ })),
34772
+ this.currentModelId
34773
+ )
34591
34774
  );
34592
34775
  }
34593
34776
  /**
@@ -34815,6 +34998,21 @@ function flattenSelectOptions(options) {
34815
34998
  function nonEmptyString(value) {
34816
34999
  return typeof value === "string" && value.trim().length > 0 ? value : void 0;
34817
35000
  }
35001
+ var NON_CHAT_MODEL_PATTERNS = [
35002
+ /\bembed(?:ding)?s?\b/i,
35003
+ // text-embedding-3-large, *-embed-*, embeddings
35004
+ /\brerank(?:er|ing)?\b/i,
35005
+ /\bmoderation\b/i,
35006
+ /\btts\b/i,
35007
+ /\bwhisper\b/i,
35008
+ /\btranscribe\b/i,
35009
+ /\bdall-?e\b/i
35010
+ ];
35011
+ function filterChatModelOptions(models, currentModelId) {
35012
+ return models.filter(
35013
+ (m) => m.id === currentModelId || !NON_CHAT_MODEL_PATTERNS.some((re2) => re2.test(`${m.id} ${m.label}`))
35014
+ );
35015
+ }
34818
35016
  function dedupeModelOptions(models) {
34819
35017
  const seen = /* @__PURE__ */ new Set();
34820
35018
  const unique = [];
@@ -41268,6 +41466,21 @@ var BatonController = class _BatonController {
41268
41466
  this._conversationId = conversationId;
41269
41467
  this.setState("LOCAL_DRIVE");
41270
41468
  }
41469
+ /**
41470
+ * The native TUI SWITCHED conversation while driving (Claude `/clear` →
41471
+ * a new id, `/resume` → an existing one): the process and the pairing are
41472
+ * unchanged, but the conversation the user is in has another id. Re-point
41473
+ * the baton at it so the read-only mirror follows that transcript and a
41474
+ * later Take Control resumes THAT conversation instead of the abandoned one. Guarded to LOCAL_DRIVE — only
41475
+ * the terminal can switch its own conversation, and during a hand-off /
41476
+ * MOBILE_DRIVE the native watcher is torn down anyway (`stop()`). Same id →
41477
+ * no-op. Re-publishes LOCAL_DRIVE so the mirror re-arms + mobile learns the id.
41478
+ */
41479
+ switchConversation(conversationId) {
41480
+ if (this._state !== "LOCAL_DRIVE" || this._conversationId === conversationId) return;
41481
+ this._conversationId = conversationId;
41482
+ this.setState("LOCAL_DRIVE");
41483
+ }
41271
41484
  async takeControl() {
41272
41485
  await this.switchDriver(
41273
41486
  "LOCAL_DRIVE",
@@ -41419,18 +41632,25 @@ var NativeTuiDriver = class _NativeTuiDriver {
41419
41632
  static QUICK_DISCOVER_MS = 8e3;
41420
41633
  outputSvc;
41421
41634
  historySvc;
41635
+ /** Unsubscribe for the runtime's conversation-switch watcher (armed per
41636
+ * `start()`, torn down in `stop()`), or null when not armed. */
41637
+ unwatchSwitch = null;
41422
41638
  setKeepAlive;
41423
41639
  keepAliveCtx;
41424
41640
  async start(resumeId) {
41425
41641
  if (resumeId !== void 0) {
41426
41642
  await this.deps.runtime.syncTranscriptForNativeResume?.(this.deps.opts.cwd, resumeId);
41427
41643
  await this.agent.restart(resumeId, false);
41644
+ this.armSwitchWatch(resumeId);
41428
41645
  return resumeId;
41429
41646
  }
41430
41647
  const spawnedAt = this.now();
41431
41648
  await this.agent.spawn();
41432
41649
  const preMinted = this.agent.spawnedSessionId;
41433
- if (preMinted) return preMinted;
41650
+ if (preMinted) {
41651
+ this.armSwitchWatch(preMinted);
41652
+ return preMinted;
41653
+ }
41434
41654
  const discover = this.deps.runtime.discoverSessionId;
41435
41655
  if (!discover) {
41436
41656
  throw new Error("NativeTuiDriver: agent did not expose a session id after spawn");
@@ -41446,7 +41666,26 @@ var NativeTuiDriver = class _NativeTuiDriver {
41446
41666
  });
41447
41667
  return null;
41448
41668
  }
41669
+ /**
41670
+ * Follow the native TUI if it switches conversation mid-session (Claude
41671
+ * `/clear` / `/resume`): the runtime's event-driven watcher reports the id
41672
+ * now being driven, we point the PTY-side history at it and tell the
41673
+ * controller to rebind. Armed once per `start()` (fresh or resume) on the
41674
+ * id the TUI is driving; inert for agents without the hook.
41675
+ */
41676
+ armSwitchWatch(currentId) {
41677
+ this.unwatchSwitch?.();
41678
+ this.unwatchSwitch = null;
41679
+ const watch3 = this.deps.runtime.watchConversationSwitch;
41680
+ if (!watch3) return;
41681
+ this.unwatchSwitch = watch3(this.deps.opts.cwd, { currentId }, (id) => {
41682
+ this.historySvc.setCurrentConversationId(id);
41683
+ this.deps.onConversationSwitch?.(id);
41684
+ });
41685
+ }
41449
41686
  async stop() {
41687
+ this.unwatchSwitch?.();
41688
+ this.unwatchSwitch = null;
41450
41689
  this.agent.kill();
41451
41690
  parkTerminalForReadonly();
41452
41691
  }
@@ -41693,8 +41932,8 @@ var TranscriptMirror = class {
41693
41932
  if (!file) return false;
41694
41933
  this.attached = true;
41695
41934
  this.emit(file);
41696
- const watch2 = this.deps.watch ?? defaultWatch;
41697
- this.unwatch = watch2(file, () => this.emit(file));
41935
+ const watch3 = this.deps.watch ?? defaultWatch;
41936
+ this.unwatch = watch3(file, () => this.emit(file));
41698
41937
  return true;
41699
41938
  }
41700
41939
  clearPoll() {
@@ -41909,10 +42148,17 @@ async function runBatonSession(opts) {
41909
42148
  // calls this once the user's first terminal turn creates the transcript.
41910
42149
  // Safe forward-ref — `controller` is initialised long before `begin()` runs,
41911
42150
  // and onLateBind only fires from inside `begin()`'s spawn.
41912
- onLateBind: (id) => controller.rebindConversation(id)
42151
+ onLateBind: (id) => controller.rebindConversation(id),
42152
+ // The native TUI switched conversation mid-drive (Claude `/clear` mints a
42153
+ // new id + JSONL; `/resume` re-opens an existing one): rebind so the mirror
42154
+ // follows that transcript and a later Take Control resumes THAT
42155
+ // conversation. Owner report 2026-08-18: after `/clear` (+`/rename`) the
42156
+ // mobile went silent — the mirror kept tailing the abandoned file.
42157
+ onConversationSwitch: (id) => controller.switchConversation(id)
41913
42158
  });
41914
42159
  let mirror = null;
41915
42160
  let firstLocalDrive = true;
42161
+ let lastPublished = null;
41916
42162
  const startMirror = (conversationId, fresh) => {
41917
42163
  mirror?.stop();
41918
42164
  mirror = new TranscriptMirror({
@@ -41945,11 +42191,13 @@ async function runBatonSession(opts) {
41945
42191
  publishState: (state, driver, conversationId) => {
41946
42192
  publishBatonState(state, driver, conversationId);
41947
42193
  if (state === "LOCAL_DRIVE" && conversationId) {
41948
- startMirror(conversationId, firstLocalDrive);
42194
+ const switched = lastPublished === "LOCAL_DRIVE";
42195
+ startMirror(conversationId, firstLocalDrive || switched);
41949
42196
  firstLocalDrive = false;
41950
42197
  } else if (state !== "LOCAL_DRIVE") {
41951
42198
  mirror?.stop();
41952
42199
  }
42200
+ lastPublished = state;
41953
42201
  }
41954
42202
  });
41955
42203
  const dispatchActive = (cmd) => controller.activeSessionDriver.dispatch(cmd);
@@ -45024,7 +45272,7 @@ function checkChokidar() {
45024
45272
  }
45025
45273
  async function doctor(args2 = []) {
45026
45274
  const json = args2.includes("--json");
45027
- const cliVersion = true ? "2.65.11" : "0.0.0-dev";
45275
+ const cliVersion = true ? "2.65.13" : "0.0.0-dev";
45028
45276
  const apiBase2 = resolveApiBaseUrl();
45029
45277
  const diagnosticId = (0, import_node_crypto13.randomUUID)();
45030
45278
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -45415,7 +45663,7 @@ async function mcpRun(args2) {
45415
45663
  // src/commands/version.ts
45416
45664
  var import_picocolors15 = __toESM(require("picocolors"));
45417
45665
  function version2() {
45418
- const v = true ? "2.65.11" : "unknown";
45666
+ const v = true ? "2.65.13" : "unknown";
45419
45667
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
45420
45668
  }
45421
45669
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.65.11",
3
+ "version": "2.65.13",
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",