codeam-cli 2.60.1 → 2.60.3

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 +16 -0
  2. package/dist/index.js +106 -19
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ 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.2] — 2026-07-08
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** Baton mirror waits for the jsonl, pushes full snapshot, live-publishes every turn
12
+
13
+ ## [2.60.1] — 2026-07-08
14
+
15
+ ### Chore
16
+
17
+ - **cli:** Remove CODEAM_BATON flag — baton unconditional for local sessions
18
+
19
+ ### Fixed
20
+
21
+ - **cli:** Baton LOCAL_DRIVE mirror publishes turns live via the output stream
22
+
7
23
  ## [2.60.0] — 2026-07-08
8
24
 
9
25
  ### Added
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.1" : "0.0.0-dev",
5656
+ cliVersion: true ? "2.60.3" : "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.1",
5837
+ version: "2.60.3",
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.1" ? { ideVersion: "2.60.1" } : {}
6908
+ ..."2.60.3" ? { ideVersion: "2.60.3" } : {}
6909
6909
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
6910
6910
  }
6911
6911
  /**
@@ -15921,7 +15921,7 @@ async function autoUpgradeBeforeCriticalCommand() {
15921
15921
  if (process.env.NODE_ENV === "test") return;
15922
15922
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
15923
15923
  if (process.env.CI) return;
15924
- const current = true ? "2.60.1" : null;
15924
+ const current = true ? "2.60.3" : null;
15925
15925
  if (!current) return;
15926
15926
  const cache = readCache();
15927
15927
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -15938,7 +15938,7 @@ function checkForUpdates() {
15938
15938
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
15939
15939
  if (process.env.CI) return;
15940
15940
  if (!process.stdout.isTTY) return;
15941
- const current = true ? "2.60.1" : null;
15941
+ const current = true ? "2.60.3" : null;
15942
15942
  if (!current) return;
15943
15943
  const cache = readCache();
15944
15944
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -15958,7 +15958,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
15958
15958
  var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
15959
15959
  var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
15960
15960
  function currentCliVersion() {
15961
- return true ? "2.60.1" : null;
15961
+ return true ? "2.60.3" : null;
15962
15962
  }
15963
15963
  function runCmd(cmd, args2, timeoutMs) {
15964
15964
  return new Promise((resolve7) => {
@@ -27361,6 +27361,19 @@ var StreamingState = class {
27361
27361
  */
27362
27362
  text = "";
27363
27363
  pending = null;
27364
+ /**
27365
+ * While true, {@link append} drops incoming session/update deltas instead of
27366
+ * publishing them. Used by the baton {@link AcpDriver} to swallow the
27367
+ * conversation REPLAY that an ACP `session/load` streams (the agent re-emits
27368
+ * the whole prior conversation as `agent_message_chunk` notifications before
27369
+ * the load resolves — ACP spec). Those deltas are history, not a live turn:
27370
+ * publishing them opens a streaming tail that never closes (no `closeAll`),
27371
+ * so mobile's "Thinking…" activity indicator stays pinned on after Take
27372
+ * Control. Mobile already has that history via `pushConversation`, so the
27373
+ * replay is pure noise. The normal ACP path never calls `loadSession`
27374
+ * (fresh `session/new` + `--resume` at spawn), so only the baton hits this.
27375
+ */
27376
+ loadReplayActive = false;
27364
27377
  /**
27365
27378
  * Per-chunkId cumulative buffers for the Epic C streaming-chunk
27366
27379
  * feed. The chat-output pipe (`publishOutput`) coalesces by
@@ -27531,7 +27544,18 @@ var StreamingState = class {
27531
27544
  }
27532
27545
  await this.publisher.publishOutput({ type: "new_turn", done: false });
27533
27546
  }
27547
+ /** Begin swallowing session/update deltas — see {@link loadReplayActive}.
27548
+ * Called by the baton {@link AcpDriver} around an ACP `session/load` so the
27549
+ * replayed conversation history never opens a live streaming tail. */
27550
+ beginLoadReplay() {
27551
+ this.loadReplayActive = true;
27552
+ }
27553
+ /** Stop swallowing deltas — live turns after the load stream normally. */
27554
+ endLoadReplay() {
27555
+ this.loadReplayActive = false;
27556
+ }
27534
27557
  append(delta) {
27558
+ if (this.loadReplayActive) return;
27535
27559
  const baseId = delta.kind === "text" ? this.turnTextChunkId ??= delta.chunkId : delta.kind === "thinking" ? this.turnThoughtChunkId ??= delta.chunkId : delta.chunkId;
27536
27560
  const chunkId = `t${this.turnSeq}:${baseId}`;
27537
27561
  const existing = this.streamingChunks.get(chunkId);
@@ -29187,6 +29211,21 @@ var BatonController = class {
29187
29211
  }
29188
29212
  };
