codeam-cli 2.60.0 → 2.60.2

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 +42 -0
  2. package/dist/index.js +102 -35
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,48 @@ 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.1] — 2026-07-08
8
+
9
+ ### Chore
10
+
11
+ - **cli:** Remove CODEAM_BATON flag — baton unconditional for local sessions
12
+
13
+ ### Fixed
14
+
15
+ - **cli:** Baton LOCAL_DRIVE mirror publishes turns live via the output stream
16
+
17
+ ## [2.60.0] — 2026-07-08
18
+
19
+ ### Added
20
+
21
+ - **cli:** Baton local-session gate + feature flag
22
+ - **cli:** BatonController turn-safe single-driver state machine
23
+ - **cli:** TranscriptMirror emits JSONL deltas for the read-only view
24
+ - **cli:** NativeTuiDriver over AgentService with idle turn-boundary
25
+ - **cli:** AcpDriver over AcpClient (spawn + loadSession resume)
26
+ - **cli:** PostBatonEvent publisher driver-state
27
+ - **cli:** Take_control/handback relay handlers
28
+ - **cli:** Wire local baton branch before the ACP fork (flag-gated)
29
+ - **cli:** Make the session baton drivable after a hand-off
30
+ - **cli:** Enable the session baton by default for local sessions (CODEAM_BATON=0 kill switch)
31
+ - **shared:** Add baton_state to USER_EVENTS (session baton wire type)
32
+
33
+ ### Changed
34
+
35
+ - **cli:** Extract shared ACP command-context assembler + proxy-relaunch helper
36
+
37
+ ### Fixed
38
+
39
+ - **cli:** Mark SessionDriver.kind readonly (contract)
40
+ - **cli:** Baton switch-failure recovery + ack-on-failure; test cleanups
41
+ - **cli:** EncodeCwd underscore-collapse + ClaudeRuntimeStrategy.resolveHistoryFile (baton mirror)
42
+ - **cli:** AcpClient.start cleans up on handshake failure (baton take-control recovery)
43
+
44
+ ### Tests
45
+
46
+ - **cli:** Prove noteOutput resets the idle window + cover throw/undefined branches
47
+ - **cli:** Gated cross-mode-resume integration test (real claude)
48
+
7
49
  ## [2.59.0] — 2026-07-08
8
50
 
9
51
  ### 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.0" : "0.0.0-dev",
5656
+ cliVersion: true ? "2.60.2" : "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.0",
5837
+ version: "2.60.2",
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.0" ? { ideVersion: "2.60.0" } : {}
6908
+ ..."2.60.2" ? { ideVersion: "2.60.2" } : {}
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.0" : null;
15924
+ const current = true ? "2.60.2" : 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.0" : null;
15941
+ const current = true ? "2.60.2" : 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.0" : null;
15961
+ return true ? "2.60.2" : null;
15962
15962
  }
