codeam-cli 2.41.1 → 2.43.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 +21 -0
  2. package/dist/index.js +229 -67
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,27 @@ 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.42.0] — 2026-06-24
8
+
9
+ ### Added
10
+
11
+ - **cli:** Confirm provider outage via status page on turn stall/failure (catch-all)
12
+
13
+ ### Fixed
14
+
15
+ - **cli:** Make agent-failure messaging honest — outage only on real provider error, re-auth on auth-notice replies
16
+ - **cli:** Keep the heartbeat punctual by detecting the git branch off the hot path
17
+
18
+ ## [2.41.0] — 2026-06-24
19
+
20
+ ### Added
21
+
22
+ - **cli:** Surface friendly provider-outage message + status-page link instead of silent hang
23
+
24
+ ### Merge
25
+
26
+ - Friendly provider-outage message + status-page link (no silent hang on provider 529/5xx)
27
+
7
28
  ## [2.40.2] — 2026-06-24
8
29
 
9
30
  ### Fixed
package/dist/index.js CHANGED
@@ -476,6 +476,13 @@ function makeConfig(baseDir) {
476
476
  matches.sort((a, b) => b.pairedAt - a.pairedAt);
477
477
  return matches[0];
478
478
  }
479
+ function setDisable1mContext2(pluginId, value) {
480
+ const c2 = load();
481
+ const s = c2.sessions.find((x) => x.pluginId === pluginId);
482
+ if (!s) return;
483
+ s.disable1mContext = value;
484
+ save(c2);
485
+ }
479
486
  function clearAll2() {
480
487
  try {
481
488
  fs.unlinkSync(file);
@@ -488,7 +495,7 @@ function makeConfig(baseDir) {
488
495
  function loadCliConfig2() {
489
496
  return load();
490
497
  }
491
- return { getConfig: getConfig2, ensurePluginId: ensurePluginId2, addSession: addSession2, removeSession: removeSession2, setActiveSession: setActiveSession2, getActiveSession: getActiveSession2, getActiveSessionForAgent: getActiveSessionForAgent2, clearAll: clearAll2, saveCliConfig: saveCliConfig2, loadCliConfig: loadCliConfig2 };
498
+ return { getConfig: getConfig2, ensurePluginId: ensurePluginId2, addSession: addSession2, removeSession: removeSession2, setActiveSession: setActiveSession2, getActiveSession: getActiveSession2, getActiveSessionForAgent: getActiveSessionForAgent2, setDisable1mContext: setDisable1mContext2, clearAll: clearAll2, saveCliConfig: saveCliConfig2, loadCliConfig: loadCliConfig2 };
492
499
  }
493
500
  var CODESPACE_ENV_KEYS = [
494
501
  "PREVIEW_TUNNEL_TOKEN",
@@ -512,7 +519,7 @@ function loadCodespaceEnv() {
512
519
  }
513
520
  }
514
521
  var _default = makeConfig();
515
- var { getConfig, ensurePluginId, addSession, removeSession, setActiveSession, getActiveSession, getActiveSessionForAgent, clearAll, saveCliConfig, loadCliConfig } = _default;
522
+ var { getConfig, ensurePluginId, addSession, removeSession, setActiveSession, getActiveSession, getActiveSessionForAgent, setDisable1mContext, clearAll, saveCliConfig, loadCliConfig } = _default;
516
523
 
517
524
  // src/commands/pair-auto.ts
518
525
  var fs41 = __toESM(require("fs"));
@@ -5390,7 +5397,7 @@ function readAnonId() {
5390
5397
  }
5391
5398
  function superProperties() {
5392
5399
  return {
5393
- cliVersion: true ? "2.41.1" : "0.0.0-dev",
5400
+ cliVersion: true ? "2.43.0" : "0.0.0-dev",
5394
5401
  nodeVersion: process.version,
5395
5402
  platform: process.platform,
5396
5403
  arch: process.arch,
@@ -5536,6 +5543,28 @@ var _execSeam = {
5536
5543
  return typeof out2 === "string" ? out2 : out2.toString("utf8");
5537
5544
  }
5538
5545
  };
5546
+ var _execSeamAsync = {
5547
+ exec: (file, args2, opts) => new Promise((resolve7, reject) => {
5548
+ (0, import_child_process.execFile)(file, args2, opts, (err, stdout) => {
5549
+ if (err) reject(err);
5550
+ else resolve7(typeof stdout === "string" ? stdout : stdout.toString("utf8"));
5551
+ });
5552
+ })
5553
+ };
5554
+ async function detectCurrentBranchAsync(cwd = process.cwd()) {
5555
+ try {
5556
+ const raw = await _execSeamAsync.exec("git", ["branch", "--show-current"], {
5557
+ cwd,
5558
+ timeout: 1e3,
5559
+ encoding: "utf8",
5560
+ windowsHide: true
5561
+ });
5562
+ const trimmed = raw.trim();
5563
+ return trimmed.length > 0 ? trimmed : null;
5564
+ } catch {
5565
+ return null;
5566
+ }
5567
+ }
5539
5568
 
5540
5569
  // src/services/command-relay.service.ts
5541
5570
  var https2 = __toESM(require("https"));
@@ -5549,7 +5578,7 @@ var os4 = __toESM(require("os"));
5549
5578
  // package.json
5550
5579
  var package_default = {
5551
5580
  name: "codeam-cli",
5552
- version: "2.41.1",
5581
+ version: "2.43.0",
5553
5582
  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
5583
  type: "commonjs",
5555
5584
  main: "dist/index.js",
@@ -6044,13 +6073,31 @@ var CommandRelayService = class {
6044
6073
  */
6045
6074
  sseWatchdog = null;
6046
6075
  sseLastByteAt = 0;
6076
+ /**
6077
+ * Last-known git branch, shipped on every heartbeat. Seeded with a
6078
+ * single SYNCHRONOUS read at `start()` (before any turn is running)
6079
+ * for an accurate first beat, then refreshed ASYNCHRONOUSLY off the
6080
+ * recurring heartbeat tick. The heartbeat POST reads this cached
6081
+ * value and never spawns `git` synchronously — so the 20 s beat
6082
+ * stays punctual even while a long agent tool call is hammering the
6083
+ * event loop. Trade-off: because the async refresh fires alongside
6084
+ * the POST (not before it), a `git checkout` propagates on the NEXT
6085
+ * beat — up to ~40 s worst case vs the old at-POST-time read. The
6086
+ * backend dedupes a stable branch, so this lag costs nothing but
6087
+ * freshness, and a punctual heartbeat matters far more.
6088
+ */
6089
+ cachedBranch = null;
6047
6090
  start() {
6048
6091
  this.cleanup();
6049
6092
  this._running = true;
6050
6093
  this.agentsRegistered = false;
6051
6094
  log.info("relay", `start pluginId=${this.pluginId.slice(0, 8)} agent=${this.agentMeta.id}`);
6095
+ this.cachedBranch = detectCurrentBranch();
6052
6096
  this.sendHeartbeat(true);
6053
- this.heartbeatTimer = setInterval(() => this.sendHeartbeat(true), 2e4);
6097
+ this.heartbeatTimer = setInterval(() => {
6098
+ void this.refreshBranch();
6099
+ this.sendHeartbeat(true);
6100
+ }, 2e4);
6054
6101
  this.agentsTimer = setInterval(() => {
6055
6102
  if (this._running && !this.agentsRegistered) this.reportAgents();
6056
6103
  }, 5e3);
@@ -6294,9 +6341,20 @@ var CommandRelayService = class {
6294
6341
  pluginId: this.pluginId,
6295
6342
  online,
6296
6343
  agentId: this.agentMeta.id,
6297
- branch: detectCurrentBranch()
6344
+ branch: this.cachedBranch
6298
6345
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
6299
6346
  }
6347
+ /**
6348
+ * Refresh {@link cachedBranch} without blocking the event loop.
6349
+ * Fire-and-forget from the heartbeat tick; failures leave the last
6350
+ * known value in place (the backend dedupes a stable branch).
6351
+ */
6352
+ async refreshBranch() {
6353
+ try {
6354
+ this.cachedBranch = await detectCurrentBranchAsync();
6355
+ } catch {
6356
+ }
6357
+ }
6300
6358
  reportAgents() {
6301
6359
  const agents = this.agentsOverride ? [...this.agentsOverride] : [
6302
6360
  {
@@ -17753,7 +17811,7 @@ async function autoUpgradeBeforeCriticalCommand() {
17753
17811
  if (process.env.NODE_ENV === "test") return;
17754
17812
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
17755
17813
  if (process.env.CI) return;
17756
- const current = true ? "2.41.1" : null;
17814
+ const current = true ? "2.43.0" : null;
17757
17815
  if (!current) return;
17758
17816
  const cache = readCache();
17759
17817
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -17770,7 +17828,7 @@ function checkForUpdates() {
17770
17828
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
17771
17829
  if (process.env.CI) return;
17772
17830
  if (!process.stdout.isTTY) return;
17773
- const current = true ? "2.41.1" : null;
17831
+ const current = true ? "2.43.0" : null;
17774
17832
  if (!current) return;
17775
17833
  const cache = readCache();
17776
17834
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -18208,7 +18266,7 @@ var defaultSpawner = (env, cwd, args2 = []) => (0, import_node_child_process14.s
18208
18266
  detached: false
18209
18267
  });
18210
18268
  function currentCliVersion() {
18211
- return true ? "2.41.1" : null;
18269
+ return true ? "2.43.0" : null;
18212
18270
  }
18213
18271
  function runCmd(cmd, args2, timeoutMs) {
18214
18272
  return new Promise((resolve7) => {
@@ -22686,7 +22744,10 @@ var AcpClient = class {
22686
22744
  );
22687
22745
  const child = (0, import_node_child_process16.spawn)(adapter.command, adapter.args, {
22688
22746
  cwd,
22689
- env: { ...process.env, PATH: augmentedPath },
22747
+ // extraEnv (e.g. CLAUDE_CODE_DISABLE_1M_CONTEXT=1 on an on-demand
22748
+ // re-spawn) layers over process.env; PATH stays last so the augmented
22749
+ // PATH always wins.
22750
+ env: { ...process.env, ...this.opts.extraEnv ?? {}, PATH: augmentedPath },
22690
22751
  stdio: ["pipe", "pipe", "pipe"]
22691
22752
  });
22692
22753
  this.child = child;
@@ -23380,6 +23441,66 @@ function buildAcpPromptBlocks(payload) {
23380
23441
  return blocks;
23381
23442
  }
23382
23443
 
23444
+ // src/agents/acp/oneMContextRecovery.ts
23445
+ var ONE_M_CONTEXT_CREDITS_RE = /usage credits required for 1m context/i;
23446
+ function looksLike1mContextCreditsError(text) {
23447
+ return ONE_M_CONTEXT_CREDITS_RE.test(text);
23448
+ }
23449
+ var ONE_M_DISABLE_OPTION = "Disable 1M context and continue";
23450
+ function shouldOfferOneMRecovery(opts) {
23451
+ return looksLike1mContextCreditsError(opts.detail) || looksLike1mContextCreditsError(opts.recentStderr) || looksLike1mContextCreditsError(opts.finalText);
23452
+ }
23453
+ function oneMRecoverySelectPrompt() {
23454
+ return {
23455
+ question: "\u26A0\uFE0F This agent's Anthropic account needs usage credits for 1M context. You can disable 1M context (uses standard 200K context) and continue.",
23456
+ options: [ONE_M_DISABLE_OPTION]
23457
+ };
23458
+ }
23459
+ function createOneMRecovery(deps) {
23460
+ let pending = null;
23461
+ const sp = oneMRecoverySelectPrompt();
23462
+ return {
23463
+ offer: async (commandId, blocks) => {
23464
+ pending = { blocks };
23465
+ await deps.publishText(sp.question);
23466
+ await deps.publishSelectPrompt(sp.question, sp.options);
23467
+ deps.appendAgentReply(sp.question);
23468
+ deps.flushHistory();
23469
+ deps.log?.(`1M-context credits gate \u2014 recovery offered id=${commandId.slice(0, 8)}`);
23470
+ await deps.sendResult(commandId, "failed", {
23471
+ error: "1M context requires usage credits \u2014 recovery offered"
23472
+ });
23473
+ },
23474
+ tryRecover: async (commandId) => {
23475
+ if (!pending) return false;
23476
+ const blocks = pending.blocks;
23477
+ pending = null;
23478
+ deps.log?.("1M-context recovery \u2192 disable + reconnect + rerun");
23479
+ await deps.reconnectWith1mDisabled();
23480
+ await deps.beginTurn();
23481
+ try {
23482
+ const reply = await deps.promptAgent(blocks);
23483
+ const finalText = deps.getCurrentText();
23484
+ await deps.closeTurn();
23485
+ deps.appendAgentReply(finalText);
23486
+ deps.flushHistory();
23487
+ await deps.sendResult(commandId, "completed", { stopReason: reply.stopReason });
23488
+ } catch (err) {
23489
+ await deps.recoverFromFailedTurn();
23490
+ const d3 = deps.describeError(err);
23491
+ const b = deps.failureBubbleFor(d3);
23492
+ if (b) {
23493
+ await deps.publishText(b);
23494
+ deps.appendAgentReply(b);
23495
+ deps.flushHistory();
23496
+ }
23497
+ await deps.sendResult(commandId, "failed", { error: d3 });
23498
+ }
23499
+ return true;
23500
+ }
23501
+ };
23502
+ }
23503
+
23383
23504
  // src/agents/acp/reconcileDelta.ts
23384
23505
  function reconcileCumulative(existing, incoming) {
23385
23506
  if (incoming.length === 0) return existing;
@@ -24450,6 +24571,22 @@ var StreamingState = class {
24450
24571
  this.flushStreamingChunks()
24451
24572
  ]);
24452
24573
  }
24574
+ /**
24575
+ * Close the turn but REPLACE the streamed reply with `bubble` as the
24576
+ * terminal chat frame. The chat pipe treats each `text` chunk's content as
24577
+ * the full (replacing) bubble body, so a final `done:true` carrying the
24578
+ * bubble overwrites whatever raw text streamed. Used when the agent's
24579
+ * completed-turn reply was itself an auth-failure notice
24580
+ * ({@link replyIsAuthFailure}) → swap the raw "Please run /login" for the
24581
+ * actionable re-auth bubble.
24582
+ */
24583
+ async closeWithBubble(bubble) {
24584
+ this.text = "";
24585
+ await Promise.all([
24586
+ this.publisher.publishOutput({ type: "text", content: bubble, done: true }),
24587
+ this.flushStreamingChunks()
24588
+ ]);
24589
+ }
24453
24590
  /**
24454
24591
  * Re-emit every open streaming-chunk buffer with `isFinal: true`
24455
24592
  * so SessionDetailScreen's bubbles flip out of "still streaming"
@@ -24617,6 +24754,10 @@ var AUTH_FAILURE_RE = /invalid authentication credentials|authentication[_ ]erro
24617
24754
  function looksLikeAuthFailure(text) {
24618
24755
  return AUTH_FAILURE_RE.test(text);
24619
24756
  }
24757
+ function replyIsAuthFailure(finalText) {
24758
+ const t2 = finalText.trim();
24759
+ return t2.length > 0 && t2.length <= 200 && looksLikeAuthFailure(t2);
24760
+ }
24620
24761
  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
24762
  var TURN_FAILURE_MESSAGE = "\u26A0\uFE0F **The agent hit an error and couldn\u2019t finish this turn.** Please send your message again.";
24622
24763
  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;
@@ -24626,46 +24767,17 @@ function looksLikeProviderOutage(text) {
24626
24767
  function agentStatusPage(agent) {
24627
24768
  const a = (agent ?? "").toLowerCase();
24628
24769
  if (a.includes("claude") || a.includes("anthropic"))
24629
- return {
24630
- vendor: "Anthropic",
24631
- url: "https://status.anthropic.com",
24632
- statusApi: "https://status.anthropic.com/api/v2/status.json"
24633
- };
24770
+ return { vendor: "Anthropic", url: "https://status.anthropic.com" };
24634
24771
  if (a.includes("codex") || a.includes("openai"))
24635
- return {
24636
- vendor: "OpenAI",
24637
- url: "https://status.openai.com",
24638
- statusApi: "https://status.openai.com/api/v2/status.json"
24639
- };
24772
+ return { vendor: "OpenAI", url: "https://status.openai.com" };
24640
24773
  if (a.includes("gemini") || a.includes("google"))
24641
24774
  return { vendor: "Google", url: "https://status.cloud.google.com" };
24642
24775
  if (a.includes("copilot"))
24643
- return {
24644
- vendor: "GitHub",
24645
- url: "https://www.githubstatus.com",
24646
- statusApi: "https://www.githubstatus.com/api/v2/status.json"
24647
- };
24776
+ return { vendor: "GitHub", url: "https://www.githubstatus.com" };
24648
24777
  if (a.includes("cursor"))
24649
- return {
24650
- vendor: "Cursor",
24651
- url: "https://status.cursor.com",
24652
- statusApi: "https://status.cursor.com/api/v2/status.json"
24653
- };
24778
+ return { vendor: "Cursor", url: "https://status.cursor.com" };
24654
24779
  return null;
24655
24780
  }
24656
- async function checkProviderStatus(agent, fetchImpl = fetch) {
24657
- const info = agentStatusPage(agent);
24658
- if (!info?.statusApi) return false;
24659
- try {
24660
- const res = await withTimeout(fetchImpl(info.statusApi), 4e3);
24661
- if (!res || !res.ok) return false;
24662
- const body = await res.json();
24663
- const indicator = body?.status?.indicator;
24664
- return typeof indicator === "string" && indicator.toLowerCase() !== "none";
24665
- } catch {
24666
- return false;
24667
- }
24668
- }
24669
24781
  function providerOutageMessage(agent) {
24670
24782
  const info = agentStatusPage(agent);
24671
24783
  const who = info ? info.vendor : "The agent provider";
@@ -24751,9 +24863,11 @@ async function runAcpSession(opts) {
24751
24863
  }
24752
24864
  });
24753
24865
  let updateCount = 0;
24754
- const client2 = new AcpClient({
24866
+ const disable1mContext = loadCliConfig().sessions.find((s) => s.pluginId === opts.pluginId)?.disable1mContext === true;
24867
+ const clientOptions = {
24755
24868
  adapter: opts.adapter,
24756
24869
  cwd: opts.cwd,
24870
+ extraEnv: disable1mContext ? { CLAUDE_CODE_DISABLE_1M_CONTEXT: "1" } : {},
24757
24871
  onSessionUpdate: (notification) => {
24758
24872
  updateCount += 1;
24759
24873
  const variant = notification.update?.sessionUpdate ?? "unknown";
@@ -24818,6 +24932,39 @@ async function runAcpSession(opts) {
24818
24932
  process.exit(1);
24819
24933
  })();
24820
24934
  }
24935
+ };
24936
+ let client2 = new AcpClient(clientOptions);
24937
+ const reconnectWith1mDisabled = async () => {
24938
+ setDisable1mContext(opts.pluginId, true);
24939
+ try {
24940
+ await client2.stop();
24941
+ } catch {
24942
+ }
24943
+ client2 = new AcpClient({ ...clientOptions, extraEnv: { CLAUDE_CODE_DISABLE_1M_CONTEXT: "1" } });
24944
+ await client2.start();
24945
+ };
24946
+ const oneMRecovery = createOneMRecovery({
24947
+ publishText: (text) => publisher.publishOutput({ type: "text", content: text, done: true }),
24948
+ publishSelectPrompt: (question, options) => publisher.publishOutput({
24949
+ type: "select_prompt",
24950
+ content: question,
24951
+ options,
24952
+ optionDescriptions: options.map(() => ""),
24953
+ currentIndex: 0,
24954
+ done: true
24955
+ }),
24956
+ sendResult: (commandId, status2, result) => relay.sendResult(commandId, status2, result),
24957
+ appendAgentReply: (text) => history.appendAgentReply(text),
24958
+ flushHistory: () => void history.flush(),
24959
+ beginTurn: () => streaming.beginTurn(),
24960
+ getCurrentText: () => streaming.getCurrentText(),
24961
+ closeTurn: () => streaming.closeTurnWithInteractiveDetection(),
24962
+ recoverFromFailedTurn: () => recoverFromFailedTurn(client2, streaming),
24963
+ reconnectWith1mDisabled,
24964
+ promptAgent: (blocks) => client2.prompt(blocks),
24965
+ failureBubbleFor: (detail) => failureBubble({ detail, recentStderr: recentStderr.join("\n"), hadText: false, agent: opts.agent }),
24966
+ describeError,
24967
+ log: (msg) => log.info("acpRunner", msg)
24821
24968
  });
24822
24969
  showInfo(`Starting ${opts.agent} via ACP adapter (${opts.adapter.requiresAgentBinary})\u2026`);
24823
24970
  const {
@@ -24912,7 +25059,8 @@ async function runAcpSession(opts) {
24912
25059
  turnFiles,
24913
25060
  getBeads,
24914
25061
  publisher,
24915
- recentStderr
25062
+ recentStderr,
25063
+ oneMRecovery
24916
25064
  );
24917
25065
  },
24918
25066
  { id: opts.agent, name: opts.agent, displayName: opts.agent }
@@ -24936,7 +25084,7 @@ async function runAcpSession(opts) {
24936
25084
  await new Promise(() => {
24937
25085
  });
24938
25086
  }
24939
- async function handleCommand(cmd, client2, relay, acpSessionId, models, streaming, opts, history, jsonlHistory, agentCaps, turnFiles, getBeads, publisher, recentStderr) {
25087
+ async function handleCommand(cmd, client2, relay, acpSessionId, models, streaming, opts, history, jsonlHistory, agentCaps, turnFiles, getBeads, publisher, recentStderr, oneMRecovery) {
24940
25088
  switch (cmd.type) {
24941
25089
  case "beads_action": {
24942
25090
  const beads = getBeads();
@@ -24977,34 +25125,47 @@ async function handleCommand(cmd, client2, relay, acpSessionId, models, streamin
24977
25125
  try {
24978
25126
  const reply = await client2.prompt(blocks);
24979
25127
  const finalText = streaming.getCurrentText();
24980
- await streaming.closeTurnWithInteractiveDetection();
24981
- const replyLine = formatAgentReplyLine(finalText);
24982
- if (replyLine.length > 0) {
24983
- showInfo(replyLine);
25128
+ if (replyIsAuthFailure(finalText)) {
25129
+ await streaming.closeWithBubble(AUTH_FAILURE_MESSAGE);
25130
+ history.appendAgentReply(AUTH_FAILURE_MESSAGE);
25131
+ void history.flush();
25132
+ turnFiles.flushTurn().catch((err) => {
25133
+ log.warn("acpRunner", `turnFiles.flushTurn failed: ${describeError(err)}`);
25134
+ });
25135
+ void reportCredentialInvalid(opts);
25136
+ log.info("acpRunner", `start_task \u2190 auth-failure-in-reply id=${cmd.id.slice(0, 8)}`);
25137
+ await relay.sendResult(cmd.id, "failed", { error: "agent reply reported auth failure" });
25138
+ } else if (shouldOfferOneMRecovery({ detail: "", recentStderr: recentStderr.join("\n"), finalText })) {
25139
+ await oneMRecovery.offer(cmd.id, blocks);
25140
+ } else {
25141
+ await streaming.closeTurnWithInteractiveDetection();
25142
+ const replyLine = formatAgentReplyLine(finalText);
25143
+ if (replyLine.length > 0) {
25144
+ showInfo(replyLine);
25145
+ }
25146
+ history.appendAgentReply(finalText);
25147
+ void history.flush();
25148
+ turnFiles.flushTurn().catch((err) => {
25149
+ log.warn("acpRunner", `turnFiles.flushTurn failed: ${describeError(err)}`);
25150
+ });
25151
+ log.info("acpRunner", `start_task \u2190 done stopReason=${reply.stopReason ?? "?"} id=${cmd.id.slice(0, 8)}`);
25152
+ await relay.sendResult(cmd.id, "completed", { stopReason: reply.stopReason });
24984
25153
  }
24985
- history.appendAgentReply(finalText);
24986
- void history.flush();
24987
- turnFiles.flushTurn().catch((err) => {
24988
- log.warn("acpRunner", `turnFiles.flushTurn failed: ${describeError(err)}`);
24989
- });
24990
- log.info("acpRunner", `start_task \u2190 done stopReason=${reply.stopReason ?? "?"} id=${cmd.id.slice(0, 8)}`);
24991
- await relay.sendResult(cmd.id, "completed", { stopReason: reply.stopReason });
24992
25154
  } catch (err) {
24993
25155
  const hadText = streaming.getCurrentText().trim().length > 0;
24994
25156
  await recoverFromFailedTurn(client2, streaming);
24995
25157
  const detail = describeError(err);
24996
25158
  log.warn("acpRunner", `prompt failed: ${detail}`);
24997
- let bubble = failureBubble({
25159
+ if (shouldOfferOneMRecovery({ detail, recentStderr: recentStderr.join("\n"), finalText: "" })) {
25160
+ await oneMRecovery.offer(cmd.id, blocks);
25161
+ return;
25162
+ }
25163
+ const bubble = failureBubble({
24998
25164
  detail,
24999
25165
  recentStderr: recentStderr.join("\n"),
25000
25166
  hadText,
25001
25167
  agent: opts.agent
25002
25168
  });
25003
- if (bubble !== AUTH_FAILURE_MESSAGE && bubble !== providerOutageMessage(opts.agent)) {
25004
- if (await checkProviderStatus(opts.agent)) {
25005
- bubble = providerOutageMessage(opts.agent);
25006
- }
25007
- }
25008
25169
  if (bubble) {
25009
25170
  await publisher.publishOutput({ type: "text", content: bubble, done: true });
25010
25171
  history.appendAgentReply(bubble);
@@ -25093,6 +25254,7 @@ async function handleCommand(cmd, client2, relay, acpSessionId, models, streamin
25093
25254
  return;
25094
25255
  }
25095
25256
  case "select_option": {
25257
+ if (await oneMRecovery.tryRecover(cmd.id)) return;
25096
25258
  const payload = cmd.payload;
25097
25259
  const index = typeof payload?.index === "number" ? payload.index : 0;
25098
25260
  const offset = typeof payload?.from === "number" ? payload.from : 0;
@@ -28936,9 +29098,9 @@ async function probeCodeamPair(provider, workspace) {
28936
29098
  }
28937
29099
  async function stopWorkspaceFromLocal(target) {
28938
29100
  if (target.provider.id === "github-codespaces") {
28939
- const { execFile: execFile12 } = await import("child_process");
29101
+ const { execFile: execFile13 } = await import("child_process");
28940
29102
  const { promisify: promisify11 } = await import("util");
28941
- const execFileP10 = promisify11(execFile12);
29103
+ const execFileP10 = promisify11(execFile13);
28942
29104
  await execFileP10("gh", ["codespace", "stop", "-c", target.id], { maxBuffer: 8 * 1024 * 1024 });
28943
29105
  return;
28944
29106
  }
@@ -29162,7 +29324,7 @@ function checkChokidar() {
29162
29324
  }
29163
29325
  async function doctor(args2 = []) {
29164
29326
  const json = args2.includes("--json");
29165
- const cliVersion = true ? "2.41.1" : "0.0.0-dev";
29327
+ const cliVersion = true ? "2.43.0" : "0.0.0-dev";
29166
29328
  const apiBase2 = resolveApiBaseUrl();
29167
29329
  const diagnosticId = (0, import_node_crypto8.randomUUID)();
29168
29330
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -29361,7 +29523,7 @@ async function completion(args2) {
29361
29523
  // src/commands/version.ts
29362
29524
  var import_picocolors14 = __toESM(require("picocolors"));
29363
29525
  function version2() {
29364
- const v = true ? "2.41.1" : "unknown";
29526
+ const v = true ? "2.43.0" : "unknown";
29365
29527
  console.log(`${import_picocolors14.default.bold("codeam-cli")} ${import_picocolors14.default.cyan(v)}`);
29366
29528
  }
29367
29529
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.41.1",
3
+ "version": "2.43.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",