29189
29213
 
29214
+ // src/baton/terminal.ts
29215
+ var TUI_MODE_RESET = "\x1B[?1004l\x1B[?2004l\x1B[?1000l\x1B[?1002l\x1B[?1003l\x1B[?1006l\x1B[?25h";
29216
+ function parkTerminalForReadonly(term = {}) {
29217
+ const out2 = term.out ?? process.stdout;
29218
+ const inp = term.inp ?? process.stdin;
29219
+ if (out2.isTTY) {
29220
+ out2.write(TUI_MODE_RESET);
29221
+ }
29222
+ try {
29223
+ if (inp.isTTY && typeof inp.setRawMode === "function") inp.setRawMode(false);
29224
+ } catch {
29225
+ }
29226
+ inp.pause?.();
29227
+ }
29228
+
29190
29229
  // src/baton/native-tui-driver.ts
29191
29230
  var NativeTuiDriver = class {
29192
29231
  constructor(deps) {
@@ -29233,6 +29272,7 @@ var NativeTuiDriver = class {
29233
29272
  }
29234
29273
  async stop() {
29235
29274
  this.agent.kill();
29275
+ parkTerminalForReadonly();
29236
29276
  }
29237
29277
  async dispatch(cmd) {
29238
29278
  const ctx = {
@@ -29297,7 +29337,12 @@ var AcpDriver = class {
29297
29337
  try {
29298
29338
  started = await this.deps.client.start();
29299
29339
  if (resumeId !== void 0) {
29300
- await this.deps.client.loadSession(resumeId);
29340
+ this.deps.streaming.beginLoadReplay();
29341
+ try {
29342
+ await this.deps.client.loadSession(resumeId);
29343
+ } finally {
29344
+ this.deps.streaming.endLoadReplay();
29345
+ }
29301
29346
  }
29302
29347
  } catch (err) {
29303
29348
  await this.deps.client.stop().catch(() => void 0);
@@ -29410,20 +29455,54 @@ var fs58 = __toESM(require("fs"));
29410
29455
  var TranscriptMirror = class {
29411
29456
  constructor(deps) {
29412
29457
  this.deps = deps;
29458
+ this.pollIntervalMs = deps.pollIntervalMs ?? 750;
29459
+ this.waitTimeoutMs = deps.waitTimeoutMs ?? 10 * 6e4;
29460
+ this.setIntervalFn = deps.setInterval ?? ((fn, ms) => setInterval(fn, ms));
29461
+ this.clearIntervalFn = deps.clearInterval ?? ((handle) => clearInterval(handle));
29413
29462
  }
29414
29463
  deps;
29415
29464
  emitted = 0;
29416
29465
  unwatch = null;
29466
+ pollHandle = null;
29467
+ attached = false;
29468
+ pollIntervalMs;
29469
+ waitTimeoutMs;
29470
+ setIntervalFn;
29471
+ clearIntervalFn;
29417
29472
  start() {
29473
+ if (this.tryAttach()) return;
29474
+ let waited = 0;
29475
+ this.pollHandle = this.setIntervalFn(() => {
29476
+ waited += this.pollIntervalMs;
29477
+ if (this.tryAttach()) {
29478
+ this.clearPoll();
29479
+ return;
29480
+ }
29481
+ if (this.waitTimeoutMs > 0 && waited >= this.waitTimeoutMs) this.clearPoll();
29482
+ }, this.pollIntervalMs);
29483
+ }
29484
+ stop() {
29485
+ this.clearPoll();
29486
+ this.unwatch?.();
29487
+ this.unwatch = null;
29488
+ }
29489
+ /** Resolve the transcript file; if present, emit the current contents and
29490
+ * begin watching. Returns whether it attached. */
29491
+ tryAttach() {
29492
+ if (this.attached) return true;
29418
29493
  const file = this.deps.runtime.resolveHistoryFile?.(this.deps.cwd, this.deps.conversationId);
29419
- if (!file) return;
29494
+ if (!file) return false;
29495
+ this.attached = true;
29420
29496
  this.emit(file);
29421
29497
  const watch2 = this.deps.watch ?? defaultWatch;
29422
29498
  this.unwatch = watch2(file, () => this.emit(file));
29499
+ return true;
29423
29500
  }
29424
- stop() {
29425
- this.unwatch?.();
29426
- this.unwatch = null;
29501
+ clearPoll() {
29502
+ if (this.pollHandle !== null) {
29503
+ this.clearIntervalFn(this.pollHandle);
29504
+ this.pollHandle = null;
29505
+ }
29427
29506
  }
29428
29507
  emit(file) {
29429
29508
  let all;
@@ -29489,6 +29568,7 @@ async function publishMirroredTurnsLive(publisher, messages) {
29489
29568
  function makeMirrorOnNewMessages(deps) {
29490
29569
  let isFirstEmit = true;
29491
29570
  let publishChain = Promise.resolve();
29571
+ const conversation = [];
29492
29572
  return (messages) => {
29493
29573
  const relevant = messages.filter((m) => m.role !== "system");
29494
29574
  if (relevant.length === 0) return;
@@ -29498,12 +29578,13 @@ function makeMirrorOnNewMessages(deps) {
29498
29578
  text: m.text,
29499
29579
  timestamp: toEpochMs(m.timestamp)
29500
29580
  }));
29581
+ conversation.push(...mapped);
29501
29582
  void deps.publisher.pushConversation({
29502
29583
  agentId: deps.agentId,
29503
29584
  sessionId: deps.conversationId,
29504
- messages: mapped
29585
+ messages: conversation.slice()
29505
29586
  });
29506
- if (!isFirstEmit) {
29587
+ if (deps.fresh || !isFirstEmit) {
29507
29588
  publishChain = publishChain.then(() => publishMirroredTurnsLive(deps.publisher, relevant)).catch((err) => {
29508
29589
  log.warn(
29509
29590
  "wireBaton",
@@ -29593,7 +29674,8 @@ async function runBatonSession(opts) {
29593
29674
  getBeads: opts.getBeads ?? (() => null)
29594
29675
  });
29595
29676
  let mirror = null;
29596
- const startMirror = (conversationId) => {
29677
+ let firstLocalDrive = true;
29678
+ const startMirror = (conversationId, fresh) => {
29597
29679
  mirror?.stop();
29598
29680
  mirror = new TranscriptMirror({
29599
29681
  runtime,
@@ -29602,7 +29684,8 @@ async function runBatonSession(opts) {
29602
29684
  onNewMessages: makeMirrorOnNewMessages({
29603
29685
  publisher,
29604
29686
  agentId: opts.agent,
29605
- conversationId
29687
+ conversationId,
29688
+ fresh
29606
29689
  })
29607
29690
  });
29608
29691
  mirror.start();
@@ -29619,8 +29702,12 @@ async function runBatonSession(opts) {
29619
29702
  driver,
29620
29703
  conversationId
29621
29704
  });
29622
- if (state === "LOCAL_DRIVE" && conversationId) startMirror(conversationId);
29623
- else if (state !== "LOCAL_DRIVE") mirror?.stop();
29705
+ if (state === "LOCAL_DRIVE" && conversationId) {
29706
+ startMirror(conversationId, firstLocalDrive);
29707
+ firstLocalDrive = false;
29708
+ } else if (state !== "LOCAL_DRIVE") {
29709
+ mirror?.stop();
29710
+ }
29624
29711
  }
29625
29712
  });
29626
29713
  const dispatchActive = (cmd) => controller.activeSessionDriver.dispatch(cmd);
@@ -32574,7 +32661,7 @@ function checkChokidar() {
32574
32661
  }
32575
32662
  async function doctor(args2 = []) {
32576
32663
  const json = args2.includes("--json");
32577
- const cliVersion = true ? "2.60.1" : "0.0.0-dev";
32664
+ const cliVersion = true ? "2.60.3" : "0.0.0-dev";
32578
32665
  const apiBase2 = resolveApiBaseUrl();
32579
32666
  const diagnosticId = (0, import_node_crypto9.randomUUID)();
32580
32667
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -32773,7 +32860,7 @@ async function completion(args2) {
32773
32860
  // src/commands/version.ts
32774
32861
  var import_picocolors15 = __toESM(require("picocolors"));
32775
32862
  function version2() {
32776
- const v = true ? "2.60.1" : "unknown";
32863
+ const v = true ? "2.60.3" : "unknown";
32777
32864
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
32778
32865
  }
32779
32866
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.60.1",
3
+ "version": "2.60.3",
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",