15963
15963
  function runCmd(cmd, args2, timeoutMs) {
15964
15964
  return new Promise((resolve7) => {
@@ -29110,11 +29110,6 @@ function fetchQuotaUsage(runtime, historySvc) {
29110
29110
  function isLocalSession(env = process.env) {
29111
29111
  return env.CODESPACES !== "true" && env.CODEAM_AUTO_APPROVE !== "1" && env.HEADROOM_ENABLED !== "1" && !env.CODEAM_AUTO_TOKEN && !env.CODEAM_ENROLL_TOKEN;
29112
29112
  }
29113
- function batonEnabled(env = process.env) {
29114
- const v = env.CODEAM_BATON;
29115
- if (v === void 0 || v === "") return true;
29116
- return v !== "0" && v.toLowerCase() !== "false";
29117
- }
29118
29113
 
29119
29114
  // src/baton/baton-controller.ts
29120
29115
  var BatonController = class {
@@ -29415,20 +29410,54 @@ var fs58 = __toESM(require("fs"));
29415
29410
  var TranscriptMirror = class {
29416
29411
  constructor(deps) {
29417
29412
  this.deps = deps;
29413
+ this.pollIntervalMs = deps.pollIntervalMs ?? 750;
29414
+ this.waitTimeoutMs = deps.waitTimeoutMs ?? 10 * 6e4;
29415
+ this.setIntervalFn = deps.setInterval ?? ((fn, ms) => setInterval(fn, ms));
29416
+ this.clearIntervalFn = deps.clearInterval ?? ((handle) => clearInterval(handle));
29418
29417
  }
29419
29418
  deps;
29420
29419
  emitted = 0;
29421
29420
  unwatch = null;
29421
+ pollHandle = null;
29422
+ attached = false;
29423
+ pollIntervalMs;
29424
+ waitTimeoutMs;
29425
+ setIntervalFn;
29426
+ clearIntervalFn;
29422
29427
  start() {
29428
+ if (this.tryAttach()) return;
29429
+ let waited = 0;
29430
+ this.pollHandle = this.setIntervalFn(() => {
29431
+ waited += this.pollIntervalMs;
29432
+ if (this.tryAttach()) {
29433
+ this.clearPoll();
29434
+ return;
29435
+ }
29436
+ if (this.waitTimeoutMs > 0 && waited >= this.waitTimeoutMs) this.clearPoll();
29437
+ }, this.pollIntervalMs);
29438
+ }
29439
+ stop() {
29440
+ this.clearPoll();
29441
+ this.unwatch?.();
29442
+ this.unwatch = null;
29443
+ }
29444
+ /** Resolve the transcript file; if present, emit the current contents and
29445
+ * begin watching. Returns whether it attached. */
29446
+ tryAttach() {
29447
+ if (this.attached) return true;
29423
29448
  const file = this.deps.runtime.resolveHistoryFile?.(this.deps.cwd, this.deps.conversationId);
29424
- if (!file) return;
29449
+ if (!file) return false;
29450
+ this.attached = true;
29425
29451
  this.emit(file);
29426
29452
  const watch2 = this.deps.watch ?? defaultWatch;
29427
29453
  this.unwatch = watch2(file, () => this.emit(file));
29454
+ return true;
29428
29455
  }
29429
- stop() {
29430
- this.unwatch?.();
29431
- this.unwatch = null;
29456
+ clearPoll() {
29457
+ if (this.pollHandle !== null) {
29458
+ this.clearIntervalFn(this.pollHandle);
29459
+ this.pollHandle = null;
29460
+ }
29432
29461
  }
29433
29462
  emit(file) {
29434
29463
  let all;
@@ -29480,6 +29509,47 @@ function makeOnCommand(deps) {
29480
29509
  await deps.dispatchActive(cmd);
29481
29510
  };
29482
29511
  }
29512
+ async function publishMirroredTurnsLive(publisher, messages) {
29513
+ for (const m of messages) {
29514
+ if (m.role === "user") {
29515
+ await publisher.publishOutput({ type: "clear" });
29516
+ await publisher.publishOutput({ type: "user_message", content: m.text, done: true });
29517
+ await publisher.publishOutput({ type: "new_turn", done: false });
29518
+ } else {
29519
+ await publisher.publishOutput({ type: "text", content: m.text, done: true });
29520
+ }
29521
+ }
29522
+ }
29523
+ function makeMirrorOnNewMessages(deps) {
29524
+ let isFirstEmit = true;
29525
+ let publishChain = Promise.resolve();
29526
+ const conversation = [];
29527
+ return (messages) => {
29528
+ const relevant = messages.filter((m) => m.role !== "system");
29529
+ if (relevant.length === 0) return;
29530
+ const mapped = relevant.map((m) => ({
29531
+ id: m.id,
29532
+ role: m.role === "user" ? "user" : "agent",
29533
+ text: m.text,
29534
+ timestamp: toEpochMs(m.timestamp)
29535
+ }));
29536
+ conversation.push(...mapped);
29537
+ void deps.publisher.pushConversation({
29538
+ agentId: deps.agentId,
29539
+ sessionId: deps.conversationId,
29540
+ messages: conversation.slice()
29541
+ });
29542
+ if (deps.fresh || !isFirstEmit) {
29543
+ publishChain = publishChain.then(() => publishMirroredTurnsLive(deps.publisher, relevant)).catch((err) => {
29544
+ log.warn(
29545
+ "wireBaton",
29546
+ `mirror live-publish failed: ${err instanceof Error ? err.message : String(err)}`
29547
+ );
29548
+ });
29549
+ }
29550
+ isFirstEmit = false;
29551
+ };
29552
+ }
29483
29553
  async function runBatonSession(opts) {
29484
29554
  const publisher = new AcpPublisher({
29485
29555
  sessionId: opts.sessionId,
@@ -29559,26 +29629,19 @@ async function runBatonSession(opts) {
29559
29629
  getBeads: opts.getBeads ?? (() => null)
29560
29630
  });
29561
29631
  let mirror = null;
29562
- const startMirror = (conversationId) => {
29632
+ let firstLocalDrive = true;
29633
+ const startMirror = (conversationId, fresh) => {
29563
29634
  mirror?.stop();
29564
29635
  mirror = new TranscriptMirror({
29565
29636
  runtime,
29566
29637
  cwd: opts.cwd,
29567
29638
  conversationId,
29568
- onNewMessages: (messages) => {
29569
- const mapped = messages.filter((m) => m.role !== "system").map((m) => ({
29570
- id: m.id,
29571
- role: m.role === "user" ? "user" : "agent",
29572
- text: m.text,
29573
- timestamp: toEpochMs(m.timestamp)
29574
- }));
29575
- if (mapped.length === 0) return;
29576
- void publisher.pushConversation({
29577
- agentId: opts.agent,
29578
- sessionId: conversationId,
29579
- messages: mapped
29580
- });
29581
- }
29639
+ onNewMessages: makeMirrorOnNewMessages({
29640
+ publisher,
29641
+ agentId: opts.agent,
29642
+ conversationId,
29643
+ fresh
29644
+ })
29582
29645
  });
29583
29646
  mirror.start();
29584
29647
  };
@@ -29594,8 +29657,12 @@ async function runBatonSession(opts) {
29594
29657
  driver,
29595
29658
  conversationId
29596
29659
  });
29597
- if (state === "LOCAL_DRIVE" && conversationId) startMirror(conversationId);
29598
- else if (state !== "LOCAL_DRIVE") mirror?.stop();
29660
+ if (state === "LOCAL_DRIVE" && conversationId) {
29661
+ startMirror(conversationId, firstLocalDrive);
29662
+ firstLocalDrive = false;
29663
+ } else if (state !== "LOCAL_DRIVE") {
29664
+ mirror?.stop();
29665
+ }
29599
29666
  }
29600
29667
  });
29601
29668
  const dispatchActive = (cmd) => controller.activeSessionDriver.dispatch(cmd);
@@ -29786,7 +29853,7 @@ async function start(requestedAgent) {
29786
29853
  `agent-spawn gate released \u2014 beads ${beads ? "ready" : "pending"}; project deps provisioned`
29787
29854
  );
29788
29855
  }
29789
- if (isLocalSession() && batonEnabled() && requiresAcp(session.agent)) {
29856
+ if (isLocalSession() && requiresAcp(session.agent)) {
29790
29857
  const adapter = getAcpAdapter(session.agent);
29791
29858
  if (adapter && session.pluginAuthToken) {
29792
29859
  await runBatonSession({
@@ -32549,7 +32616,7 @@ function checkChokidar() {
32549
32616
  }
32550
32617
  async function doctor(args2 = []) {
32551
32618
  const json = args2.includes("--json");
32552
- const cliVersion = true ? "2.60.0" : "0.0.0-dev";
32619
+ const cliVersion = true ? "2.60.2" : "0.0.0-dev";
32553
32620
  const apiBase2 = resolveApiBaseUrl();
32554
32621
  const diagnosticId = (0, import_node_crypto9.randomUUID)();
32555
32622
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -32748,7 +32815,7 @@ async function completion(args2) {
32748
32815
  // src/commands/version.ts
32749
32816
  var import_picocolors15 = __toESM(require("picocolors"));
32750
32817
  function version2() {
32751
- const v = true ? "2.60.0" : "unknown";
32818
+ const v = true ? "2.60.2" : "unknown";
32752
32819
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
32753
32820
  }
32754
32821
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.60.0",
3
+ "version": "2.60.2",
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",