codeam-cli 2.60.4 → 2.60.6

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 +12 -0
  2. package/dist/index.js +103 -20
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ 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.4] — 2026-07-08
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** Serialize baton state POSTs so handback never sticks on "Switching…"
12
+
13
+ ## [2.60.3] — 2026-07-08
14
+
15
+ ### Fixed
16
+
17
+ - **cli:** Clean up terminal + mobile activity on baton Take Control
18
+
7
19
  ## [2.60.2] — 2026-07-08
8
20
 
9
21
  ### Fixed
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.4" : "0.0.0-dev",
5656
+ cliVersion: true ? "2.60.6" : "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.4",
5837
+ version: "2.60.6",
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.4" ? { ideVersion: "2.60.4" } : {}
6908
+ ..."2.60.6" ? { ideVersion: "2.60.6" } : {}
6909
6909
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
6910
6910
  }
6911
6911
  /**
@@ -13399,13 +13399,71 @@ var fs23 = __toESM(require("fs"));
13399
13399
  var os21 = __toESM(require("os"));
13400
13400
  var path27 = __toESM(require("path"));
13401
13401
  var HISTORY_ROOT = path27.join(os21.homedir(), ".cursor", "projects");
13402
+ function encodeCursorCwd(cwd) {
13403
+ return cwd.replace(/^[/\\]+/, "").replace(/[/\\:]/g, "-");
13404
+ }
13405
+ function resolveHistoryFile3(cwd, sessionId, root = HISTORY_ROOT) {
13406
+ const rel = path27.join("agent-transcripts", sessionId, `${sessionId}.jsonl`);
13407
+ const primary = path27.join(root, encodeCursorCwd(cwd), rel);
13408
+ if (fs23.existsSync(primary)) return primary;
13409
+ let entries;
13410
+ try {
13411
+ entries = fs23.readdirSync(root, { withFileTypes: true });
13412
+ } catch {
13413
+ return null;
13414
+ }
13415
+ for (const e of entries) {
13416
+ if (!e.isDirectory()) continue;
13417
+ const candidate = path27.join(root, e.name, rel);
13418
+ if (fs23.existsSync(candidate)) return candidate;
13419
+ }
13420
+ return null;
13421
+ }
13402
13422
  function resolveHistoryDir3(cwd) {
13403
13423
  if (!fs23.existsSync(HISTORY_ROOT)) return null;
13404
- void cwd;
13405
- return HISTORY_ROOT;
13424
+ const dir = path27.join(HISTORY_ROOT, encodeCursorCwd(cwd));
13425
+ return fs23.existsSync(dir) ? dir : null;
13406
13426
  }
13407
- function parseHistoryFile3(_filePath) {
13408
- return [];
13427
+ function extractText2(content) {
13428
+ if (!Array.isArray(content)) return "";
13429
+ return content.filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("");
13430
+ }
13431
+ function unwrapUserQuery(text) {
13432
+ const m = text.match(/<user_query>\s*([\s\S]*?)\s*<\/user_query>/);
13433
+ return (m ? m[1] : text).trim();
13434
+ }
13435
+ function parseHistoryFile3(filePath) {
13436
+ let raw;
13437
+ try {
13438
+ raw = fs23.readFileSync(filePath, "utf8");
13439
+ } catch {
13440
+ return [];
13441
+ }
13442
+ const out2 = [];
13443
+ let idx = 0;
13444
+ for (const line of raw.split("\n")) {
13445
+ if (!line.trim()) continue;
13446
+ let rec;
13447
+ try {
13448
+ rec = JSON.parse(line);
13449
+ } catch {
13450
+ continue;
13451
+ }
13452
+ if (rec.role !== "user" && rec.role !== "assistant") continue;
13453
+ const blockText = extractText2(rec.message?.content);
13454
+ const text = rec.role === "user" ? unwrapUserQuery(blockText) : blockText.trim();
13455
+ if (!text) continue;
13456
+ out2.push({
13457
+ id: `cursor:${idx}`,
13458
+ role: rec.role === "user" ? "user" : "agent",
13459
+ text,
13460
+ // Cursor records carry no per-message timestamp; order is preserved by
13461
+ // file position, so a stable epoch keeps the shape valid without lying.
13462
+ timestamp: (/* @__PURE__ */ new Date(0)).toISOString()
13463
+ });
13464
+ idx += 1;
13465
+ }
13466
+ return out2;
13409
13467
  }
