codeam-cli 2.41.0 → 2.42.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 +10 -0
  2. package/dist/index.js +109 -24
  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.41.0] — 2026-06-24
8
+
9
+ ### Added
10
+
11
+ - **cli:** Surface friendly provider-outage message + status-page link instead of silent hang
12
+
13
+ ### Merge
14
+
15
+ - Friendly provider-outage message + status-page link (no silent hang on provider 529/5xx)
16
+
7
17
  ## [2.40.2] — 2026-06-24
8
18
 
9
19
  ### Fixed
package/dist/index.js CHANGED
@@ -5390,7 +5390,7 @@ function readAnonId() {
5390
5390
  }
5391
5391
  function superProperties() {
5392
5392
  return {
5393
- cliVersion: true ? "2.41.0" : "0.0.0-dev",
5393
+ cliVersion: true ? "2.42.0" : "0.0.0-dev",
5394
5394
  nodeVersion: process.version,
5395
5395
  platform: process.platform,
5396
5396
  arch: process.arch,
@@ -5536,6 +5536,28 @@ var _execSeam = {
5536
5536
  return typeof out2 === "string" ? out2 : out2.toString("utf8");
5537
5537
  }
5538
5538
  };
5539
+ var _execSeamAsync = {
5540
+ exec: (file, args2, opts) => new Promise((resolve7, reject) => {
5541
+ (0, import_child_process.execFile)(file, args2, opts, (err, stdout) => {
5542
+ if (err) reject(err);
5543
+ else resolve7(typeof stdout === "string" ? stdout : stdout.toString("utf8"));
5544
+ });
5545
+ })
5546
+ };
5547
+ async function detectCurrentBranchAsync(cwd = process.cwd()) {
5548
+ try {
5549
+ const raw = await _execSeamAsync.exec("git", ["branch", "--show-current"], {
5550
+ cwd,
5551
+ timeout: 1e3,
5552
+ encoding: "utf8",
5553
+ windowsHide: true
5554
+ });
5555
+ const trimmed = raw.trim();
5556
+ return trimmed.length > 0 ? trimmed : null;
5557
+ } catch {
5558
+ return null;
5559
+ }
5560
+ }
5539
5561
 
5540
5562
  // src/services/command-relay.service.ts
5541
5563
  var https2 = __toESM(require("https"));
@@ -5549,7 +5571,7 @@ var os4 = __toESM(require("os"));
5549
5571
  // package.json
5550
5572
  var package_default = {
5551
5573
  name: "codeam-cli",
5552
- version: "2.41.0",
5574
+ version: "2.42.0",
5553
5575
  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.",
5554
5576
  type: "commonjs",
5555
5577
  main: "dist/index.js",
@@ -6044,13 +6066,31 @@ var CommandRelayService = class {
6044
6066
  */
6045
6067
  sseWatchdog = null;
6046
6068
  sseLastByteAt = 0;
6069
+ /**
6070
+ * Last-known git branch, shipped on every heartbeat. Seeded with a
6071
+ * single SYNCHRONOUS read at `start()` (before any turn is running)
6072
+ * for an accurate first beat, then refreshed ASYNCHRONOUSLY off the
6073
+ * recurring heartbeat tick. The heartbeat POST reads this cached
6074
+ * value and never spawns `git` synchronously — so the 20 s beat
6075
+ * stays punctual even while a long agent tool call is hammering the
6076
+ * event loop. Trade-off: because the async refresh fires alongside
6077
+ * the POST (not before it), a `git checkout` propagates on the NEXT
6078
+ * beat — up to ~40 s worst case vs the old at-POST-time read. The
6079
+ * backend dedupes a stable branch, so this lag costs nothing but
6080
+ * freshness, and a punctual heartbeat matters far more.
6081
+ */
6082
+ cachedBranch = null;
6047
6083
  start() {
6048
6084
  this.cleanup();
6049
6085
  this._running = true;
6050
6086
  this.agentsRegistered = false;
6051
6087
  log.info("relay", `start pluginId=${this.pluginId.slice(0, 8)} agent=${this.agentMeta.id}`);
6088
+ this.cachedBranch = detectCurrentBranch();
6052
6089
  this.sendHeartbeat(true);
6053
- this.heartbeatTimer = setInterval(() => this.sendHeartbeat(true), 2e4);
6090
+ this.heartbeatTimer = setInterval(() => {
6091
+ void this.refreshBranch();
6092
+ this.sendHeartbeat(true);
6093
+ }, 2e4);
6054
6094
  this.agentsTimer = setInterval(() => {
6055
6095
  if (this._running && !this.agentsRegistered) this.reportAgents();
6056
6096
  }, 5e3);
@@ -6294,9 +6334,20 @@ var CommandRelayService = class {
6294
6334
  pluginId: this.pluginId,
6295
6335
  online,
6296
6336
  agentId: this.agentMeta.id,
6297
- branch: detectCurrentBranch()
6337
+ branch: this.cachedBranch
6298
6338
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
6299
6339
  }
6340
+ /**
6341
+ * Refresh {@link cachedBranch} without blocking the event loop.
6342
+ * Fire-and-forget from the heartbeat tick; failures leave the last
6343
+ * known value in place (the backend dedupes a stable branch).
6344
+ */
6345
+ async refreshBranch() {
6346
+ try {
6347
+ this.cachedBranch = await detectCurrentBranchAsync();
6348
+ } catch {
6349
+ }
6350
+ }
6300
6351
  reportAgents() {
6301
6352
  const agents = this.agentsOverride ? [...this.agentsOverride] : [
6302
6353
  {
@@ -17753,7 +17804,7 @@ async function autoUpgradeBeforeCriticalCommand() {
17753
17804
  if (process.env.NODE_ENV === "test") return;
17754
17805
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
17755
17806
  if (process.env.CI) return;
17756
- const current = true ? "2.41.0" : null;
17807
+ const current = true ? "2.42.0" : null;
17757
17808
  if (!current) return;
17758
17809
  const cache = readCache();
17759
17810
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -17770,7 +17821,7 @@ function checkForUpdates() {
17770
17821
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
17771
17822
  if (process.env.CI) return;
17772
17823
  if (!process.stdout.isTTY) return;
17773
- const current = true ? "2.41.0" : null;
17824
+ const current = true ? "2.42.0" : null;
17774
17825
  if (!current) return;
17775
17826
  const cache = readCache();
17776
17827
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -18208,7 +18259,7 @@ var defaultSpawner = (env, cwd, args2 = []) => (0, import_node_child_process14.s
18208
18259
  detached: false
18209
18260
  });
18210
18261
  function currentCliVersion() {
18211
- return true ? "2.41.0" : null;
18262
+ return true ? "2.42.0" : null;
18212
18263
  }
18213
18264
  function runCmd(cmd, args2, timeoutMs) {
18214
18265
  return new Promise((resolve7) => {
@@ -24450,6 +24501,22 @@ var StreamingState = class {
24450
24501
  this.flushStreamingChunks()
24451
24502
  ]);
24452
24503
  }
24504
+ /**
24505
+ * Close the turn but REPLACE the streamed reply with `bubble` as the
24506
+ * terminal chat frame. The chat pipe treats each `text` chunk's content as
24507
+ * the full (replacing) bubble body, so a final `done:true` carrying the
24508
+ * bubble overwrites whatever raw text streamed. Used when the agent's
24509
+ * completed-turn reply was itself an auth-failure notice
24510
+ * ({@link replyIsAuthFailure}) → swap the raw "Please run /login" for the
24511
+ * actionable re-auth bubble.
24512
+ */
24513
+ async closeWithBubble(bubble) {
24514
+ this.text = "";
24515
+ await Promise.all([
24516
+ this.publisher.publishOutput({ type: "text", content: bubble, done: true }),
24517
+ this.flushStreamingChunks()
24518
+ ]);
24519
+ }
24453
24520
  /**
24454
24521
  * Re-emit every open streaming-chunk buffer with `isFinal: true`
24455
24522
  * so SessionDetailScreen's bubbles flip out of "still streaming"
@@ -24617,6 +24684,10 @@ var AUTH_FAILURE_RE = /invalid authentication credentials|authentication[_ ]erro
24617
24684
  function looksLikeAuthFailure(text) {
24618
24685
  return AUTH_FAILURE_RE.test(text);
24619
24686
  }
24687
+ function replyIsAuthFailure(finalText) {
24688
+ const t2 = finalText.trim();
24689
+ return t2.length > 0 && t2.length <= 200 && looksLikeAuthFailure(t2);
24690
+ }
24620
24691
  var AUTH_FAILURE_MESSAGE = "\u{1F512} **Authentication failed \u2014 your agent credentials are invalid or expired (API 401).**\n\nTap [Re-authenticate this agent](codeam://reauth) to renew your credentials in Profile \u203A Agents, then send your message again.";
24621
24692
  var TURN_FAILURE_MESSAGE = "\u26A0\uFE0F **The agent hit an error and couldn\u2019t finish this turn.** Please send your message again.";
24622
24693
  var PROVIDER_OUTAGE_RE = /overloaded_error|\boverloaded\b|service[ _]unavailable|temporarily[ _]unavailable|(?:api error|http|status)[:\s]+(?:529|503|502|504)\b|\b(?:529|503|502|504)\b[^\n]{0,40}(?:overload|unavailable|gateway|upstream|server error)|bad gateway|gateway time-?out|upstream (?:error|connect|timeout)/i;
@@ -24631,8 +24702,10 @@ function agentStatusPage(agent) {
24631
24702
  return { vendor: "OpenAI", url: "https://status.openai.com" };
24632
24703
  if (a.includes("gemini") || a.includes("google"))
24633
24704
  return { vendor: "Google", url: "https://status.cloud.google.com" };
24634
- if (a.includes("copilot")) return { vendor: "GitHub", url: "https://www.githubstatus.com" };
24635
- if (a.includes("cursor")) return { vendor: "Cursor", url: "https://status.cursor.com" };
24705
+ if (a.includes("copilot"))
24706
+ return { vendor: "GitHub", url: "https://www.githubstatus.com" };
24707
+ if (a.includes("cursor"))
24708
+ return { vendor: "Cursor", url: "https://status.cursor.com" };
24636
24709
  return null;
24637
24710
  }
24638
24711
  function providerOutageMessage(agent) {
@@ -24946,18 +25019,30 @@ async function handleCommand(cmd, client2, relay, acpSessionId, models, streamin
24946
25019
  try {
24947
25020
  const reply = await client2.prompt(blocks);
24948
25021
  const finalText = streaming.getCurrentText();
24949
- await streaming.closeTurnWithInteractiveDetection();
24950
- const replyLine = formatAgentReplyLine(finalText);
24951
- if (replyLine.length > 0) {
24952
- showInfo(replyLine);
25022
+ if (replyIsAuthFailure(finalText)) {
25023
+ await streaming.closeWithBubble(AUTH_FAILURE_MESSAGE);
25024
+ history.appendAgentReply(AUTH_FAILURE_MESSAGE);
25025
+ void history.flush();
25026
+ turnFiles.flushTurn().catch((err) => {
25027
+ log.warn("acpRunner", `turnFiles.flushTurn failed: ${describeError(err)}`);
25028
+ });
25029
+ void reportCredentialInvalid(opts);
25030
+ log.info("acpRunner", `start_task \u2190 auth-failure-in-reply id=${cmd.id.slice(0, 8)}`);
25031
+ await relay.sendResult(cmd.id, "failed", { error: "agent reply reported auth failure" });
25032
+ } else {
25033
+ await streaming.closeTurnWithInteractiveDetection();
25034
+ const replyLine = formatAgentReplyLine(finalText);
25035
+ if (replyLine.length > 0) {
25036
+ showInfo(replyLine);
25037
+ }
25038
+ history.appendAgentReply(finalText);
25039
+ void history.flush();
25040
+ turnFiles.flushTurn().catch((err) => {
25041
+ log.warn("acpRunner", `turnFiles.flushTurn failed: ${describeError(err)}`);
25042
+ });
25043
+ log.info("acpRunner", `start_task \u2190 done stopReason=${reply.stopReason ?? "?"} id=${cmd.id.slice(0, 8)}`);
25044
+ await relay.sendResult(cmd.id, "completed", { stopReason: reply.stopReason });
24953
25045
  }
24954
- history.appendAgentReply(finalText);
24955
- void history.flush();
24956
- turnFiles.flushTurn().catch((err) => {
24957
- log.warn("acpRunner", `turnFiles.flushTurn failed: ${describeError(err)}`);
24958
- });
24959
- log.info("acpRunner", `start_task \u2190 done stopReason=${reply.stopReason ?? "?"} id=${cmd.id.slice(0, 8)}`);
24960
- await relay.sendResult(cmd.id, "completed", { stopReason: reply.stopReason });
24961
25046
  } catch (err) {
24962
25047
  const hadText = streaming.getCurrentText().trim().length > 0;
24963
25048
  await recoverFromFailedTurn(client2, streaming);
@@ -28900,9 +28985,9 @@ async function probeCodeamPair(provider, workspace) {
28900
28985
  }
28901
28986
  async function stopWorkspaceFromLocal(target) {
28902
28987
  if (target.provider.id === "github-codespaces") {
28903
- const { execFile: execFile12 } = await import("child_process");
28988
+ const { execFile: execFile13 } = await import("child_process");
28904
28989
  const { promisify: promisify11 } = await import("util");
28905
- const execFileP10 = promisify11(execFile12);
28990
+ const execFileP10 = promisify11(execFile13);
28906
28991
  await execFileP10("gh", ["codespace", "stop", "-c", target.id], { maxBuffer: 8 * 1024 * 1024 });
28907
28992
  return;
28908
28993
  }
@@ -29126,7 +29211,7 @@ function checkChokidar() {
29126
29211
  }
29127
29212
  async function doctor(args2 = []) {
29128
29213
  const json = args2.includes("--json");
29129
- const cliVersion = true ? "2.41.0" : "0.0.0-dev";
29214
+ const cliVersion = true ? "2.42.0" : "0.0.0-dev";
29130
29215
  const apiBase2 = resolveApiBaseUrl();
29131
29216
  const diagnosticId = (0, import_node_crypto8.randomUUID)();
29132
29217
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -29325,7 +29410,7 @@ async function completion(args2) {
29325
29410
  // src/commands/version.ts
29326
29411
  var import_picocolors14 = __toESM(require("picocolors"));
29327
29412
  function version2() {
29328
- const v = true ? "2.41.0" : "unknown";
29413
+ const v = true ? "2.42.0" : "unknown";
29329
29414
  console.log(`${import_picocolors14.default.bold("codeam-cli")} ${import_picocolors14.default.cyan(v)}`);
29330
29415
  }
29331
29416
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.41.0",
3
+ "version": "2.42.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",