codeam-cli 2.58.1 → 2.60.0

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 +24 -0
  2. package/dist/index.js +752 -112
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,30 @@ 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.59.0] — 2026-07-08
8
+
9
+ ### Added
10
+
11
+ - **vsc-plugin:** Route stop_task through AgentStrategyRegistry, alias escape_key
12
+ - **vsc-plugin:** CopilotLmStrategy.stop() cancels the active LM stream
13
+ - **jetbrains-plugin:** Alias escape_key to stop_task
14
+ - **jetbrains-plugin:** Best-effort surface-interrupt helper for GUI agents
15
+ - **jetbrains-plugin:** Concrete strategies interrupt their surface on stop()
16
+
17
+ ### Tests
18
+
19
+ - **cli:** Regression-guard stopTaskH cancels ACP turn and acks once
20
+
21
+ ## [2.58.1] — 2026-07-08
22
+
23
+ ### CI
24
+
25
+ - **workflow:** Add win32-native cursor-agent PATH resolution gate
26
+
27
+ ### Fixed
28
+
29
+ - **cli:** Resolve cursor-agent by absolute path on Windows (stale-PATH ENOENT)
30
+
7
31
  ## [2.58.0] — 2026-07-07
8
32
 
9
33
  ### CI
package/dist/index.js CHANGED
@@ -559,7 +559,8 @@ var USER_EVENTS = {
559
559
  AGENT_INSTALL_PROGRESS: "agent_install_progress",
560
560
  AGENT_INSTALL_FAILED: "agent_install_failed",
561
561
  CLI_UPDATE_PROGRESS: "cli_update_progress",
562
- CLI_UPDATE_FAILED: "cli_update_failed"
562
+ CLI_UPDATE_FAILED: "cli_update_failed",
563
+ BATON_STATE: "baton_state"
563
564
  };
564
565
 
565
566
  // ../../packages/shared/src/preview-prompts.ts
@@ -5652,7 +5653,7 @@ function readAnonId() {
5652
5653
  }