13410
13468
  function getCurrentUsage3(_historyDir) {
13411
13469
  return null;
@@ -13527,6 +13585,13 @@ var CursorRuntimeStrategy = class {
13527
13585
  resolveHistoryDir(cwd) {
13528
13586
  return resolveHistoryDir3(cwd);
13529
13587
  }
13588
+ /** Session transcript at
13589
+ * `~/.cursor/projects/<encoded-cwd>/agent-transcripts/<id>/<id>.jsonl`.
13590
+ * Presence of this method is what lets the baton engage for Cursor
13591
+ * (`runtimeSupportsBaton`), so the LOCAL_DRIVE mirror can tail it. */
13592
+ resolveHistoryFile(cwd, sessionId) {
13593
+ return resolveHistoryFile3(cwd, sessionId);
13594
+ }
13530
13595
  parseHistoryFile(filePath) {
13531
13596
  return parseHistoryFile3(filePath);
13532
13597
  }
@@ -15921,7 +15986,7 @@ async function autoUpgradeBeforeCriticalCommand() {
15921
15986
  if (process.env.NODE_ENV === "test") return;
15922
15987
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
15923
15988
  if (process.env.CI) return;
15924
- const current = true ? "2.60.4" : null;
15989
+ const current = true ? "2.60.6" : null;
15925
15990
  if (!current) return;
15926
15991
  const cache = readCache();
15927
15992
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -15938,7 +16003,7 @@ function checkForUpdates() {
15938
16003
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
15939
16004
  if (process.env.CI) return;
15940
16005
  if (!process.stdout.isTTY) return;
15941
- const current = true ? "2.60.4" : null;
16006
+ const current = true ? "2.60.6" : null;
15942
16007
  if (!current) return;
15943
16008
  const cache = readCache();
15944
16009
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -15958,7 +16023,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
15958
16023
  var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
15959
16024
  var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
15960
16025
  function currentCliVersion() {
15961
- return true ? "2.60.4" : null;
16026
+ return true ? "2.60.6" : null;
15962
16027
  }
15963
16028
  function runCmd(cmd, args2, timeoutMs) {
15964
16029
  return new Promise((resolve7) => {
@@ -21728,7 +21793,7 @@ var historyRecordSchema = import_zod2.z.object({
21728
21793
  }).passthrough().optional()
21729
21794
  }).passthrough();
21730
21795
  var API_BASE8 = resolveApiBaseUrl();
21731
- function extractText2(content) {
21796
+ function extractText3(content) {
21732
21797
  if (typeof content === "string") return content;
21733
21798
  if (Array.isArray(content)) {
21734
21799
  return content.filter((b) => b["type"] === "text").map((b) => b["text"]).join("\n");
@@ -21767,10 +21832,10 @@ function parseJsonl(filePath) {
21767
21832
  const uuid = record.uuid ?? `${Date.now()}-${Math.random()}`;
21768
21833
  const msg = record.message;
21769
21834
  if (record.type === "user" && msg) {
21770
- const text = extractText2(msg.content).trim();
21835
+ const text = extractText3(msg.content).trim();
21771
21836
  if (text) messages.push({ id: uuid, role: "user", text, timestamp });
21772
21837
  } else if (record.type === "assistant" && msg) {
21773
- const text = extractText2(msg.content).trim();
21838
+ const text = extractText3(msg.content).trim();
21774
21839
  if (text) messages.push({ id: uuid, role: "agent", text, timestamp });
21775
21840
  }
21776
21841
  }
@@ -24815,6 +24880,12 @@ var AcpClient = class {
24815
24880
  * (the agent is demonstrably working, just blocked on the tool), and
24816
24881
  * re-arm only once the last one finishes. Reset per prompt. */
24817
24882
  pendingToolCalls = /* @__PURE__ */ new Set();
24883
+ /** Serializes {@link prompt}: each turn waits for the previous to fully
24884
+ * settle before arming its own watchdog. `promptIdle`/`pendingToolCalls`
24885
+ * are single-instance fields, so a 2nd concurrent prompt() would overwrite
24886
+ * them mid-turn and break turn A's idle watchdog (mobile "send-while-active"
24887
+ * can fire two prompts back-to-back). See {@link prompt}. */
24888
+ promptChain = Promise.resolve();
24818
24889
  /** Last few adapter stderr lines — so a startup failure surfaces the REAL
24819
24890
  * cause (e.g. gemini's `IneligibleTierError`) instead of a bare timeout. */
24820
24891
  recentStderr = [];
@@ -24969,6 +25040,14 @@ var AcpClient = class {
24969
25040
  * shows a permanent "Thinking…" spinner with no way to recover.
24970
25041
  */
24971
25042
  async prompt(input) {
25043
+ const run = this.promptChain.then(() => this.runPrompt(input));
25044
+ this.promptChain = run.then(
25045
+ () => void 0,
25046
+ () => void 0
25047
+ );
25048
+ return run;
25049
+ }
25050
+ async runPrompt(input) {
24972
25051
  if (!this.connection || !this.sessionId) {
24973
25052
  throw new Error("AcpClient.prompt called before start()");
24974
25053
  }
@@ -25853,12 +25932,12 @@ function mapSessionUpdate(notification) {
25853
25932
  const update = notification.update;
25854
25933
  switch (update.sessionUpdate) {
25855
25934
  case "agent_message_chunk": {
25856
- const text = extractText3(update.content);
25935
+ const text = extractText4(update.content);
25857
25936
  if (!text) return [];
25858
25937
  return [{ chunkId: messageChunkId(update.messageId), kind: "text", delta: text }];
25859
25938
  }
25860
25939
  case "agent_thought_chunk": {
25861
- const text = extractText3(update.content);
25940
+ const text = extractText4(update.content);
25862
25941
  if (!text) return [];
25863
25942
  return [
25864
25943
  {
@@ -25923,7 +26002,7 @@ function messageChunkId(messageId) {
25923
26002
  if (typeof messageId === "string" && messageId.length > 0) return messageId;
25924
26003
  return (0, import_node_crypto6.randomUUID)();
25925
26004
  }
25926
- function extractText3(content) {
26005
+ function extractText4(content) {
25927
26006
  if (!content || typeof content !== "object") return null;
25928
26007
  if ("type" in content && content.type === "text") {
25929
26008
  const t2 = content.text;
@@ -25953,7 +26032,7 @@ function describeToolCallUpdate(update) {
25953
26032
  for (const item of update.content) {
25954
26033
  if (!item || typeof item !== "object") continue;
25955
26034
  if (item.type === "content" && item.content) {
25956
- const text = extractText3(item.content);
26035
+ const text = extractText4(item.content);
25957
26036
  if (text) parts.push(text);
25958
26037
  } else if (item.type === "diff") {
25959
26038
  const p2 = item.path;
@@ -29131,6 +29210,9 @@ function fetchQuotaUsage(runtime, historySvc) {
29131
29210
  }
29132
29211
 
29133
29212
  // src/baton/gate.ts
29213
+ function runtimeSupportsBaton(runtime) {
29214
+ return typeof runtime.resolveHistoryFile === "function";
29215
+ }
29134
29216
  function isLocalSession(env = process.env) {
29135
29217
  return env.CODESPACES !== "true" && env.CODEAM_AUTO_APPROVE !== "1" && env.HEADROOM_ENABLED !== "1" && !env.CODEAM_AUTO_TOKEN && !env.CODEAM_ENROLL_TOKEN;
29136
29218
  }
@@ -29911,7 +29993,8 @@ async function start(requestedAgent) {
29911
29993
  }
29912
29994
  if (isLocalSession() && requiresAcp(session.agent)) {
29913
29995
  const adapter = getAcpAdapter(session.agent);
29914
- if (adapter && session.pluginAuthToken) {
29996
+ const batonCapable = runtimeSupportsBaton(createRuntimeStrategy(session.agent));
29997
+ if (batonCapable && adapter && session.pluginAuthToken) {
29915
29998
  await runBatonSession({
29916
29999
  agent: session.agent,
29917
30000
  sessionId: session.id,
@@ -32672,7 +32755,7 @@ function checkChokidar() {
32672
32755
  }
32673
32756
  async function doctor(args2 = []) {
32674
32757
  const json = args2.includes("--json");
32675
- const cliVersion = true ? "2.60.4" : "0.0.0-dev";
32758
+ const cliVersion = true ? "2.60.6" : "0.0.0-dev";
32676
32759
  const apiBase2 = resolveApiBaseUrl();
32677
32760
  const diagnosticId = (0, import_node_crypto9.randomUUID)();
32678
32761
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -32871,7 +32954,7 @@ async function completion(args2) {
32871
32954
  // src/commands/version.ts
32872
32955
  var import_picocolors15 = __toESM(require("picocolors"));
32873
32956
  function version2() {
32874
- const v = true ? "2.60.4" : "unknown";
32957
+ const v = true ? "2.60.6" : "unknown";
32875
32958
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
32876
32959
  }
32877
32960
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.60.4",
3
+ "version": "2.60.6",
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",