codeam-cli 2.73.3 → 2.73.4

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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@ 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.73.3] — 2026-09-02
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** Debounce MCP reprovisions so a run of changes costs ONE respawn (#684)
12
+
7
13
  ## [2.73.2] — 2026-09-02
8
14
 
9
15
  ### CI
package/dist/index.js CHANGED
@@ -8097,7 +8097,7 @@ function readAnonId() {
8097
8097
  }
8098
8098
  function superProperties() {
8099
8099
  return {
8100
- cliVersion: true ? "2.73.3" : "0.0.0-dev",
8100
+ cliVersion: true ? "2.73.4" : "0.0.0-dev",
8101
8101
  nodeVersion: process.version,
8102
8102
  platform: process.platform,
8103
8103
  arch: process.arch,
@@ -8339,7 +8339,7 @@ var http = __toESM(require("http"));
8339
8339
  // package.json
8340
8340
  var package_default = {
8341
8341
  name: "codeam-cli",
8342
- version: "2.73.3",
8342
+ version: "2.73.4",
8343
8343
  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.",
8344
8344
  type: "commonjs",
8345
8345
  main: "dist/index.js",
@@ -9932,7 +9932,7 @@ var CommandRelayService = class _CommandRelayService {
9932
9932
  // fresh + clear the "CLI update available" banner after a self-update
9933
9933
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
9934
9934
  // pair/reconnect). Older backends ignore the extra field.
9935
- ..."2.73.3" ? { ideVersion: "2.73.3" } : {}
9935
+ ..."2.73.4" ? { ideVersion: "2.73.4" } : {}
9936
9936
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
9937
9937
  }
9938
9938
  /**
@@ -22208,7 +22208,7 @@ async function autoUpgradeBeforeCriticalCommand() {
22208
22208
  if (process.env.NODE_ENV === "test") return;
22209
22209
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
22210
22210
  if (process.env.CI) return;
22211
- const current2 = true ? "2.73.3" : null;
22211
+ const current2 = true ? "2.73.4" : null;
22212
22212
  if (!current2) return;
22213
22213
  const cache = readCache();
22214
22214
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -22225,7 +22225,7 @@ function checkForUpdates() {
22225
22225
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
22226
22226
  if (process.env.CI) return;
22227
22227
  if (!process.stdout.isTTY) return;
22228
- const current2 = true ? "2.73.3" : null;
22228
+ const current2 = true ? "2.73.4" : null;
22229
22229
  if (!current2) return;
22230
22230
  const cache = readCache();
22231
22231
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -22248,7 +22248,7 @@ var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
22248
22248
  var SELF_UPDATE_LS_TIMEOUT_MS = 15e3;
22249
22249
  var SUDO_PREFLIGHT_TIMEOUT_MS = 5e3;
22250
22250
  function currentCliVersion() {
22251
- return true ? "2.73.3" : null;
22251
+ return true ? "2.73.4" : null;
22252
22252
  }
22253
22253
  async function installedVersion(deps) {
22254
22254
  const ls = await deps.run(
@@ -35677,6 +35677,7 @@ var _acpStartSeam = {
35677
35677
  var PROTOCOL_VERSION3 = 1;
35678
35678
  var FATAL_STARTUP_RE = /IneligibleTierError|UNSUPPORTED_CLIENT|no longer supported for Gemini Code Assist|not eligible for Gemini Code Assist|Error authenticating|ProjectIdRequiredError/i;
35679
35679
  var NEWSESSION_TIMEOUT_MS = 12e4;
35680
+ var MCP_STARTUP_TIMEOUT_MS = NEWSESSION_TIMEOUT_MS - 3e4;
35680
35681
  var LOADSESSION_TIMEOUT_MS = 6e4;
35681
35682
  var PROMPT_IDLE_TIMEOUT_MS = 9e4;
35682
35683
  var PROMPT_ACTIVE_IDLE_TIMEOUT_MS = 6e5;
@@ -35855,7 +35856,15 @@ var AcpClient = class {
35855
35856
  // extraEnv (e.g. CLAUDE_CODE_DISABLE_1M_CONTEXT=1 on an on-demand
35856
35857
  // re-spawn) layers over process.env; PATH stays last so the augmented
35857
35858
  // PATH always wins.
35858
- env: { ...process.env, ...this.opts.extraEnv ?? {}, PATH: augmentedPath },
35859
+ //
35860
+ // MCP_TIMEOUT comes FIRST so the caller can still override it, and it is
35861
+ // load-bearing — see MCP_STARTUP_TIMEOUT_MS.
35862
+ env: {
35863
+ ...process.env,
35864
+ MCP_TIMEOUT: String(MCP_STARTUP_TIMEOUT_MS),
35865
+ ...this.opts.extraEnv ?? {},
35866
+ PATH: augmentedPath
35867
+ },
35859
35868
  stdio: ["pipe", "pipe", "pipe"]
35860
35869
  });
35861
35870
  this.child = child;
@@ -43702,12 +43711,22 @@ var TranscriptMirror = class {
43702
43711
  unwatch = null;
43703
43712
  pollHandle = null;
43704
43713
  attached = false;
43714
+ /** True while `start()`'s own synchronous attach attempt is running, so
43715
+ * `tryAttach` can tell "the file was already there" from "it appeared while
43716
+ * we waited". The distinction is the whole basis for whether the first
43717
+ * batch is history or live content. */
43718
+ attachingAtStart = false;
43705
43719
  pollIntervalMs;
43706
43720
  waitTimeoutMs;
43707
43721
  setIntervalFn;
43708
43722
  clearIntervalFn;
43709
43723
  start() {
43710
- if (this.tryAttach()) return;
43724
+ this.attachingAtStart = true;
43725
+ try {
43726
+ if (this.tryAttach()) return;
43727
+ } finally {
43728
+ this.attachingAtStart = false;
43729
+ }
43711
43730
  let waited = 0;
43712
43731
  this.pollHandle = this.setIntervalFn(() => {
43713
43732
  waited += this.pollIntervalMs;
@@ -43730,7 +43749,7 @@ var TranscriptMirror = class {
43730
43749
  const file = this.deps.runtime.resolveHistoryFile?.(this.deps.cwd, this.deps.conversationId);
43731
43750
  if (!file) return false;
43732
43751
  this.attached = true;
43733
- this.emit(file);
43752
+ this.emit(file, this.attachingAtStart);
43734
43753
  const watch4 = this.deps.watch ?? defaultWatch;
43735
43754
  this.unwatch = watch4(file, () => this.emit(file));
43736
43755
  return true;
@@ -43741,7 +43760,7 @@ var TranscriptMirror = class {
43741
43760
  this.pollHandle = null;
43742
43761
  }
43743
43762
  }
43744
- emit(file) {
43763
+ emit(file, preexisting = false) {
43745
43764
  let all;
43746
43765
  try {
43747
43766
  all = this.deps.runtime.parseHistoryFile(file);
@@ -43751,7 +43770,7 @@ var TranscriptMirror = class {
43751
43770
  if (all.length <= this.emitted) return;
43752
43771
  const delta = all.slice(this.emitted);
43753
43772
  this.emitted = all.length;
43754
- this.deps.onNewMessages(delta);
43773
+ this.deps.onNewMessages(delta, { preexisting });
43755
43774
  }
43756
43775
  };
43757
43776
  function defaultWatch(file, onChange) {
@@ -43803,10 +43822,9 @@ async function publishMirroredTurnsLive(publisher, messages) {
43803
43822
  }
43804
43823
  }
43805
43824
  function makeMirrorOnNewMessages(deps) {
43806
- let isFirstEmit = true;
43807
43825
  let publishChain = Promise.resolve();
43808
43826
  const conversation = [];
43809
- return (messages) => {
43827
+ return (messages, meta) => {
43810
43828
  const relevant = messages.filter((m) => m.role !== "system");
43811
43829
  if (relevant.length === 0) return;
43812
43830
  const mapped = relevant.map((m) => ({
@@ -43821,7 +43839,7 @@ function makeMirrorOnNewMessages(deps) {
43821
43839
  sessionId: deps.conversationId,
43822
43840
  messages: conversation.slice()
43823
43841
  });
43824
- if (deps.fresh || !isFirstEmit) {
43842
+ if (meta?.preexisting !== true) {
43825
43843
  publishChain = publishChain.then(() => publishMirroredTurnsLive(deps.publisher, relevant)).catch((err) => {
43826
43844
  log.warn(
43827
43845
  "wireBaton",
@@ -43829,7 +43847,6 @@ function makeMirrorOnNewMessages(deps) {
43829
43847
  );
43830
43848
  });
43831
43849
  }
43832
- isFirstEmit = false;
43833
43850
  };
43834
43851
  }
43835
43852
  function makeSerializedBatonPoster(post2) {
@@ -43956,9 +43973,8 @@ async function runBatonSession(opts) {
43956
43973
  onConversationSwitch: (id) => controller.switchConversation(id)
43957
43974
  });
43958
43975
  let mirror = null;
43959
- let firstLocalDrive = true;
43960
43976
  let lastPublished = null;
43961
- const startMirror = (conversationId, fresh) => {
43977
+ const startMirror = (conversationId) => {
43962
43978
  mirror?.stop();
43963
43979
  mirror = new TranscriptMirror({
43964
43980
  runtime,
@@ -43967,8 +43983,7 @@ async function runBatonSession(opts) {
43967
43983
  onNewMessages: makeMirrorOnNewMessages({
43968
43984
  publisher,
43969
43985
  agentId: opts.agent,
43970
- conversationId,
43971
- fresh
43986
+ conversationId
43972
43987
  })
43973
43988
  });
43974
43989
  mirror.start();
@@ -43990,9 +44005,7 @@ async function runBatonSession(opts) {
43990
44005
  publishState: (state, driver, conversationId) => {
43991
44006
  publishBatonState(state, driver, conversationId);
43992
44007
  if (state === "LOCAL_DRIVE" && conversationId) {
43993
- const switched = lastPublished === "LOCAL_DRIVE";
43994
- startMirror(conversationId, firstLocalDrive || switched);
43995
- firstLocalDrive = false;
44008
+ startMirror(conversationId);
43996
44009
  } else if (state !== "LOCAL_DRIVE") {
43997
44010
  mirror?.stop();
43998
44011
  }
@@ -47086,7 +47099,7 @@ function checkChokidar() {
47086
47099
  }
47087
47100
  async function doctor(args2 = []) {
47088
47101
  const json = args2.includes("--json");
47089
- const cliVersion = true ? "2.73.3" : "0.0.0-dev";
47102
+ const cliVersion = true ? "2.73.4" : "0.0.0-dev";
47090
47103
  const apiBase2 = resolveApiBaseUrl();
47091
47104
  const diagnosticId = (0, import_node_crypto13.randomUUID)();
47092
47105
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -47474,10 +47487,77 @@ async function mcpRun(args2) {
47474
47487
  await proxy.start();
47475
47488
  }
47476
47489
 
47490
+ // src/integrations/mcp-warm.ts
47491
+ var import_node_child_process35 = require("child_process");
47492
+ function specOf(delivery) {
47493
+ if (!delivery.command) return null;
47494
+ const spec = (delivery.args ?? []).find((a) => !a.startsWith("-"));
47495
+ return spec ?? null;
47496
+ }
47497
+ function warmTargets() {
47498
+ const bySpec = /* @__PURE__ */ new Map();
47499
+ for (const integration of getEnabledIntegrations()) {
47500
+ const delivery = integration.delivery?.mcp;
47501
+ if (!delivery) continue;
47502
+ const spec = specOf(delivery);
47503
+ if (!spec) continue;
47504
+ if (!bySpec.has(spec)) {
47505
+ bySpec.set(spec, { id: integration.id, launcher: delivery.command, spec });
47506
+ }
47507
+ }
47508
+ return [...bySpec.values()];
47509
+ }
47510
+ function warmCommand(target) {
47511
+ if (target.launcher === "npx") {
47512
+ return { command: "npx", args: ["-y", "--package", target.spec, "-c", "true"] };
47513
+ }
47514
+ if (target.launcher === "uvx") {
47515
+ return { command: "uv", args: ["tool", "install", target.spec] };
47516
+ }
47517
+ return null;
47518
+ }
47519
+ async function mcpWarm(argv = []) {
47520
+ const timeoutMs = 3e5;
47521
+ const targets = warmTargets();
47522
+ const only = argv.filter((a) => !a.startsWith("-"));
47523
+ const selected = only.length > 0 ? targets.filter((t2) => only.includes(t2.id)) : targets;
47524
+ const result = { warmed: [], skipped: [], failed: [] };
47525
+ console.log(`[codeam mcp-warm] ${selected.length} MCP package(s) to pre-fetch`);
47526
+ for (const target of selected) {
47527
+ const cmd = warmCommand(target);
47528
+ if (!cmd) {
47529
+ result.skipped.push(`${target.spec} (no warm recipe for '${target.launcher}')`);
47530
+ continue;
47531
+ }
47532
+ const started = Date.now();
47533
+ const run = (0, import_node_child_process35.spawnSync)(cmd.command, cmd.args, {
47534
+ stdio: ["ignore", "ignore", "pipe"],
47535
+ timeout: timeoutMs,
47536
+ encoding: "utf8"
47537
+ });
47538
+ const secs = ((Date.now() - started) / 1e3).toFixed(1);
47539
+ if (run.status === 0) {
47540
+ result.warmed.push(target.spec);
47541
+ console.log(` OK ${target.spec} (${secs}s)`);
47542
+ } else {
47543
+ const reason = run.error?.message ?? (run.stderr ?? "").trim().split("\n").slice(-1)[0] ?? `exit ${String(run.status)}`;
47544
+ result.failed.push({ spec: target.spec, reason });
47545
+ console.log(` MISS ${target.spec} (${secs}s) \u2014 fetched at runtime instead: ${reason}`);
47546
+ }
47547
+ }
47548
+ console.log(
47549
+ `[codeam mcp-warm] warmed=${result.warmed.length} failed=${result.failed.length} skipped=${result.skipped.length}`
47550
+ );
47551
+ log.info(
47552
+ "mcpWarm",
47553
+ `warmed=${result.warmed.length} failed=${result.failed.length} skipped=${result.skipped.length}`
47554
+ );
47555
+ }
47556
+
47477
47557
  // src/commands/version.ts
47478
47558
  var import_picocolors15 = __toESM(require("picocolors"));
47479
47559
  function version2() {
47480
- const v = true ? "2.73.3" : "unknown";
47560
+ const v = true ? "2.73.4" : "unknown";
47481
47561
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
47482
47562
  }
47483
47563
 
@@ -47678,6 +47758,9 @@ async function main() {
47678
47758
  if (command === "mcp-run") {
47679
47759
  return mcpRun(args);
47680
47760
  }
47761
+ if (command === "mcp-warm") {
47762
+ return mcpWarm(args);
47763
+ }
47681
47764
  if (command === "deploy") {
47682
47765
  if (args[0] === "ls" || args[0] === "list") return deployList();
47683
47766
  if (args[0] === "stop" || args[0] === "remove") return deployStop();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.73.3",
3
+ "version": "2.73.4",
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",