codeam-cli 2.56.0 → 2.57.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ 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.56.0] — 2026-07-07
8
+
9
+ ### Changed
10
+
11
+ - **cli:** Runner.ts dispatch table — AcpCommandContext replaces the 19-param handleCommand (Phase 3)
12
+ - **cli:** Split host-agent.ts into commands/host/ modules + one spawnHeadroomProxy (Phase 3)
13
+ - **cli:** Decompose previewStartH into stages + event-driven tunnel wait (Phase 3 wave 2)
14
+
7
15
  ## [2.55.1] — 2026-07-07
8
16
 
9
17
  ## [2.54.0] — 2026-07-06
package/dist/index.js CHANGED
@@ -5621,7 +5621,7 @@ function readAnonId() {
5621
5621
  }
5622
5622
  function superProperties() {
5623
5623
  return {
5624
- cliVersion: true ? "2.56.0" : "0.0.0-dev",
5624
+ cliVersion: true ? "2.57.0" : "0.0.0-dev",
5625
5625
  nodeVersion: process.version,
5626
5626
  platform: process.platform,
5627
5627
  arch: process.arch,
@@ -5802,7 +5802,7 @@ var os4 = __toESM(require("os"));
5802
5802
  // package.json
5803
5803
  var package_default = {
5804
5804
  name: "codeam-cli",
5805
- version: "2.56.0",
5805
+ version: "2.57.0",
5806
5806
  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.",
5807
5807
  type: "commonjs",
5808
5808
  main: "dist/index.js",
@@ -6850,7 +6850,7 @@ var CommandRelayService = class {
6850
6850
  // fresh + clear the "CLI update available" banner after a self-update
6851
6851
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
6852
6852
  // pair/reconnect). Older backends ignore the extra field.
6853
- ..."2.56.0" ? { ideVersion: "2.56.0" } : {}
6853
+ ..."2.57.0" ? { ideVersion: "2.57.0" } : {}
6854
6854
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
6855
6855
  }
6856
6856
  /**
@@ -15878,7 +15878,7 @@ async function autoUpgradeBeforeCriticalCommand() {
15878
15878
  if (process.env.NODE_ENV === "test") return;
15879
15879
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
15880
15880
  if (process.env.CI) return;
15881
- const current = true ? "2.56.0" : null;
15881
+ const current = true ? "2.57.0" : null;
15882
15882
  if (!current) return;
15883
15883
  const cache = readCache();
15884
15884
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -15895,7 +15895,7 @@ function checkForUpdates() {
15895
15895
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
15896
15896
  if (process.env.CI) return;
15897
15897
  if (!process.stdout.isTTY) return;
15898
- const current = true ? "2.56.0" : null;
15898
+ const current = true ? "2.57.0" : null;
15899
15899
  if (!current) return;
15900
15900
  const cache = readCache();
15901
15901
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -15915,7 +15915,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
15915
15915
  var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
15916
15916
  var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
15917
15917
  function currentCliVersion() {
15918
- return true ? "2.56.0" : null;
15918
+ return true ? "2.57.0" : null;
15919
15919
  }
15920
15920
  function runCmd(cmd, args2, timeoutMs) {
15921
15921
  return new Promise((resolve7) => {
@@ -25478,67 +25478,6 @@ var AcpPublisher = class {
25478
25478
  }
25479
25479
  };
25480
25480
 
25481
- // src/agents/acp/oneMContextRecovery.ts
25482
- var ONE_M_CONTEXT_CREDITS_RE = /usage credits required for 1m context/i;
25483
- function looksLike1mContextCreditsError(text) {
25484
- return ONE_M_CONTEXT_CREDITS_RE.test(text);
25485
- }
25486
- var ONE_M_DISABLE_OPTION = "Disable 1M context and continue";
25487
- function shouldOfferOneMRecovery(opts) {
25488
- return looksLike1mContextCreditsError(opts.detail) || looksLike1mContextCreditsError(opts.recentStderr) || looksLike1mContextCreditsError(opts.finalText);
25489
- }
25490
- function oneMRecoverySelectPrompt() {
25491
- return {
25492
- 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.",
25493
- options: [ONE_M_DISABLE_OPTION]
25494
- };
25495
- }
25496
- function createOneMRecovery(deps) {
25497
- let pending = null;
25498
- const sp = oneMRecoverySelectPrompt();
25499
- return {
25500
- offer: async (commandId, blocks) => {
25501
- pending = { blocks };
25502
- await deps.publishText(sp.question);
25503
- await deps.publishSelectPrompt(sp.question, sp.options);
25504
- await deps.publishAwaitingAnswer(sp.question, sp.options);
25505
- deps.appendAgentReply(sp.question);
25506
- deps.flushHistory();
25507
- deps.log?.(`1M-context credits gate \u2014 recovery offered id=${commandId.slice(0, 8)}`);
25508
- await deps.sendResult(commandId, "failed", {
25509
- error: "1M context requires usage credits \u2014 recovery offered"
25510
- });
25511
- },
25512
- tryRecover: async (commandId) => {
25513
- if (!pending) return false;
25514
- const blocks = pending.blocks;
25515
- pending = null;
25516
- deps.log?.("1M-context recovery \u2192 disable + reconnect + rerun");
25517
- await deps.reconnectWith1mDisabled();
25518
- await deps.beginTurn();
25519
- try {
25520
- const reply = await deps.promptAgent(blocks);
25521
- const finalText = deps.getCurrentText();
25522
- await deps.closeTurn();
25523
- deps.appendAgentReply(finalText);
25524
- deps.flushHistory();
25525
- await deps.sendResult(commandId, "completed", { stopReason: reply.stopReason });
25526
- } catch (err) {
25527
- await deps.recoverFromFailedTurn();
25528
- const d3 = deps.describeError(err);
25529
- const b = deps.failureBubbleFor(d3);
25530
- if (b) {
25531
- await deps.publishText(b);
25532
- deps.appendAgentReply(b);
25533
- deps.flushHistory();
25534
- }
25535
- await deps.sendResult(commandId, "failed", { error: d3 });
25536
- }
25537
- return true;
25538
- }
25539
- };
25540
- }
25541
-
25542
25481
  // src/agents/acp/budgetRecovery.ts
25543
25482
  var BUDGET_EXCEEDED_RE = /budget exceeded for (\w+) period/i;
25544
25483
  function looksLikeBudgetExceeded(text) {
@@ -26506,6 +26445,50 @@ function withTimeout(p2, ms) {
26506
26445
  });
26507
26446
  }
26508
26447
 
26448
+ // src/agents/acp/oneMContextRecovery.ts
26449
+ var ONE_M_CONTEXT_CREDITS_RE = /usage credits required for 1m context/i;
26450
+ function looksLike1mContextCreditsError(text) {
26451
+ return ONE_M_CONTEXT_CREDITS_RE.test(text);
26452
+ }
26453
+ function shouldOfferOneMRecovery(opts) {
26454
+ return looksLike1mContextCreditsError(opts.detail) || looksLike1mContextCreditsError(opts.recentStderr) || looksLike1mContextCreditsError(opts.finalText);
26455
+ }
26456
+
26457
+ // src/agents/acp/agent-hooks.ts
26458
+ function replyIsCursorUpgradeRequired(finalText) {
26459
+ const t2 = finalText.trim().toLowerCase();
26460
+ if (t2.length === 0 || t2.length > 200) return false;
26461
+ return t2.includes("upgrade your plan to continue") || t2.includes("upgrade your plan") && t2.includes("continue");
26462
+ }
26463
+ function isGeminiIneligibleTier(haystack) {
26464
+ return /IneligibleTierError|UNSUPPORTED_CLIENT|no longer supported for Gemini Code Assist|not eligible for Gemini Code Assist/i.test(
26465
+ haystack
26466
+ );
26467
+ }
26468
+ var ACP_AGENT_HOOKS = {
26469
+ claude: {
26470
+ statusPage: { vendor: "Anthropic", url: "https://status.anthropic.com" }
26471
+ },
26472
+ codex: {
26473
+ statusPage: { vendor: "OpenAI", url: "https://status.openai.com" },
26474
+ startupExtraEnv: ({ autoApprovePermissions }) => autoApprovePermissions ? { INITIAL_AGENT_MODE: "agent-full-access" } : {}
26475
+ },
26476
+ gemini: {
26477
+ statusPage: { vendor: "Google", url: "https://status.cloud.google.com" },
26478
+ classifyStartupFailure: (haystack) => isGeminiIneligibleTier(haystack) ? "ineligible_tier" : null
26479
+ },
26480
+ copilot: {
26481
+ statusPage: { vendor: "GitHub", url: "https://www.githubstatus.com" }
26482
+ },
26483
+ cursor: {
26484
+ statusPage: { vendor: "Cursor", url: "https://status.cursor.com" },
26485
+ classifyCompletedReply: (finalText) => replyIsCursorUpgradeRequired(finalText) ? "upgrade_required" : null
26486
+ }
26487
+ };
26488
+ function agentHooks(agent) {
26489
+ return ACP_AGENT_HOOKS[agent];
26490
+ }
26491
+
26509
26492
  // src/agents/acp/failure-messages.ts
26510
26493
  function describeError(err) {
26511
26494
  if (err instanceof Error) return err.message;
@@ -26520,11 +26503,6 @@ function replyIsAuthFailure(finalText) {
26520
26503
  return t2.length > 0 && t2.length <= 200 && looksLikeAuthFailure(t2);
26521
26504
  }
26522
26505
  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.";
26523
- function replyIsCursorUpgradeRequired(finalText) {
26524
- const t2 = finalText.trim().toLowerCase();
26525
- if (t2.length === 0 || t2.length > 200) return false;
26526
- return t2.includes("upgrade your plan to continue") || t2.includes("upgrade your plan") && t2.includes("continue");
26527
- }
26528
26506
  var CURSOR_UPGRADE_MESSAGE = "\u26A1 **Cursor needs a paid plan to run the agent.**\n\nThe headless Cursor Agent requires Cursor **Pro** \u2014 your Free plan\u2019s included usage does NOT cover Agent runs, even with quota left. This is your Cursor account (not CodeAgent). Upgrade, then send your message again:\n\n[Upgrade to Cursor Pro \u2192](https://cursor.com/dashboard)";
26529
26507
  var ONE_M_CREDITS_MESSAGE = "\u{1F504} **Reconnect your Claude subscription to continue.**\n\nClaude requested 1M-context but your account doesn\u2019t have the usage credits for it on this credential. Reconnecting refreshes your subscription so the agent can keep going \u2014 disabling 1M context won\u2019t fix a credits gate.\n\nTap [Reconnect this agent](codeam://reauth) to reconnect your Claude subscription in Profile \u203A Agents, then send your message again.";
26530
26508
  var TURN_FAILURE_MESSAGE = "\u26A0\uFE0F **The agent hit an error and couldn\u2019t finish this turn.** Please send your message again.";
@@ -26534,17 +26512,8 @@ function looksLikeProviderOutage(text) {
26534
26512
  }
26535
26513
  function agentStatusPage(agent) {
26536
26514
  const a = (agent ?? "").toLowerCase();
26537
- if (a.includes("claude") || a.includes("anthropic"))
26538
- return { vendor: "Anthropic", url: "https://status.anthropic.com" };
26539
- if (a.includes("codex") || a.includes("openai"))
26540
- return { vendor: "OpenAI", url: "https://status.openai.com" };
26541
- if (a.includes("gemini") || a.includes("google"))
26542
- return { vendor: "Google", url: "https://status.cloud.google.com" };
26543
- if (a.includes("copilot"))
26544
- return { vendor: "GitHub", url: "https://www.githubstatus.com" };
26545
- if (a.includes("cursor"))
26546
- return { vendor: "Cursor", url: "https://status.cursor.com" };
26547
- return null;
26515
+ const id = a.includes("claude") || a.includes("anthropic") ? "claude" : a.includes("codex") || a.includes("openai") ? "codex" : a.includes("gemini") || a.includes("google") ? "gemini" : a.includes("copilot") ? "copilot" : a.includes("cursor") ? "cursor" : null;
26516
+ return id ? agentHooks(id)?.statusPage ?? null : null;
26548
26517
  }
26549
26518
  function providerOutageMessage(agent) {
26550
26519
  const info = agentStatusPage(agent);
@@ -26555,15 +26524,10 @@ The agent provider returned an overload/outage error, so this turn couldn\u2019t
26555
26524
 
26556
26525
  Follow the status here: [${info.url}](${info.url})` : "");
26557
26526
  }
26558
- function isGeminiIneligibleTier(haystack) {
26559
- return /IneligibleTierError|UNSUPPORTED_CLIENT|no longer supported for Gemini Code Assist|not eligible for Gemini Code Assist/i.test(
26560
- haystack
26561
- );
26562
- }
26563
26527
  function startupFailureMessage(agent, detail, recentStderr) {
26564
26528
  const haystack = `${detail}
26565
26529
  ${recentStderr}`;
26566
- if (agent === "gemini" && isGeminiIneligibleTier(haystack)) {
26530
+ if (agentHooks(agent)?.classifyStartupFailure?.(haystack) === "ineligible_tier") {
26567
26531
  return [
26568
26532
  "\u26A0\uFE0F **Gemini couldn't start \u2014 your Google account isn't eligible.**",
26569
26533
  "",
@@ -26750,19 +26714,13 @@ async function handleGetConversation(args2) {
26750
26714
  }
26751
26715
  function buildLegacyContextForACP(opts, relay, runtime) {
26752
26716
  return {
26753
- outputSvc: null,
26754
- agent: null,
26755
- historySvc: null,
26756
26717
  runtime,
26757
26718
  relay,
26758
- setKeepAlive: null,
26759
- keepAliveCtx: null,
26760
26719
  pluginId: opts.pluginId,
26761
26720
  sessionId: opts.sessionId,
26762
26721
  // The running ACP agent (claude/codex/gemini/cursor). REQUIRED: headroom_configure
26763
26722
  // resolves the agent from ctx.agentId; without it the enable gate sees '' and
26764
- // returns {supported:false} for a real Claude session (the `as unknown` cast
26765
- // below previously hid this missing field from the type checker).
26723
+ // returns {supported:false} for a real Claude session.
26766
26724
  agentId: opts.agent,
26767
26725
  pluginAuthToken: opts.pluginAuthToken
26768
26726
  };
@@ -26820,7 +26778,7 @@ async function startTaskH(ctx) {
26820
26778
  try {
26821
26779
  const reply = await client2.prompt(blocks);
26822
26780
  const finalText = streaming.getCurrentText();
26823
- if (opts.agent === "cursor" && replyIsCursorUpgradeRequired(finalText)) {
26781
+ if (agentHooks(opts.agent)?.classifyCompletedReply?.(finalText) === "upgrade_required") {
26824
26782
  await streaming.closeWithBubble(CURSOR_UPGRADE_MESSAGE);
26825
26783
  history.appendAgentReply(CURSOR_UPGRADE_MESSAGE);
26826
26784
  void history.flush();
@@ -26989,8 +26947,7 @@ async function ackEmptyH(ctx) {
26989
26947
  return;
26990
26948
  }
26991
26949
  async function selectOptionH(ctx) {
26992
- const { cmd, client: client2, relay, streaming, history, oneMRecovery, budgetRecovery } = ctx;
26993
- if (await oneMRecovery.tryRecover(cmd.id)) return;
26950
+ const { cmd, client: client2, relay, streaming, history, budgetRecovery } = ctx;
26994
26951
  const payload = cmd.payload;
26995
26952
  const index = typeof payload?.index === "number" ? payload.index : 0;
26996
26953
  const offset = typeof payload?.from === "number" ? payload.from : 0;
@@ -27710,9 +27667,12 @@ function buildRelaunchProxyEnv(baseEnv) {
27710
27667
  function computeAdapterExtraEnv(params) {
27711
27668
  const env = {};
27712
27669
  if (params.disable1mContext) env.CLAUDE_CODE_DISABLE_1M_CONTEXT = "1";
27713
- if (params.agent === "codex" && params.autoApprovePermissions) {
27714
- env.INITIAL_AGENT_MODE = "agent-full-access";
27715
- }
27670
+ Object.assign(
27671
+ env,
27672
+ agentHooks(params.agent)?.startupExtraEnv?.({
27673
+ autoApprovePermissions: params.autoApprovePermissions
27674
+ }) ?? {}
27675
+ );
27716
27676
  return env;
27717
27677
  }
27718
27678
  async function runAcpSession(opts) {
@@ -27818,48 +27778,7 @@ async function runAcpSession(opts) {
27818
27778
  })();
27819
27779
  }
27820
27780
  };
27821
- let client2 = new AcpClient(clientOptions);
27822
- const reconnectWith1mDisabled = async () => {
27823
- setDisable1mContext(opts.pluginId, true);
27824
- try {
27825
- await client2.stop();
27826
- } catch {
27827
- }
27828
- client2 = new AcpClient({
27829
- ...clientOptions,
27830
- extraEnv: { ...extraEnv, CLAUDE_CODE_DISABLE_1M_CONTEXT: "1" }
27831
- });
27832
- await client2.start();
27833
- };
27834
- const oneMRecovery = createOneMRecovery({
27835
- publishText: (text) => publisher.publishOutput({ type: "text", content: text, done: true }),
27836
- publishSelectPrompt: (question, options) => publisher.publishOutput({
27837
- type: "select_prompt",
27838
- content: question,
27839
- options,
27840
- optionDescriptions: options.map(() => ""),
27841
- currentIndex: 0,
27842
- done: true
27843
- }),
27844
- // The button driver — mobile renders the tappable option from this
27845
- // awaiting-answer event; the user's tap returns as a `select_option`
27846
- // command, caught at the top of the select_option case by tryRecover.
27847
- publishAwaitingAnswer: (prompt, options) => publisher.publishAwaitingAnswer({ questionId: (0, import_node_crypto7.randomUUID)(), prompt, options }),
27848
- sendResult: (commandId, status2, result) => relay.sendResult(commandId, status2, result),
27849
- appendAgentReply: (text) => history.appendAgentReply(text),
27850
- flushHistory: () => void history.flush(),
27851
- beginTurn: () => streaming.beginTurn(),
27852
- getCurrentText: () => streaming.getCurrentText(),
27853
- closeTurn: async () => {
27854
- await streaming.closeTurnWithInteractiveDetection();
27855
- },
27856
- recoverFromFailedTurn: () => recoverFromFailedTurn(client2, streaming),
27857
- reconnectWith1mDisabled,
27858
- promptAgent: (blocks) => client2.prompt(blocks),
27859
- failureBubbleFor: (detail) => failureBubble({ detail, recentStderr: recentStderr.join("\n"), hadText: false, agent: opts.agent }),
27860
- describeError,
27861
- log: (msg) => log.info("acpRunner", msg)
27862
- });
27781
+ const client2 = new AcpClient(clientOptions);
27863
27782
  let _budgetReachedPosted = false;
27864
27783
  const relaunchProxyWithoutBudget = async () => {
27865
27784
  const { spawn: spawn38 } = await import("child_process");
@@ -28013,7 +27932,6 @@ async function runAcpSession(opts) {
28013
27932
  getBeads,
28014
27933
  publisher,
28015
27934
  recentStderr,
28016
- oneMRecovery,
28017
27935
  budgetRecovery,
28018
27936
  { get: () => _budgetReachedPosted, set: (v) => {
28019
27937
  _budgetReachedPosted = v;
@@ -28052,7 +27970,7 @@ async function runAcpSession(opts) {
28052
27970
  await new Promise(() => {
28053
27971
  });
28054
27972
  }
28055
- async function handleCommand(cmd, client2, relay, acpSessionId, models, streaming, opts, history, jsonlHistory, agentCaps, turnFiles, getBeads, publisher, recentStderr, oneMRecovery, budgetRecovery, budgetReachedFlag) {
27973
+ async function handleCommand(cmd, client2, relay, acpSessionId, models, streaming, opts, history, jsonlHistory, agentCaps, turnFiles, getBeads, publisher, recentStderr, budgetRecovery, budgetReachedFlag) {
28056
27974
  await dispatchAcpCommand({
28057
27975
  cmd,
28058
27976
  client: client2,
@@ -28068,7 +27986,6 @@ async function handleCommand(cmd, client2, relay, acpSessionId, models, streamin
28068
27986
  getBeads,
28069
27987
  publisher,
28070
27988
  recentStderr,
28071
- oneMRecovery,
28072
27989
  budgetRecovery,
28073
27990
  budgetReachedFlag
28074
27991
  });
@@ -31970,7 +31887,7 @@ function checkChokidar() {
31970
31887
  }
31971
31888
  async function doctor(args2 = []) {
31972
31889
  const json = args2.includes("--json");
31973
- const cliVersion = true ? "2.56.0" : "0.0.0-dev";
31890
+ const cliVersion = true ? "2.57.0" : "0.0.0-dev";
31974
31891
  const apiBase2 = resolveApiBaseUrl();
31975
31892
  const diagnosticId = (0, import_node_crypto8.randomUUID)();
31976
31893
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -32169,7 +32086,7 @@ async function completion(args2) {
32169
32086
  // src/commands/version.ts
32170
32087
  var import_picocolors15 = __toESM(require("picocolors"));
32171
32088
  function version2() {
32172
- const v = true ? "2.56.0" : "unknown";
32089
+ const v = true ? "2.57.0" : "unknown";
32173
32090
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
32174
32091
  }
32175
32092
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.56.0",
3
+ "version": "2.57.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",