codeam-cli 2.65.14 → 2.65.15

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 +10 -0
  2. package/dist/index.js +166 -17
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ 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.65.14] — 2026-08-20
8
+
9
+ ### CI
10
+
11
+ - **workflow:** All workflows report to Discord (catch-all) (#643)
12
+
13
+ ### Fixed
14
+
15
+ - **cli:** Permission guards never scan plan text and never auto-reject silently
16
+
7
17
  ## [2.65.13] — 2026-08-20
8
18
 
9
19
  ### Fixed
package/dist/index.js CHANGED
@@ -8079,7 +8079,7 @@ function readAnonId() {
8079
8079
  }
8080
8080
  function superProperties() {
8081
8081
  return {
8082
- cliVersion: true ? "2.65.14" : "0.0.0-dev",
8082
+ cliVersion: true ? "2.65.15" : "0.0.0-dev",
8083
8083
  nodeVersion: process.version,
8084
8084
  platform: process.platform,
8085
8085
  arch: process.arch,
@@ -8321,7 +8321,7 @@ var http = __toESM(require("http"));
8321
8321
  // package.json
8322
8322
  var package_default = {
8323
8323
  name: "codeam-cli",
8324
- version: "2.65.14",
8324
+ version: "2.65.15",
8325
8325
  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.",
8326
8326
  type: "commonjs",
8327
8327
  main: "dist/index.js",
@@ -9842,7 +9842,7 @@ var CommandRelayService = class _CommandRelayService {
9842
9842
  // fresh + clear the "CLI update available" banner after a self-update
9843
9843
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
9844
9844
  // pair/reconnect). Older backends ignore the extra field.
9845
- ..."2.65.14" ? { ideVersion: "2.65.14" } : {}
9845
+ ..."2.65.15" ? { ideVersion: "2.65.15" } : {}
9846
9846
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
9847
9847
  }
9848
9848
  /**
@@ -18315,22 +18315,31 @@ async function waitForCommandOnPath(cmd, opts = {}) {
18315
18315
  }
18316
18316
  return check();
18317
18317
  }
18318
- function augmentUserLocalBinPaths(deps = {}) {
18318
+ function agentInstallBinDirs(deps = {}) {
18319
18319
  const env = deps.env ?? process.env;
18320
18320
  const home = deps.homedir ?? import_os4.default.homedir();
18321
18321
  const p2 = deps.pathApi ?? import_path4.default;
18322
- const candidates = [
18322
+ return [
18323
18323
  // XDG-style per-user bin — npm's default global-prefix bin dir on most
18324
18324
  // Linux setups (`npm config set prefix ~/.local` or an nvm-less
18325
- // per-user npm), and where curl-based agent installers commonly land.
18325
+ // per-user npm), and where curl-based agent installers commonly land
18326
+ // (cursor-agent, coderabbit, pip --user's aider).
18326
18327
  p2.join(home, ".local", "bin"),
18327
18328
  // Common explicit npm global-prefix conventions seen in the wild
18328
18329
  // (`npm config set prefix ~/.npm-global`, and Debian/Fedora's
18329
18330
  // `~/.local/share/npm` layout for `npm config set prefix
18330
18331
  // ~/.local/share/npm`).
18331
18332
  p2.join(home, ".npm-global", "bin"),
18332
- p2.join(home, ".local", "share", "npm", "bin")
18333
+ p2.join(home, ".local", "share", "npm", "bin"),
18334
+ // Vendor curl-installer targets that are NOT on any default PATH.
18335
+ p2.join(env.KIMI_CODE_HOME || p2.join(home, ".kimi-code"), "bin"),
18336
+ p2.join(env.OPENCODE_HOME || p2.join(home, ".opencode"), "bin")
18333
18337
  ];
18338
+ }
18339
+ function augmentUserLocalBinPaths(deps = {}) {
18340
+ const env = deps.env ?? process.env;
18341
+ const p2 = deps.pathApi ?? import_path4.default;
18342
+ const candidates = agentInstallBinDirs(deps);
18334
18343
  const parts = (env.PATH ?? "").split(p2.delimiter).filter((s) => s.length > 0);
18335
18344
  const existing = new Set(parts);
18336
18345
  const additions = candidates.filter((dir) => !existing.has(dir));
@@ -20784,6 +20793,38 @@ async function reportDeployProgress(auth, deployId, step, message, sessionId) {
20784
20793
  } catch {
20785
20794
  }
20786
20795
  }
20796
+ async function postSessionErrorBubble(auth, message) {
20797
+ const chunks = [
20798
+ { type: "new_turn", done: false },
20799
+ { type: "text", content: message, done: true }
20800
+ ];
20801
+ for (const chunk of chunks) {
20802
+ try {
20803
+ const controller = new AbortController();
20804
+ const timer = setTimeout(() => controller.abort(), PROGRESS_TIMEOUT_MS);
20805
+ timer.unref?.();
20806
+ try {
20807
+ await fetch(`${apiBase()}/api/commands/output`, {
20808
+ method: "POST",
20809
+ headers: {
20810
+ "Content-Type": "application/json",
20811
+ "X-Plugin-Auth-Token": auth.pluginAuthToken,
20812
+ ...vercelBypassHeader()
20813
+ },
20814
+ body: JSON.stringify({
20815
+ sessionId: auth.sessionId,
20816
+ pluginId: auth.pluginId,
20817
+ ...chunk
20818
+ }),
20819
+ signal: controller.signal
20820
+ });
20821
+ } finally {
20822
+ clearTimeout(timer);
20823
+ }
20824
+ } catch {
20825
+ }
20826
+ }
20827
+ }
20787
20828
 
20788
20829
  // src/commands/host/workspace.ts
20789
20830
  var fs39 = __toESM(require("fs"));
@@ -21966,7 +22007,7 @@ async function autoUpgradeBeforeCriticalCommand() {
21966
22007
  if (process.env.NODE_ENV === "test") return;
21967
22008
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
21968
22009
  if (process.env.CI) return;
21969
- const current2 = true ? "2.65.14" : null;
22010
+ const current2 = true ? "2.65.15" : null;
21970
22011
  if (!current2) return;
21971
22012
  const cache = readCache();
21972
22013
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -21983,7 +22024,7 @@ function checkForUpdates() {
21983
22024
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
21984
22025
  if (process.env.CI) return;
21985
22026
  if (!process.stdout.isTTY) return;
21986
- const current2 = true ? "2.65.14" : null;
22027
+ const current2 = true ? "2.65.15" : null;
21987
22028
  if (!current2) return;
21988
22029
  const cache = readCache();
21989
22030
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -22003,7 +22044,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
22003
22044
  var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
22004
22045
  var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
22005
22046
  function currentCliVersion() {
22006
- return true ? "2.65.14" : null;
22047
+ return true ? "2.65.15" : null;
22007
22048
  }
22008
22049
  function runCmd(cmd, args2, timeoutMs) {
22009
22050
  return new Promise((resolve10) => {
@@ -22096,6 +22137,9 @@ function resolveInputPricePerMillion(agentId) {
22096
22137
  return getPricing(model).input;
22097
22138
  }
22098
22139
  var HEARTBEAT_INTERVAL_MS = 2e4;
22140
+ var RESUME_RETRY_BACKOFF_MS = [15e3, 3e4, 6e4, 12e4];
22141
+ var RESUME_REPROBE_INTERVAL_MS = 5 * 6e4;
22142
+ var RESUME_HEALTHY_AFTER_MS = 10 * 6e4;
22099
22143
  function maybeStartHeadroomReporter(ctx) {
22100
22144
  if (process.env["HEADROOM_ENABLED"] !== "1") return null;
22101
22145
  try {
@@ -22431,6 +22475,7 @@ var HostAgentSupervisor = class {
22431
22475
  this.selfUpdate = deps.selfUpdate ?? runSelfUpdate;
22432
22476
  this.onUpdated = deps.onUpdated ?? defaultOnUpdated;
22433
22477
  this.docker = deps.docker ?? defaultDockerRunner;
22478
+ this.postResumeFailure = deps.postResumeFailure ?? postSessionErrorBubble;
22434
22479
  }
22435
22480
  identity;
22436
22481
  deps;
@@ -22472,6 +22517,20 @@ var HostAgentSupervisor = class {
22472
22517
  docker;
22473
22518
  /** Guards against firing the self-heal more than once. */
22474
22519
  healing = false;
22520
+ /** Visible-error poster for the exhausted-resume path (injectable). */
22521
+ postResumeFailure;
22522
+ /** How many resume re-spawns have failed since the last healthy child. */
22523
+ resumeRetryAttempts = 0;
22524
+ /** Pending bounded-backoff resume retry (cleared on stop()). */
22525
+ resumeRetryTimer = null;
22526
+ /** Set once the bounded retries exhaust → heartbeat-ridden slow re-probe. */
22527
+ resumeExhausted = false;
22528
+ /** Guards the one-shot visible error bubble per failure episode. */
22529
+ resumeFailurePosted = false;
22530
+ /** Last slow re-probe attempt (epoch ms) — throttles the heartbeat rider. */
22531
+ lastResumeReprobeAt = 0;
22532
+ /** True after stop() — no resume retries may be scheduled past teardown. */
22533
+ stopped = false;
22475
22534
  /**
22476
22535
  * Resolve the self-update interval, honoring `CODEAM_HOST_SELF_UPDATE_MS`.
22477
22536
  * A finite value > 0 overrides the default; 0 or negative DISABLES the
@@ -22515,6 +22574,11 @@ var HostAgentSupervisor = class {
22515
22574
  }
22516
22575
  /** Stop the control channel + heartbeats + kill every child. */
22517
22576
  stop() {
22577
+ this.stopped = true;
22578
+ if (this.resumeRetryTimer) {
22579
+ clearTimeout(this.resumeRetryTimer);
22580
+ this.resumeRetryTimer = null;
22581
+ }
22518
22582
  if (this.heartbeatTimer) {
22519
22583
  clearInterval(this.heartbeatTimer);
22520
22584
  this.heartbeatTimer = null;
@@ -22530,6 +22594,7 @@ var HostAgentSupervisor = class {
22530
22594
  this.children.clear();
22531
22595
  }
22532
22596
  async beat() {
22597
+ this.resumeRecoveryTick();
22533
22598
  try {
22534
22599
  let metrics;
22535
22600
  try {
@@ -23222,15 +23287,12 @@ var HostAgentSupervisor = class {
23222
23287
  proc.once("exit", (code) => {
23223
23288
  if (this.children.get(session.id)?.proc === proc) this.children.delete(session.id);
23224
23289
  if (typeof code === "number" && code !== 0) {
23225
- log.warn(
23226
- "host-agent",
23227
- `resumed session ${session.id.slice(0, 8)} exited (${code}): ${tail.trim().slice(-300)}`
23228
- );
23290
+ this.onResumeChildExit(session, code, tail.trim().slice(-300));
23229
23291
  }
23230
23292
  });
23231
23293
  log.info(
23232
23294
  "host-agent",
23233
- `resumed session ${session.id.slice(0, 8)} pluginId=${session.pluginId.slice(0, 12)} (ACP)`
23295
+ `resumed session ${session.id.slice(0, 8)} pluginId=${session.pluginId.slice(0, 12)} (ACP)` + (this.resumeRetryAttempts > 0 ? ` [retry ${this.resumeRetryAttempts}]` : "")
23234
23296
  );
23235
23297
  } catch (err) {
23236
23298
  log.warn(
@@ -23239,6 +23301,92 @@ var HostAgentSupervisor = class {
23239
23301
  );
23240
23302
  }
23241
23303
  }
23304
+ /**
23305
+ * A resumed session child died with a non-zero exit. Before 2026-08-20 this
23306
+ * was ONE warn log and permanent, silent surrender: the v2.65.13 self-update
23307
+ * restarted the fleet-1 unit, the resumed kimi child died `ENOENT — 'kimi'
23308
+ * was not found on PATH`, and the session sat dead for 3+ hours while the
23309
+ * HOST heartbeat stayed green (nothing retried, nothing surfaced anywhere).
23310
+ * Now: bounded backoff retries ({@link RESUME_RETRY_BACKOFF_MS}); when they
23311
+ * exhaust, post an HONEST error bubble into the session's chat (the relay/
23312
+ * backend are up — only the agent child is dead) and hand off to the
23313
+ * heartbeat-ridden slow re-probe ({@link resumeRecoveryTick}) so an
23314
+ * externally-fixed cause heals without a manual restart.
23315
+ */
23316
+ onResumeChildExit(session, code, detail) {
23317
+ if (this.stopped) return;
23318
+ const reason = detail ? `exit ${code}: ${detail}` : `exit ${code}`;
23319
+ if (this.resumeRetryAttempts < RESUME_RETRY_BACKOFF_MS.length) {
23320
+ const delay = RESUME_RETRY_BACKOFF_MS[this.resumeRetryAttempts];
23321
+ this.resumeRetryAttempts += 1;
23322
+ log.warn(
23323
+ "host-agent",
23324
+ `resumed session ${session.id.slice(0, 8)} died (${reason}) \u2014 retry ${this.resumeRetryAttempts}/${RESUME_RETRY_BACKOFF_MS.length} in ${Math.round(delay / 1e3)}s`
23325
+ );
23326
+ this.resumeRetryTimer = setTimeout(() => {
23327
+ this.resumeRetryTimer = null;
23328
+ this.resumePersistedSession();
23329
+ }, delay);
23330
+ this.resumeRetryTimer.unref?.();
23331
+ return;
23332
+ }
23333
+ this.resumeExhausted = true;
23334
+ this.lastResumeReprobeAt = Date.now();
23335
+ log.error(
23336
+ "host-agent",
23337
+ `resumed session ${session.id.slice(0, 8)} FAILED permanently after ${RESUME_RETRY_BACKOFF_MS.length + 1} attempts (${reason}) \u2014 posting visible error, re-probing every ${Math.round(RESUME_REPROBE_INTERVAL_MS / 6e4)} min`
23338
+ );
23339
+ if (!this.resumeFailurePosted) {
23340
+ this.resumeFailurePosted = true;
23341
+ if (session.pluginId && session.pluginAuthToken) {
23342
+ void this.postResumeFailure(
23343
+ {
23344
+ sessionId: session.id,
23345
+ pluginId: session.pluginId,
23346
+ pluginAuthToken: session.pluginAuthToken
23347
+ },
23348
+ `The agent failed to restart after a CLI update/restart (${reason}). I tried ${RESUME_RETRY_BACKOFF_MS.length + 1} times without success. The host stays online and keeps retrying about every ${Math.round(RESUME_REPROBE_INTERVAL_MS / 6e4)} minutes \u2014 once the cause is fixed (for example the agent binary is reinstalled), the session reconnects automatically. You can also redeploy this server from My Servers.`
23349
+ ).catch(() => void 0);
23350
+ } else {
23351
+ log.warn(
23352
+ "host-agent",
23353
+ "resume failure bubble skipped \u2014 persisted session has no pluginAuthToken"
23354
+ );
23355
+ }
23356
+ }
23357
+ }
23358
+ /**
23359
+ * Heartbeat rider for resume recovery (synchronous scheduling only — the
23360
+ * beat must stay punctual, and this adds NO new timers):
23361
+ * - a live session child ⇒ the failure episode (if any) is over: reset
23362
+ * the retry counter + flags so a FUTURE restart gets fresh retries and
23363
+ * a fresh (single) error bubble.
23364
+ * - retries exhausted + no child ⇒ re-probe the resume, throttled to
23365
+ * {@link RESUME_REPROBE_INTERVAL_MS}, so a fixed PATH / reinstalled
23366
+ * binary heals the session WITHOUT another manual restart. A re-probe
23367
+ * that fails again stays in this state (the bubble is not re-posted).
23368
+ */
23369
+ resumeRecoveryTick() {
23370
+ if (this.children.size > 0) {
23371
+ const now2 = Date.now();
23372
+ const hasHealthyChild = [...this.children.values()].some(
23373
+ (c2) => now2 - c2.startedAt >= RESUME_HEALTHY_AFTER_MS
23374
+ );
23375
+ if (hasHealthyChild && (this.resumeRetryAttempts > 0 || this.resumeExhausted)) {
23376
+ log.info("host-agent", "resume recovered \u2014 session child healthy, retry state reset");
23377
+ this.resumeRetryAttempts = 0;
23378
+ this.resumeExhausted = false;
23379
+ this.resumeFailurePosted = false;
23380
+ }
23381
+ return;
23382
+ }
23383
+ if (!this.resumeExhausted || this.resumeRetryTimer) return;
23384
+ const now = Date.now();
23385
+ if (now - this.lastResumeReprobeAt < RESUME_REPROBE_INTERVAL_MS) return;
23386
+ this.lastResumeReprobeAt = now;
23387
+ log.info("host-agent", "resume re-probe (post-exhaustion heartbeat rider)");
23388
+ this.resumePersistedSession();
23389
+ }
23242
23390
  /**
23243
23391
  * Run the backend-supplied per-agent CLI install script (e.g.
23244
23392
  * `claude.ai/install.sh`, `npm i -g @openai/codex`). Best-effort + bounded:
@@ -35061,6 +35209,7 @@ function knownAgentBinaryDirs() {
35061
35209
  out2.push("/usr/bin");
35062
35210
  out2.push(path75.join(home, ".local/bin"));
35063
35211
  out2.push(path75.join(home, "bin"));
35212
+ out2.push(...agentInstallBinDirs());
35064
35213
  if (process.platform === "win32") {
35065
35214
  const { LOCALAPPDATA, APPDATA } = process.env;
35066
35215
  if (LOCALAPPDATA) out2.push(path75.join(LOCALAPPDATA, "cursor-agent"));
@@ -45316,7 +45465,7 @@ function checkChokidar() {
45316
45465
  }
45317
45466
  async function doctor(args2 = []) {
45318
45467
  const json = args2.includes("--json");
45319
- const cliVersion = true ? "2.65.14" : "0.0.0-dev";
45468
+ const cliVersion = true ? "2.65.15" : "0.0.0-dev";
45320
45469
  const apiBase2 = resolveApiBaseUrl();
45321
45470
  const diagnosticId = (0, import_node_crypto13.randomUUID)();
45322
45471
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -45707,7 +45856,7 @@ async function mcpRun(args2) {
45707
45856
  // src/commands/version.ts
45708
45857
  var import_picocolors15 = __toESM(require("picocolors"));
45709
45858
  function version2() {
45710
- const v = true ? "2.65.14" : "unknown";
45859
+ const v = true ? "2.65.15" : "unknown";
45711
45860
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
45712
45861
  }
45713
45862
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.65.14",
3
+ "version": "2.65.15",
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",