5653
5654
  function superProperties() {
5654
5655
  return {
5655
- cliVersion: true ? "2.58.1" : "0.0.0-dev",
5656
+ cliVersion: true ? "2.60.0" : "0.0.0-dev",
5656
5657
  nodeVersion: process.version,
5657
5658
  platform: process.platform,
5658
5659
  arch: process.arch,
@@ -5833,7 +5834,7 @@ var os4 = __toESM(require("os"));
5833
5834
  // package.json
5834
5835
  var package_default = {
5835
5836
  name: "codeam-cli",
5836
- version: "2.58.1",
5837
+ version: "2.60.0",
5837
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.",
5838
5839
  type: "commonjs",
5839
5840
  main: "dist/index.js",
@@ -6114,6 +6115,29 @@ async function postPreviewEvent(input) {
6114
6115
  };
6115
6116
  }
6116
6117
  }
6118
+ async function postBatonEvent(input) {
6119
+ try {
6120
+ await _transport.postJsonAuthed(
6121
+ `${API_BASE}/api/baton/events`,
6122
+ {
6123
+ sessionId: input.sessionId,
6124
+ pluginId: input.pluginId,
6125
+ state: input.state,
6126
+ driver: input.driver,
6127
+ conversationId: input.conversationId
6128
+ },
6129
+ input.pluginAuthToken
6130
+ );
6131
+ return { ok: true };
6132
+ } catch (err) {
6133
+ const e = err;
6134
+ return {
6135
+ ok: false,
6136
+ status: typeof e.statusCode === "number" ? e.statusCode : 0,
6137
+ message: e.message || "unknown"
6138
+ };
6139
+ }
6140
+ }
6117
6141
  async function postHeadroomEvent(input) {
6118
6142
  try {
6119
6143
  await _transport.postJsonAuthed(
@@ -6881,7 +6905,7 @@ var CommandRelayService = class {
6881
6905
  // fresh + clear the "CLI update available" banner after a self-update
6882
6906
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
6883
6907
  // pair/reconnect). Older backends ignore the extra field.
6884
- ..."2.58.1" ? { ideVersion: "2.58.1" } : {}
6908
+ ..."2.60.0" ? { ideVersion: "2.60.0" } : {}
6885
6909
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
6886
6910
  }
6887
6911
  /**
@@ -12097,7 +12121,7 @@ var fs16 = __toESM(require("fs"));
12097
12121
  var path19 = __toESM(require("path"));
12098
12122
  var os14 = __toESM(require("os"));
12099
12123
  function encodeCwd(cwd) {
12100
- return cwd.replace(/[\\/:]/g, "-");
12124
+ return cwd.replace(/[\\/:_]/g, "-");
12101
12125
  }
12102
12126
  function resolveHistoryDir(cwd, projectsRoot) {
12103
12127
  const root = projectsRoot ?? path19.join(os14.homedir(), ".claude", "projects");
@@ -12117,6 +12141,12 @@ function resolveHistoryDir(cwd, projectsRoot) {
12117
12141
  }
12118
12142
  return null;
12119
12143
  }
12144
+ function resolveHistoryFile(cwd, sessionId, projectsRoot) {
12145
+ const dir = resolveHistoryDir(cwd, projectsRoot);
12146
+ if (!dir) return null;
12147
+ const filePath = path19.join(dir, `${sessionId}.jsonl`);
12148
+ return fs16.existsSync(filePath) ? filePath : null;
12149
+ }
12120
12150
  function getCurrentUsage(historyDir, bootTimeMs = 0) {
12121
12151
  const GRACE_MS = 5e3;
12122
12152
  const cutoff = bootTimeMs > 0 ? bootTimeMs - GRACE_MS : 0;
@@ -12350,6 +12380,9 @@ var ClaudeRuntimeStrategy = class {
12350
12380
  resolveHistoryDir(cwd) {
12351
12381
  return resolveHistoryDir(cwd);
12352
12382
  }
12383
+ resolveHistoryFile(cwd, sessionId) {
12384
+ return resolveHistoryFile(cwd, sessionId);
12385
+ }
12353
12386
  parseHistoryFile(filePath) {
12354
12387
  return parseHistoryFile(filePath);
12355
12388
  }
@@ -12794,7 +12827,7 @@ function listResumableSessions2(cwd, homeOverride) {
12794
12827
  out2.sort((a, b) => b.timestamp - a.timestamp);
12795
12828
  return out2;
12796
12829
  }
12797
- function resolveHistoryFile(cwd, sessionId, homeOverride) {
12830
+ function resolveHistoryFile2(cwd, sessionId, homeOverride) {
12798
12831
  const home = homeOverride ?? import_node_os.default.homedir();
12799
12832
  const sessionsRoot = import_node_path2.default.join(home, ".codex", "sessions");
12800
12833
  if (!import_node_fs3.default.existsSync(sessionsRoot)) return null;
@@ -13017,7 +13050,7 @@ var CodexRuntimeStrategy = class {
13017
13050
  return parseHistoryFile2(filePath);
13018
13051
  }
13019
13052
  resolveHistoryFile(cwd, sessionId) {
13020
- return resolveHistoryFile(cwd, sessionId);
13053
+ return resolveHistoryFile2(cwd, sessionId);
13021
13054
  }
13022
13055
  getCurrentUsage(historyDir) {
13023
13056
  return getCurrentUsage2(historyDir);
@@ -15888,7 +15921,7 @@ async function autoUpgradeBeforeCriticalCommand() {
15888
15921
  if (process.env.NODE_ENV === "test") return;
15889
15922
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
15890
15923
  if (process.env.CI) return;
15891
- const current = true ? "2.58.1" : null;
15924
+ const current = true ? "2.60.0" : null;
15892
15925
  if (!current) return;
15893
15926
  const cache = readCache();
15894
15927
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -15905,7 +15938,7 @@ function checkForUpdates() {
15905
15938
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
15906
15939
  if (process.env.CI) return;
15907
15940
  if (!process.stdout.isTTY) return;
15908
- const current = true ? "2.58.1" : null;
15941
+ const current = true ? "2.60.0" : null;
15909
15942
  if (!current) return;
15910
15943
  const cache = readCache();
15911
15944
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -15925,7 +15958,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
15925
15958
  var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
15926
15959
  var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
15927
15960
  function currentCliVersion() {
15928
- return true ? "2.58.1" : null;
15961
+ return true ? "2.60.0" : null;
15929
15962
  }
15930
15963
  function runCmd(cmd, args2, timeoutMs) {
15931
15964
  return new Promise((resolve7) => {
@@ -19663,6 +19696,32 @@ var envWriteH = async (ctx, cmd, parsed) => {
19663
19696
  await ctx.relay.sendResult(cmd.id, "failed", { error: err.message });
19664
19697
  }
19665
19698
  };
19699
+ var takeControlH = async (ctx, cmd) => {
19700
+ if (!ctx.baton) {
19701
+ await ctx.relay.sendResult(cmd.id, "failed", { code: "NO_BATON" });
19702
+ return;
19703
+ }
19704
+ try {
19705
+ await ctx.baton.takeControl();
19706
+ } catch {
19707
+ await ctx.relay.sendResult(cmd.id, "failed", { code: "BATON_SWITCH_FAILED" });
19708
+ return;
19709
+ }
19710
+ await ctx.relay.sendResult(cmd.id, "completed", { state: ctx.baton.state });
19711
+ };
19712
+ var handbackH = async (ctx, cmd) => {
19713
+ if (!ctx.baton) {
19714
+ await ctx.relay.sendResult(cmd.id, "failed", { code: "NO_BATON" });
19715
+ return;
19716
+ }
19717
+ try {
19718
+ await ctx.baton.handback();
19719
+ } catch {
19720
+ await ctx.relay.sendResult(cmd.id, "failed", { code: "BATON_SWITCH_FAILED" });
19721
+ return;
19722
+ }
19723
+ await ctx.relay.sendResult(cmd.id, "completed", { state: ctx.baton.state });
19724
+ };
19666
19725
  var _activeReporter = null;
19667
19726
  var _headroomEmitChain = Promise.resolve();
19668
19727
  var headroomConfigureH = async (ctx, cmd, parsed) => {
@@ -20503,6 +20562,8 @@ var handlers = {
20503
20562
  save_preview_config: savePreviewConfigH,
20504
20563
  env_read: envReadH,
20505
20564
  env_write: envWriteH,
20565
+ take_control: takeControlH,
20566
+ handback: handbackH,
20506
20567
  headroom_configure: headroomConfigureH,
20507
20568
  headroom_budget: headroomBudgetH,
20508
20569
  beads_configure: beadsConfigureH,
@@ -24816,62 +24877,85 @@ var AcpClient = class {
24816
24877
  this.opts.onStderr?.(`Failed to launch ${adapter.command}: ${code}${hint}`);
24817
24878
  this.opts.onUnexpectedExit?.(null, null);
24818
24879
  });
24819
- if (!child.stdin || !child.stdout) {
24820
- throw new Error("Spawned ACP adapter is missing stdio handles");
24821
- }
24822
- const input = import_node_stream.Readable.toWeb(child.stdout);
24823
- const output = import_node_stream.Writable.toWeb(child.stdin);
24824
- const stream = ndJsonStream(output, input);
24825
- this.connection = new ClientSideConnection(
24826
- (_agent) => this.buildClient(),
24827
- stream
24828
- );
24829
- log.info("acpClient", "initialize \u2192 sending");
24830
- const initialize = await this.connection.initialize({
24831
- protocolVersion: PROTOCOL_VERSION2,
24832
- clientCapabilities: CLIENT_CAPABILITIES
24833
- });
24834
- log.info(
24835
- "acpClient",
24836
- `initialize \u2190 ok protocolVersion=${initialize.protocolVersion} agentCaps=${JSON.stringify(initialize.agentCapabilities ?? {}).slice(0, 200)}`
24837
- );
24838
- log.info("acpClient", "newSession \u2192 sending");
24839
- let startupTimer;
24840
- const startupFailure = new Promise((_2, reject) => {
24841
- this.startupFailureReject = reject;
24842
- startupTimer = setTimeout(() => {
24843
- const tail = this.recentStderr.slice(-4).join(" | ");
24844
- reject(
24845
- new Error(
24846
- `AGENT_STARTUP_TIMEOUT: ${this.opts.adapter.requiresAgentBinary} did not create a session within ${Math.round(
24847
- NEWSESSION_TIMEOUT_MS / 1e3
24848
- )}s${tail ? ` \u2014 last output: ${tail}` : ""}`
24849
- )
24850
- );
24851
- }, NEWSESSION_TIMEOUT_MS);
24852
- });
24853
- let newSession;
24854
24880
  try {
24855
- newSession = await Promise.race([
24856
- this.connection.newSession({ cwd, mcpServers: [] }),
24857
- startupFailure
24858
- ]);
24859
- } finally {
24860
- if (startupTimer) clearTimeout(startupTimer);
24861
- this.startupFailureReject = null;
24881
+ if (!child.stdin || !child.stdout) {
24882
+ throw new Error("Spawned ACP adapter is missing stdio handles");
24883
+ }
24884
+ const input = import_node_stream.Readable.toWeb(child.stdout);
24885
+ const output = import_node_stream.Writable.toWeb(child.stdin);
24886
+ const stream = ndJsonStream(output, input);
24887
+ this.connection = new ClientSideConnection(
24888
+ (_agent) => this.buildClient(),
24889
+ stream
24890
+ );
24891
+ log.info("acpClient", "initialize \u2192 sending");
24892
+ const initialize = await this.connection.initialize({
24893
+ protocolVersion: PROTOCOL_VERSION2,
24894
+ clientCapabilities: CLIENT_CAPABILITIES
24895
+ });
24896
+ log.info(
24897
+ "acpClient",
24898
+ `initialize \u2190 ok protocolVersion=${initialize.protocolVersion} agentCaps=${JSON.stringify(initialize.agentCapabilities ?? {}).slice(0, 200)}`
24899
+ );
24900
+ log.info("acpClient", "newSession \u2192 sending");
24901
+ let startupTimer;
24902
+ const startupFailure = new Promise((_2, reject) => {
24903
+ this.startupFailureReject = reject;
24904
+ startupTimer = setTimeout(() => {
24905
+ const tail = this.recentStderr.slice(-4).join(" | ");
24906
+ reject(
24907
+ new Error(
24908
+ `AGENT_STARTUP_TIMEOUT: ${this.opts.adapter.requiresAgentBinary} did not create a session within ${Math.round(
24909
+ NEWSESSION_TIMEOUT_MS / 1e3
24910
+ )}s${tail ? ` \u2014 last output: ${tail}` : ""}`
24911
+ )
24912
+ );
24913
+ }, NEWSESSION_TIMEOUT_MS);
24914
+ });
24915
+ let newSession;
24916
+ try {
24917
+ newSession = await Promise.race([
24918
+ this.connection.newSession({ cwd, mcpServers: [] }),
24919
+ startupFailure
24920
+ ]);
24921
+ } finally {
24922
+ if (startupTimer) clearTimeout(startupTimer);
24923
+ this.startupFailureReject = null;
24924
+ }
24925
+ this.sessionId = newSession.sessionId;
24926
+ const newSessionMeta = newSession;
24927
+ log.info(
24928
+ "acpClient",
24929
+ `newSession \u2190 ok sessionId=${newSession.sessionId.slice(0, 8)} model=${newSessionMeta.currentModelId ?? "?"} tier=${newSessionMeta.currentServiceTier ?? "?"}`
24930
+ );
24931
+ return {
24932
+ sessionId: newSession.sessionId,
24933
+ initialize,
24934
+ model: newSessionMeta.currentModelId,
24935
+ tier: newSessionMeta.currentServiceTier
24936
+ };
24937
+ } catch (err) {
24938
+ this.cleanupAfterFailedStart(child);
24939
+ throw err;
24862
24940
  }
24863
- this.sessionId = newSession.sessionId;
24864
- const newSessionMeta = newSession;
24865
- log.info(
24866
- "acpClient",
24867
- `newSession \u2190 ok sessionId=${newSession.sessionId.slice(0, 8)} model=${newSessionMeta.currentModelId ?? "?"} tier=${newSessionMeta.currentServiceTier ?? "?"}`
24868
- );
24869
- return {
24870
- sessionId: newSession.sessionId,
24871
- initialize,
24872
- model: newSessionMeta.currentModelId,
24873
- tier: newSessionMeta.currentServiceTier
24874
- };
24941
+ }
24942
+ /**
24943
+ * Undo the partial state `start()` set before its handshake threw, so a
24944
+ * fresh `start()` call is retryable instead of permanently hard-throwing
24945
+ * 'AcpClient already started'. Removes OUR `exit`/`error` listeners first
24946
+ * so killing the half-started child doesn't also fire
24947
+ * `onUnexpectedExit` — the baton's `onUnexpectedExit` calls
24948
+ * `process.exit`, which must never fire for a failure the caller already
24949
+ * received as a thrown error from `start()`.
24950
+ */
24951
+ cleanupAfterFailedStart(child) {
24952
+ child.removeAllListeners("exit");
24953
+ child.removeAllListeners("error");
24954
+ killQuiet(child, "SIGKILL");
24955
+ this.child = null;
24956
+ this.connection = null;
24957
+ this.sessionId = null;
24958
+ this.startupFailureReject = null;
24875
24959
  }
24876
24960
  /**
24877
24961
  * Send a user prompt to the active session. Returns the
@@ -25177,6 +25261,38 @@ function expandPathForAgentBinaries(existingPath) {
25177
25261
  return [...additions, existingPath].filter((p2) => p2.length > 0).join(path58.delimiter);
25178
25262
  }
25179
25263
 
25264
+ // src/agents/acp/headroom-budget-proxy.ts
25265
+ function buildRelaunchProxyEnv(baseEnv) {
25266
+ const env = { ...baseEnv, HEADROOM_KOMPRESS_BACKEND: "onnx_cpu" };
25267
+ delete env["HEADROOM_BUDGET"];
25268
+ delete env["HEADROOM_BUDGET_PERIOD"];
25269
+ return env;
25270
+ }
25271
+ var relaunchProxyWithoutBudget = async () => {
25272
+ const { spawn: spawn38 } = await import("child_process");
25273
+ killHeadroomProxy();
25274
+ await new Promise((r) => setTimeout(r, 500));
25275
+ const proxyEnv = buildRelaunchProxyEnv(process.env);
25276
+ try {
25277
+ const proxy = spawn38(
25278
+ "headroom",
25279
+ ["proxy", "--port", "8787"],
25280
+ { stdio: "ignore", detached: true, env: proxyEnv }
25281
+ );
25282
+ proxy.once("error", (e) => {
25283
+ log.warn("acpRunner", `budget recovery proxy relaunch error (best-effort): ${e.message}`);
25284
+ });
25285
+ proxy.unref();
25286
+ writeHeadroomProxyPidfile(proxy.pid);
25287
+ } catch (e) {
25288
+ log.warn(
25289
+ "acpRunner",
25290
+ `budget recovery proxy relaunch failed (best-effort): ${e instanceof Error ? e.message : String(e)}`
25291
+ );
25292
+ }
25293
+ await new Promise((r) => setTimeout(r, 3e3));
25294
+ };
25295
+
25180
25296
  // src/services/streaming/transport.ts
25181
25297
  var http7 = __toESM(require("http"));
25182
25298
  var https8 = __toESM(require("https"));
@@ -26063,7 +26179,7 @@ function defaultRunGit(cwd, args2) {
26063
26179
  });
26064
26180
  }
26065
26181
  async function discoverRepos(workingDir, maxDepth = 4) {
26066
- const fs60 = await import("fs/promises");
26182
+ const fs61 = await import("fs/promises");
26067
26183
  const out2 = [];
26068
26184
  await walk(workingDir, 0);
26069
26185
  return out2;
@@ -26071,7 +26187,7 @@ async function discoverRepos(workingDir, maxDepth = 4) {
26071
26187
  if (depth > maxDepth) return;
26072
26188
  let entries = [];
26073
26189
  try {
26074
- const dirents = await fs60.readdir(dir, { withFileTypes: true });
26190
+ const dirents = await fs61.readdir(dir, { withFileTypes: true });
26075
26191
  entries = dirents.filter((d3) => !d3.name.startsWith(".") || d3.name === ".git").map((d3) => ({ name: d3.name, isDirectory: d3.isDirectory() }));
26076
26192
  } catch {
26077
26193
  return;
@@ -26710,6 +26826,9 @@ function formatAgentReplyLine(text) {
26710
26826
  }
26711
26827
 
26712
26828
  // src/agents/acp/command-handlers.ts
26829
+ function assembleAcpCommandContext(session, cmd) {
26830
+ return { ...session, cmd };
26831
+ }
26713
26832
  var ACP_QUICK_REPLIES = ["Continue", "Yes, go ahead", "Explain"];
26714
26833
  async function recoverFromFailedTurn(client2, streaming) {
26715
26834
  await cancelStuckTurn(client2);
@@ -27680,12 +27799,6 @@ async function surfaceStartupFailure(opts) {
27680
27799
  );
27681
27800
  errRelay.start();
27682
27801
  }
27683
- function buildRelaunchProxyEnv(baseEnv) {
27684
- const env = { ...baseEnv, HEADROOM_KOMPRESS_BACKEND: "onnx_cpu" };
27685
- delete env["HEADROOM_BUDGET"];
27686
- delete env["HEADROOM_BUDGET_PERIOD"];
27687
- return env;
27688
- }
27689
27802
  function computeAdapterExtraEnv(params) {
27690
27803
  const env = {};
27691
27804
  if (params.disable1mContext) env.CLAUDE_CODE_DISABLE_1M_CONTEXT = "1";
@@ -27802,30 +27915,6 @@ async function runAcpSession(opts) {
27802
27915
  };
27803
27916
  const client2 = new AcpClient(clientOptions);
27804
27917
  let _budgetReachedPosted = false;
27805
- const relaunchProxyWithoutBudget = async () => {
27806
- const { spawn: spawn38 } = await import("child_process");
27807
- killHeadroomProxy();
27808
- await new Promise((r) => setTimeout(r, 500));
27809
- const proxyEnv = buildRelaunchProxyEnv(process.env);
27810
- try {
27811
- const proxy = spawn38(
27812
- "headroom",
27813
- ["proxy", "--port", "8787"],
27814
- { stdio: "ignore", detached: true, env: proxyEnv }
27815
- );
27816
- proxy.once("error", (e) => {
27817
- log.warn("acpRunner", `budget recovery proxy relaunch error (best-effort): ${e.message}`);
27818
- });
27819
- proxy.unref();
27820
- writeHeadroomProxyPidfile(proxy.pid);
27821
- } catch (e) {
27822
- log.warn(
27823
- "acpRunner",
27824
- `budget recovery proxy relaunch failed (best-effort): ${e instanceof Error ? e.message : String(e)}`
27825
- );
27826
- }
27827
- await new Promise((r) => setTimeout(r, 3e3));
27828
- };
27829
27918
  const budgetRecovery = createBudgetRecovery({
27830
27919
  publishText: (text) => publisher.publishOutput({ type: "text", content: text, done: true }),
27831
27920
  publishSelectPrompt: (question, options) => publisher.publishOutput({
@@ -27993,8 +28082,7 @@ async function runAcpSession(opts) {
27993
28082
  });
27994
28083
  }
27995
28084
  async function handleCommand(cmd, client2, relay, acpSessionId, models, streaming, opts, history, jsonlHistory, agentCaps, turnFiles, getBeads, publisher, recentStderr, budgetRecovery, budgetReachedFlag) {
27996
- await dispatchAcpCommand({
27997
- cmd,
28085
+ const session = {
27998
28086
  client: client2,
27999
28087
  relay,
28000
28088
  acpSessionId,
@@ -28010,7 +28098,8 @@ async function handleCommand(cmd, client2, relay, acpSessionId, models, streamin
28010
28098
  recentStderr,
28011
28099
  budgetRecovery,
28012
28100
  budgetReachedFlag
28013
- });
28101
+ };
28102
+ await dispatchAcpCommand(assembleAcpCommandContext(session, cmd));
28014
28103
  }
28015
28104
  function buildBannerSubtitle(agentId, acpSessionId, model, tier) {
28016
28105
  const meta = AGENT_REGISTRY[agentId];
@@ -29017,8 +29106,543 @@ function fetchQuotaUsage(runtime, historySvc) {
29017
29106
  });
29018
29107
  }
29019
29108
 
29020
- // src/agents/claude/onboarding.ts
29109
+ // src/baton/gate.ts
29110
+ function isLocalSession(env = process.env) {
29111
+ return env.CODESPACES !== "true" && env.CODEAM_AUTO_APPROVE !== "1" && env.HEADROOM_ENABLED !== "1" && !env.CODEAM_AUTO_TOKEN && !env.CODEAM_ENROLL_TOKEN;
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
+
29119
+ // src/baton/baton-controller.ts
29120
+ var BatonController = class {
29121
+ constructor(deps) {
29122
+ this.deps = deps;
29123
+ }
29124
+ deps;
29125
+ _state = "LOCAL_DRIVE";
29126
+ _active = "local_tui";
29127
+ _conversationId = null;
29128
+ get state() {
29129
+ return this._state;
29130
+ }
29131
+ get activeDriver() {
29132
+ return this._active;
29133
+ }
29134
+ /** The driver object currently holding the baton. The baton router forwards
29135
+ * non-baton commands to `activeSessionDriver.dispatch(cmd)`, so whichever side
29136
+ * holds the baton is the one that actually runs the command. Reads `_active`
29137
+ * live, so mid-turn commands during a `SWITCHING` window still target the
29138
+ * pre-switch driver (the switch waits for the turn boundary before flipping). */
29139
+ get activeSessionDriver() {
29140
+ return this._active === "local_tui" ? this.deps.local : this.deps.mobile;
29141
+ }
29142
+ get conversationId() {
29143
+ return this._conversationId;
29144
+ }
29145
+ async begin() {
29146
+ this._conversationId = await this.deps.local.start(void 0);
29147
+ this._active = "local_tui";
29148
+ this.setState("LOCAL_DRIVE");
29149
+ }
29150
+ async takeControl() {
29151
+ await this.switchDriver(
29152
+ "LOCAL_DRIVE",
29153
+ this.deps.local,
29154
+ this.deps.mobile,
29155
+ "MOBILE_DRIVE",
29156
+ "mobile_acp"
29157
+ );
29158
+ }
29159
+ async handback() {
29160
+ await this.switchDriver(
29161
+ "MOBILE_DRIVE",
29162
+ this.deps.mobile,
29163
+ this.deps.local,
29164
+ "LOCAL_DRIVE",
29165
+ "local_tui"
29166
+ );
29167
+ }
29168
+ async shutdown() {
29169
+ await Promise.allSettled([this.deps.local.stop(), this.deps.mobile.stop()]);
29170
+ }
29171
+ async switchDriver(from, current, next, to, nextKind) {
29172
+ if (this._state !== from) return;
29173
+ this.setState("SWITCHING");
29174
+ const priorActive = this._active;
29175
+ const priorConversationId = this._conversationId;
29176
+ try {
29177
+ await current.whenSafeToYield();
29178
+ await current.stop();
29179
+ this._conversationId = await next.start(this._conversationId ?? void 0);
29180
+ this._active = nextKind;
29181
+ this.setState(to);
29182
+ } catch (err) {
29183
+ this._active = priorActive;
29184
+ this._conversationId = priorConversationId;
29185
+ this.setState(from);
29186
+ throw err;
29187
+ }
29188
+ }
29189
+ setState(state) {
29190
+ this._state = state;
29191
+ this.deps.publishState(state, this._active, this._conversationId);
29192
+ }
29193
+ };
29194
+
29195
+ // src/baton/native-tui-driver.ts
29196
+ var NativeTuiDriver = class {
29197
+ constructor(deps) {
29198
+ this.deps = deps;
29199
+ this.agent = deps.agent;
29200
+ this.idleMs = deps.idleMs ?? 750;
29201
+ this.now = deps.now ?? Date.now;
29202
+ this.lastOutput = this.now();
29203
+ this.historySvc = new HistoryService(deps.runtime, deps.opts.pluginId, deps.opts.cwd, {
29204
+ pluginAuthToken: deps.opts.pluginAuthToken
29205
+ });
29206
+ this.outputSvc = new OutputService(
29207
+ deps.opts.sessionId,
29208
+ deps.opts.pluginId,
29209
+ (conversationId) => this.historySvc.setCurrentConversationId(conversationId),
29210
+ (reset) => this.historySvc.setRateLimitReset(reset),
29211
+ void 0,
29212
+ void 0,
29213
+ deps.opts.pluginAuthToken,
29214
+ deps.runtime
29215
+ );
29216
+ this.keepAliveCtx = { inCodespace: false, codespaceName: void 0 };
29217
+ this.setKeepAlive = buildKeepAlive(this.keepAliveCtx).apply;
29218
+ }
29219
+ deps;
29220
+ kind = "local_tui";
29221
+ agent;
29222
+ idleMs;
29223
+ now;
29224
+ lastOutput;
29225
+ outputSvc;
29226
+ historySvc;
29227
+ setKeepAlive;
29228
+ keepAliveCtx;
29229
+ async start(resumeId) {
29230
+ if (resumeId !== void 0) {
29231
+ await this.agent.restart(resumeId, false);
29232
+ return resumeId;
29233
+ }
29234
+ await this.agent.spawn();
29235
+ const id = this.agent.spawnedSessionId;
29236
+ if (!id) throw new Error("NativeTuiDriver: agent did not expose a session id after spawn");
29237
+ return id;
29238
+ }
29239
+ async stop() {
29240
+ this.agent.kill();
29241
+ }
29242
+ async dispatch(cmd) {
29243
+ const ctx = {
29244
+ outputSvc: this.outputSvc,
29245
+ agent: this.agent,
29246
+ historySvc: this.historySvc,
29247
+ runtime: this.deps.runtime,
29248
+ relay: this.deps.getRelay(),
29249
+ setKeepAlive: this.setKeepAlive,
29250
+ keepAliveCtx: this.keepAliveCtx,
29251
+ pluginId: this.deps.opts.pluginId,
29252
+ sessionId: this.deps.opts.sessionId,
29253
+ agentId: this.deps.opts.agentId,
29254
+ pluginAuthToken: this.deps.opts.pluginAuthToken,
29255
+ beads: this.deps.getBeads()
29256
+ };
29257
+ await dispatchCommand(ctx, cmd);
29258
+ }
29259
+ /** Call on every PTY data chunk: reset the idle timer AND feed the output
29260
+ * pipe so a mobile-routed turn's reply streams back to the app. */
29261
+ handlePtyData(raw) {
29262
+ this.noteOutput();
29263
+ this.outputSvc.push(raw);
29264
+ }
29265
+ /** Reset the idle timer only. Retained for callers that just need the boundary
29266
+ * clock nudged without routing bytes through the output pipe. */
29267
+ noteOutput() {
29268
+ this.lastOutput = this.now();
29269
+ }
29270
+ whenSafeToYield() {
29271
+ return new Promise((resolve7) => {
29272
+ const tick = () => {
29273
+ const quietFor = this.now() - this.lastOutput;
29274
+ if (quietFor >= this.idleMs) resolve7();
29275
+ else setTimeout(tick, this.idleMs - quietFor);
29276
+ };
29277
+ tick();
29278
+ });
29279
+ }
29280
+ };
29281
+
29282
+ // src/baton/acp-driver.ts
29283
+ var import_node_crypto8 = require("crypto");
29284
+ var AcpDriver = class {
29285
+ constructor(deps) {
29286
+ this.deps = deps;
29287
+ }
29288
+ deps;
29289
+ kind = "mobile_acp";
29290
+ turnActive = false;
29291
+ waiters = [];
29292
+ /** Set once the adapter has handshaked (in {@link start}); the shared
29293
+ * conversation id (resumed id, or the freshly minted one). */
29294
+ acpSessionId = null;
29295
+ agentCaps;
29296
+ /** Lazily-built, memoised per {@link start} — cleared when the conversation
29297
+ * changes so history/models rebind to the current conversation. */
29298
+ session = null;
29299
+ budgetReachedPosted = false;
29300
+ async start(resumeId) {
29301
+ let started;
29302
+ try {
29303
+ started = await this.deps.client.start();
29304
+ if (resumeId !== void 0) {
29305
+ await this.deps.client.loadSession(resumeId);
29306
+ }
29307
+ } catch (err) {
29308
+ await this.deps.client.stop().catch(() => void 0);
29309
+ throw err;
29310
+ }
29311
+ const conversationId = resumeId !== void 0 ? resumeId : started.sessionId;
29312
+ this.acpSessionId = conversationId;
29313
+ this.agentCaps = started.initialize.agentCapabilities;
29314
+ this.session = null;
29315
+ this.budgetReachedPosted = false;
29316
+ return conversationId;
29317
+ }
29318
+ async stop() {
29319
+ await this.deps.client.stop();
29320
+ this.acpSessionId = null;
29321
+ this.session = null;
29322
+ }
29323
+ async dispatch(cmd) {
29324
+ const relay = this.deps.getRelay();
29325
+ const acpSessionId = this.acpSessionId;
29326
+ if (acpSessionId === null) {
29327
+ await relay.sendResult(cmd.id, "failed", { code: "BATON_MOBILE_NOT_STARTED" });
29328
+ return;
29329
+ }
29330
+ const session = await this.ensureSession(relay, acpSessionId, this.agentCaps);
29331
+ this.beginTurn();
29332
+ try {
29333
+ await dispatchAcpCommand(assembleAcpCommandContext(session, cmd));
29334
+ } finally {
29335
+ this.endTurn();
29336
+ }
29337
+ }
29338
+ /** Build (once per conversation) the session-scoped ACP context — the same
29339
+ * shape `runAcpSession` assembles. Memoised on {@link session}. */
29340
+ async ensureSession(relay, acpSessionId, agentCaps) {
29341
+ if (this.session) return this.session;
29342
+ const { opts, publisher, streaming, runtime, recentStderr } = this.deps;
29343
+ const models = await runtime.listModels();
29344
+ const jsonlHistory = new HistoryService(runtime, opts.pluginId, opts.cwd, {
29345
+ pluginAuthToken: opts.pluginAuthToken
29346
+ });
29347
+ const history = new AcpHistory(publisher, { agent: opts.agent, acpSessionId });
29348
+ const turnFiles = new TurnFileAggregator({
29349
+ workingDir: opts.cwd,
29350
+ sessionId: opts.sessionId,
29351
+ pluginId: opts.pluginId,
29352
+ pluginAuthToken: opts.pluginAuthToken,
29353
+ agentId: opts.agent
29354
+ });
29355
+ const budgetReachedFlag = {
29356
+ get: () => this.budgetReachedPosted,
29357
+ set: (v) => {
29358
+ this.budgetReachedPosted = v;
29359
+ }
29360
+ };
29361
+ const budgetRecovery = createBudgetRecovery({
29362
+ publishText: (text) => publisher.publishOutput({ type: "text", content: text, done: true }),
29363
+ publishSelectPrompt: (question, options) => publisher.publishOutput({
29364
+ type: "select_prompt",
29365
+ content: question,
29366
+ options,
29367
+ optionDescriptions: options.map(() => ""),
29368
+ currentIndex: 0,
29369
+ done: true
29370
+ }),
29371
+ publishAwaitingAnswer: (prompt, options) => publisher.publishAwaitingAnswer({ questionId: (0, import_node_crypto8.randomUUID)(), prompt, options }),
29372
+ publishRawChunk: (chunk) => publisher.publishOutput(chunk),
29373
+ sendResult: (commandId, status2, result) => relay.sendResult(commandId, status2, result),
29374
+ appendAgentReply: (text) => history.appendAgentReply(text),
29375
+ flushHistory: () => void history.flush(),
29376
+ relaunchProxyWithoutBudget,
29377
+ agentId: opts.agent
29378
+ });
29379
+ this.session = {
29380
+ client: this.deps.client,
29381
+ relay,
29382
+ acpSessionId,
29383
+ models,
29384
+ streaming,
29385
+ opts,
29386
+ history,
29387
+ jsonlHistory,
29388
+ agentCaps,
29389
+ turnFiles,
29390
+ getBeads: opts.getBeads ?? (() => null),
29391
+ publisher,
29392
+ recentStderr,
29393
+ budgetRecovery,
29394
+ budgetReachedFlag
29395
+ };
29396
+ return this.session;
29397
+ }
29398
+ beginTurn() {
29399
+ this.turnActive = true;
29400
+ }
29401
+ endTurn() {
29402
+ this.turnActive = false;
29403
+ const waiters = this.waiters;
29404
+ this.waiters = [];
29405
+ waiters.forEach((resolve7) => resolve7());
29406
+ }
29407
+ whenSafeToYield() {
29408
+ if (!this.turnActive) return Promise.resolve();
29409
+ return new Promise((resolve7) => this.waiters.push(resolve7));
29410
+ }
29411
+ };
29412
+
29413
+ // src/baton/transcript-mirror.ts
29021
29414
  var fs58 = __toESM(require("fs"));
29415
+ var TranscriptMirror = class {
29416
+ constructor(deps) {
29417
+ this.deps = deps;
29418
+ }
29419
+ deps;
29420
+ emitted = 0;
29421
+ unwatch = null;
29422
+ start() {
29423
+ const file = this.deps.runtime.resolveHistoryFile?.(this.deps.cwd, this.deps.conversationId);
29424
+ if (!file) return;
29425
+ this.emit(file);
29426
+ const watch2 = this.deps.watch ?? defaultWatch;
29427
+ this.unwatch = watch2(file, () => this.emit(file));
29428
+ }
29429
+ stop() {
29430
+ this.unwatch?.();
29431
+ this.unwatch = null;
29432
+ }
29433
+ emit(file) {
29434
+ let all;
29435
+ try {
29436
+ all = this.deps.runtime.parseHistoryFile(file);
29437
+ } catch {
29438
+ return;
29439
+ }
29440
+ if (all.length <= this.emitted) return;
29441
+ const delta = all.slice(this.emitted);
29442
+ this.emitted = all.length;
29443
+ this.deps.onNewMessages(delta);
29444
+ }
29445
+ };
29446
+ function defaultWatch(file, onChange) {
29447
+ const w3 = fs58.watch(file, { persistent: false }, () => onChange());
29448
+ return () => w3.close();
29449
+ }
29450
+
29451
+ // src/baton/wire-baton.ts
29452
+ function makeOnCommand(deps) {
29453
+ return async function onCommand(cmd) {
29454
+ if (cmd.type === "take_control") {
29455
+ try {
29456
+ await deps.controller.takeControl();
29457
+ } catch (err) {
29458
+ await deps.ack(cmd.id, "failed", {
29459
+ code: "BATON_SWITCH_FAILED",
29460
+ message: err instanceof Error ? err.message : String(err)
29461
+ });
29462
+ return;
29463
+ }
29464
+ await deps.ack(cmd.id, "completed", { state: deps.controller.state });
29465
+ return;
29466
+ }
29467
+ if (cmd.type === "handback") {
29468
+ try {
29469
+ await deps.controller.handback();
29470
+ } catch (err) {
29471
+ await deps.ack(cmd.id, "failed", {
29472
+ code: "BATON_SWITCH_FAILED",
29473
+ message: err instanceof Error ? err.message : String(err)
29474
+ });
29475
+ return;
29476
+ }
29477
+ await deps.ack(cmd.id, "completed", { state: deps.controller.state });
29478
+ return;
29479
+ }
29480
+ await deps.dispatchActive(cmd);
29481
+ };
29482
+ }
29483
+ async function runBatonSession(opts) {
29484
+ const publisher = new AcpPublisher({
29485
+ sessionId: opts.sessionId,
29486
+ pluginId: opts.pluginId,
29487
+ pluginAuthToken: opts.pluginAuthToken,
29488
+ refreshAuthToken: () => fetchCurrentPluginAuthToken(opts.sessionId, opts.pluginId, opts.pollSecret)
29489
+ });
29490
+ const runtime = createRuntimeStrategy(opts.agent);
29491
+ const streaming = new StreamingState(publisher);
29492
+ const recentStderr = [];
29493
+ const client2 = new AcpClient({
29494
+ adapter: opts.adapter,
29495
+ cwd: opts.cwd,
29496
+ onSessionUpdate: (notification) => {
29497
+ for (const delta of mapSessionUpdate(notification)) streaming.append(delta);
29498
+ },
29499
+ onRequestPermission: async (request) => {
29500
+ const { event, optionIdByLabel } = mapPermissionRequest(request);
29501
+ await publisher.publishAwaitingAnswer(event);
29502
+ return streaming.registerPermission({
29503
+ questionId: event.questionId,
29504
+ labels: event.options ?? [],
29505
+ optionIdByLabel
29506
+ });
29507
+ },
29508
+ onStderr: (line) => {
29509
+ recentStderr.push(line);
29510
+ if (recentStderr.length > 40) recentStderr.shift();
29511
+ },
29512
+ onUnexpectedExit: (code, signal) => {
29513
+ log.warn("baton", `ACP adapter exited code=${code} signal=${signal}; flushing`);
29514
+ void streaming.closeAll().finally(() => process.exit(code ?? 1));
29515
+ }
29516
+ });
29517
+ let relay;
29518
+ const acpOpts = {
29519
+ agent: opts.agent,
29520
+ sessionId: opts.sessionId,
29521
+ pluginId: opts.pluginId,
29522
+ pluginAuthToken: opts.pluginAuthToken,
29523
+ adapter: opts.adapter,
29524
+ cwd: opts.cwd,
29525
+ getBeads: opts.getBeads,
29526
+ pollSecret: opts.pollSecret
29527
+ };
29528
+ const mobileDriver = new AcpDriver({
29529
+ client: client2,
29530
+ publisher,
29531
+ streaming,
29532
+ runtime,
29533
+ recentStderr,
29534
+ opts: acpOpts,
29535
+ getRelay: () => relay
29536
+ });
29537
+ let nativeDriver;
29538
+ const agent = new AgentService(runtime, {
29539
+ cwd: opts.cwd,
29540
+ onData(raw) {
29541
+ nativeDriver.handlePtyData(raw);
29542
+ },
29543
+ onExit(code) {
29544
+ teardown();
29545
+ process.exit(code);
29546
+ }
29547
+ });
29548
+ nativeDriver = new NativeTuiDriver({
29549
+ agent,
29550
+ runtime,
29551
+ opts: {
29552
+ sessionId: opts.sessionId,
29553
+ pluginId: opts.pluginId,
29554
+ agentId: opts.agent,
29555
+ pluginAuthToken: opts.pluginAuthToken,
29556
+ cwd: opts.cwd
29557
+ },
29558
+ getRelay: () => relay,
29559
+ getBeads: opts.getBeads ?? (() => null)
29560
+ });
29561
+ let mirror = null;
29562
+ const startMirror = (conversationId) => {
29563
+ mirror?.stop();
29564
+ mirror = new TranscriptMirror({
29565
+ runtime,
29566
+ cwd: opts.cwd,
29567
+ 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
+ }
29582
+ });
29583
+ mirror.start();
29584
+ };
29585
+ const controller = new BatonController({
29586
+ local: nativeDriver,
29587
+ mobile: mobileDriver,
29588
+ publishState: (state, driver, conversationId) => {
29589
+ void postBatonEvent({
29590
+ sessionId: opts.sessionId,
29591
+ pluginId: opts.pluginId,
29592
+ pluginAuthToken: opts.pluginAuthToken,
29593
+ state,
29594
+ driver,
29595
+ conversationId
29596
+ });
29597
+ if (state === "LOCAL_DRIVE" && conversationId) startMirror(conversationId);
29598
+ else if (state !== "LOCAL_DRIVE") mirror?.stop();
29599
+ }
29600
+ });
29601
+ const dispatchActive = (cmd) => controller.activeSessionDriver.dispatch(cmd);
29602
+ relay = new CommandRelayService(
29603
+ opts.pluginId,
29604
+ makeOnCommand({
29605
+ controller,
29606
+ dispatchActive,
29607
+ ack: (id, status2, result) => relay.sendResult(id, status2, result)
29608
+ }),
29609
+ runtime.meta
29610
+ );
29611
+ let torn = false;
29612
+ function teardown() {
29613
+ if (torn) return;
29614
+ torn = true;
29615
+ process.removeListener("SIGINT", onSignal);
29616
+ process.removeListener("SIGTERM", onSignal);
29617
+ process.removeListener("SIGHUP", onSignal);
29618
+ mirror?.stop();
29619
+ relay.stop();
29620
+ void controller.shutdown();
29621
+ }
29622
+ const onSignal = () => {
29623
+ teardown();
29624
+ process.exit(0);
29625
+ };
29626
+ process.once("SIGINT", onSignal);
29627
+ process.once("SIGTERM", onSignal);
29628
+ process.once("SIGHUP", onSignal);
29629
+ showInfo(`Starting ${opts.agent} baton (local) \u2014 native TUI + mobile take-control\u2026`);
29630
+ await controller.begin();
29631
+ relay.start();
29632
+ showSuccess(`${opts.agent} baton online \u2014 you're driving locally; mobile can take control.`);
29633
+ showRelayNotice();
29634
+ await new Promise(() => {
29635
+ });
29636
+ }
29637
+ function toEpochMs(ts) {
29638
+ const asNum = Number(ts);
29639
+ if (Number.isFinite(asNum) && asNum > 0) return asNum;
29640
+ const parsed = Date.parse(ts);
29641
+ return Number.isFinite(parsed) ? parsed : Date.now();
29642
+ }
29643
+
29644
+ // src/agents/claude/onboarding.ts
29645
+ var fs59 = __toESM(require("fs"));
29022
29646
  var os45 = __toESM(require("os"));
29023
29647
  var path62 = __toESM(require("path"));
29024
29648
  function ensureClaudeOnboarded() {
@@ -29026,7 +29650,7 @@ function ensureClaudeOnboarded() {
29026
29650
  const file = path62.join(os45.homedir(), ".claude.json");
29027
29651
  let config = {};
29028
29652
  try {
29029
- config = JSON.parse(fs58.readFileSync(file, "utf8"));
29653
+ config = JSON.parse(fs59.readFileSync(file, "utf8"));
29030
29654
  } catch {
29031
29655
  }
29032
29656
  if (config.hasCompletedOnboarding === true && typeof config.theme === "string") {
@@ -29037,8 +29661,8 @@ function ensureClaudeOnboarded() {
29037
29661
  if (typeof config.lastOnboardingVersion !== "string") {
29038
29662
  config.lastOnboardingVersion = "2.1.177";
29039
29663
  }
29040
- fs58.mkdirSync(path62.dirname(file), { recursive: true });
29041
- fs58.writeFileSync(file, JSON.stringify(config, null, 2));
29664
+ fs59.mkdirSync(path62.dirname(file), { recursive: true });
29665
+ fs59.writeFileSync(file, JSON.stringify(config, null, 2));
29042
29666
  log.info("claude", "pre-completed Claude onboarding (skip first-run theme picker)");
29043
29667
  } catch (err) {
29044
29668
  log.warn("claude", `ensureClaudeOnboarded failed (non-fatal): ${err.message}`);
@@ -29162,6 +29786,22 @@ async function start(requestedAgent) {
29162
29786
  `agent-spawn gate released \u2014 beads ${beads ? "ready" : "pending"}; project deps provisioned`
29163
29787
  );
29164
29788
  }
29789
+ if (isLocalSession() && batonEnabled() && requiresAcp(session.agent)) {
29790
+ const adapter = getAcpAdapter(session.agent);
29791
+ if (adapter && session.pluginAuthToken) {
29792
+ await runBatonSession({
29793
+ agent: session.agent,
29794
+ sessionId: session.id,
29795
+ pluginId,
29796
+ pluginAuthToken: session.pluginAuthToken,
29797
+ pollSecret: session.pollSecret,
29798
+ cwd,
29799
+ adapter,
29800
+ getBeads
29801
+ });
29802
+ return;
29803
+ }
29804
+ }
29165
29805
  if (requiresAcp(session.agent)) {
29166
29806
  const adapter = getAcpAdapter(session.agent);
29167
29807
  if (!adapter || !session.pluginAuthToken) {
@@ -31739,8 +32379,8 @@ async function invite() {
31739
32379
  // src/commands/doctor.ts
31740
32380
  var import_node_dns = require("dns");
31741
32381
  var import_node_util5 = require("util");
31742
- var import_node_crypto8 = require("crypto");
31743
- var fs59 = __toESM(require("fs"));
32382
+ var import_node_crypto9 = require("crypto");
32383
+ var fs60 = __toESM(require("fs"));
31744
32384
  var path67 = __toESM(require("path"));
31745
32385
  var import_picocolors14 = __toESM(require("picocolors"));
31746
32386
  var dnsResolveP = (0, import_node_util5.promisify)(import_node_dns.resolve);
@@ -31799,11 +32439,11 @@ async function checkHealth(apiBase2) {
31799
32439
  function checkConfigDir() {
31800
32440
  const dir = path67.join(require("os").homedir(), ".codeam");
31801
32441
  try {
31802
- fs59.mkdirSync(dir, { recursive: true, mode: 448 });
32442
+ fs60.mkdirSync(dir, { recursive: true, mode: 448 });
31803
32443
  const probe = path67.join(dir, ".doctor-probe");
31804
- fs59.writeFileSync(probe, "ok", { mode: 384 });
31805
- const read2 = fs59.readFileSync(probe, "utf8");
31806
- fs59.unlinkSync(probe);
32444
+ fs60.writeFileSync(probe, "ok", { mode: 384 });
32445
+ const read2 = fs60.readFileSync(probe, "utf8");
32446
+ fs60.unlinkSync(probe);
31807
32447
  if (read2 !== "ok") throw new Error("write/read round-trip mismatch");
31808
32448
  return {
31809
32449
  id: "config-dir",
@@ -31909,9 +32549,9 @@ function checkChokidar() {
31909
32549
  }
31910
32550
  async function doctor(args2 = []) {
31911
32551
  const json = args2.includes("--json");
31912
- const cliVersion = true ? "2.58.1" : "0.0.0-dev";
32552
+ const cliVersion = true ? "2.60.0" : "0.0.0-dev";
31913
32553
  const apiBase2 = resolveApiBaseUrl();
31914
- const diagnosticId = (0, import_node_crypto8.randomUUID)();
32554
+ const diagnosticId = (0, import_node_crypto9.randomUUID)();
31915
32555
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
31916
32556
  const [dns, health] = await Promise.all([
31917
32557
  checkDns(apiBase2),
@@ -32108,7 +32748,7 @@ async function completion(args2) {
32108
32748
  // src/commands/version.ts
32109
32749
  var import_picocolors15 = __toESM(require("picocolors"));
32110
32750
  function version2() {
32111
- const v = true ? "2.58.1" : "unknown";
32751
+ const v = true ? "2.60.0" : "unknown";
32112
32752
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
32113
32753
  }
32114
32754
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.58.1",
3
+ "version": "2.60.0",
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",