codeam-cli 2.27.5 → 2.27.7

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 +12 -0
  2. package/dist/index.js +117 -19
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ 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.27.6] — 2026-06-06
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** ACP runner — ack every relay command + body envelope for auth
12
+
13
+ ## [2.27.5] — 2026-06-06
14
+
15
+ ### Chore
16
+
17
+ - **cli:** Drop cursor-agent-acp — pulls deprecated SDK
18
+
7
19
  ## [2.27.4] — 2026-06-06
8
20
 
9
21
  ### Tests
package/dist/index.js CHANGED
@@ -498,7 +498,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
498
498
  // package.json
499
499
  var package_default = {
500
500
  name: "codeam-cli",
501
- version: "2.27.5",
501
+ version: "2.27.7",
502
502
  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.",
503
503
  type: "commonjs",
504
504
  main: "dist/index.js",
@@ -5873,7 +5873,7 @@ function readAnonId() {
5873
5873
  }
5874
5874
  function superProperties() {
5875
5875
  return {
5876
- cliVersion: true ? "2.27.5" : "0.0.0-dev",
5876
+ cliVersion: true ? "2.27.7" : "0.0.0-dev",
5877
5877
  nodeVersion: process.version,
5878
5878
  platform: process.platform,
5879
5879
  arch: process.arch,
@@ -12058,6 +12058,7 @@ var fs21 = __toESM(require("fs/promises"));
12058
12058
  var import_node_stream = require("stream");
12059
12059
  var import_sdk = require("@agentclientprotocol/sdk");
12060
12060
  var PROTOCOL_VERSION2 = 1;
12061
+ var PROMPT_TIMEOUT_MS = 9e4;
12061
12062
  var CLIENT_CAPABILITIES = {
12062
12063
  fs: { readTextFile: true, writeTextFile: true },
12063
12064
  terminal: false
@@ -12079,6 +12080,10 @@ var AcpClient = class {
12079
12080
  async start() {
12080
12081
  if (this.child) throw new Error("AcpClient already started");
12081
12082
  const { adapter, cwd } = this.opts;
12083
+ log.info(
12084
+ "acpClient",
12085
+ `spawn cmd=${adapter.command} args=[${adapter.args.join(",")}] cwd=${cwd}`
12086
+ );
12082
12087
  const child = (0, import_node_child_process11.spawn)(adapter.command, adapter.args, {
12083
12088
  cwd,
12084
12089
  env: process.env,
@@ -12089,7 +12094,10 @@ var AcpClient = class {
12089
12094
  child.stderr?.on("data", (chunk2) => {
12090
12095
  for (const line of chunk2.split(/\r?\n/)) {
12091
12096
  const trimmed = line.trim();
12092
- if (trimmed) this.opts.onStderr?.(trimmed);
12097
+ if (trimmed) {
12098
+ log.info("acpAdapter", trimmed);
12099
+ this.opts.onStderr?.(trimmed);
12100
+ }
12093
12101
  }
12094
12102
  });
12095
12103
  child.on("exit", (code, signal) => {
@@ -12107,15 +12115,22 @@ var AcpClient = class {
12107
12115
  (_agent) => this.buildClient(),
12108
12116
  stream
12109
12117
  );
12118
+ log.info("acpClient", "initialize \u2192 sending");
12110
12119
  const initialize = await this.connection.initialize({
12111
12120
  protocolVersion: PROTOCOL_VERSION2,
12112
12121
  clientCapabilities: CLIENT_CAPABILITIES
12113
12122
  });
12123
+ log.info(
12124
+ "acpClient",
12125
+ `initialize \u2190 ok protocolVersion=${initialize.protocolVersion} agentCaps=${JSON.stringify(initialize.agentCapabilities ?? {}).slice(0, 200)}`
12126
+ );
12127
+ log.info("acpClient", "newSession \u2192 sending");
12114
12128
  const newSession = await this.connection.newSession({
12115
12129
  cwd,
12116
12130
  mcpServers: []
12117
12131
  });
12118
12132
  this.sessionId = newSession.sessionId;
12133
+ log.info("acpClient", `newSession \u2190 ok sessionId=${newSession.sessionId.slice(0, 8)}`);
12119
12134
  return { sessionId: newSession.sessionId, initialize };
12120
12135
  }
12121
12136
  /**
@@ -12123,15 +12138,49 @@ var AcpClient = class {
12123
12138
  * {@link PromptResponse} which carries the agent's stop reason
12124
12139
  * once the turn finishes. Session/update notifications keep
12125
12140
  * arriving on `onSessionUpdate` while the turn streams.
12141
+ *
12142
+ * Wrapped in a hard timeout because adapters CAN hang silently
12143
+ * when their underlying agent's auth/network is broken — without
12144
+ * a ceiling the relay command sits "pending" forever and mobile
12145
+ * shows a permanent "Thinking…" spinner with no way to recover.
12126
12146
  */
12127
12147
  async prompt(text) {
12128
12148
  if (!this.connection || !this.sessionId) {
12129
12149
  throw new Error("AcpClient.prompt called before start()");
12130
12150
  }
12131
- return this.connection.prompt({
12151
+ log.info(
12152
+ "acpClient",
12153
+ `prompt \u2192 session=${this.sessionId.slice(0, 8)} chars=${text.length}`
12154
+ );
12155
+ const t0 = Date.now();
12156
+ const send = this.connection.prompt({
12132
12157
  sessionId: this.sessionId,
12133
12158
  prompt: [{ type: "text", text }]
12134
12159
  });
12160
+ const timeout = new Promise((_resolve, reject) => {
12161
+ const id = setTimeout(() => {
12162
+ reject(
12163
+ new Error(
12164
+ `ACP prompt timed out after ${PROMPT_TIMEOUT_MS / 1e3}s \u2014 adapter never responded. Likely the underlying agent's auth or network is misconfigured; check the adapter stderr lines above (acpAdapter tag) for the actual error.`
12165
+ )
12166
+ );
12167
+ }, PROMPT_TIMEOUT_MS);
12168
+ void send.finally(() => clearTimeout(id));
12169
+ });
12170
+ try {
12171
+ const result = await Promise.race([send, timeout]);
12172
+ log.info(
12173
+ "acpClient",
12174
+ `prompt \u2190 ok stopReason=${result.stopReason ?? "?"} elapsedMs=${Date.now() - t0}`
12175
+ );
12176
+ return result;
12177
+ } catch (err) {
12178
+ log.warn(
12179
+ "acpClient",
12180
+ `prompt \u2190 failed elapsedMs=${Date.now() - t0} err=${err instanceof Error ? err.message : String(err)}`
12181
+ );
12182
+ throw err;
12183
+ }
12135
12184
  }
12136
12185
  /**
12137
12186
  * Cancel the in-flight prompt turn. Notification — no response.
@@ -12324,6 +12373,21 @@ var AcpPublisher = class {
12324
12373
  opts;
12325
12374
  apiBase;
12326
12375
  headers;
12376
+ /**
12377
+ * Wrap the event with `sessionId` + `pluginId` at the top level.
12378
+ * The backend's `PluginAuthGuard` reads both fields from the JSON
12379
+ * body even when `X-Plugin-Auth-Token` is set on the header and
12380
+ * `:sessionId` is on the URL path. Without the body fields it
12381
+ * rejects every POST with `PLUGIN_TOKEN_REQUIRED` — same shape the
12382
+ * legacy `streaming-emitter.service.ts` `postWithRetries` uses.
12383
+ */
12384
+ envelope(event) {
12385
+ return JSON.stringify({
12386
+ sessionId: this.opts.sessionId,
12387
+ pluginId: this.opts.pluginId,
12388
+ ...event
12389
+ });
12390
+ }
12327
12391
  /**
12328
12392
  * Fire-and-forget chunk POST. The backend's per-user SSE bus
12329
12393
  * forwards each chunk to mobile/landing within ~20 ms (PRO) /
@@ -12336,7 +12400,7 @@ var AcpPublisher = class {
12336
12400
  const { statusCode, body } = await _transport2.post(
12337
12401
  url,
12338
12402
  this.headers,
12339
- JSON.stringify(event)
12403
+ this.envelope(event)
12340
12404
  );
12341
12405
  if (statusCode < 200 || statusCode >= 300) {
12342
12406
  log.warn("acpPublisher", `chunk status=${statusCode} body=${body.slice(0, 200)}`);
@@ -12357,7 +12421,7 @@ var AcpPublisher = class {
12357
12421
  const { statusCode, body } = await _transport2.post(
12358
12422
  url,
12359
12423
  this.headers,
12360
- JSON.stringify(event)
12424
+ this.envelope(event)
12361
12425
  );
12362
12426
  if (statusCode < 200 || statusCode >= 300) {
12363
12427
  log.warn("acpPublisher", `awaiting-answer status=${statusCode} body=${body.slice(0, 200)}`);
@@ -12553,13 +12617,22 @@ async function runAcpSession(opts) {
12553
12617
  pluginId: opts.pluginId,
12554
12618
  pluginAuthToken: opts.pluginAuthToken
12555
12619
  });
12620
+ let updateCount = 0;
12556
12621
  const client2 = new AcpClient({
12557
12622
  adapter: opts.adapter,
12558
12623
  cwd: opts.cwd,
12559
12624
  onSessionUpdate: (notification) => {
12625
+ updateCount += 1;
12626
+ const variant = notification.update?.sessionUpdate ?? "unknown";
12560
12627
  const chunks = mapSessionUpdate(notification);
12628
+ log.info(
12629
+ "acpRunner",
12630
+ `update #${updateCount} variant=${variant} mappedChunks=${chunks.length}`
12631
+ );
12561
12632
  for (const chunk2 of chunks) {
12562
- void publisher.publishChunk(chunk2);
12633
+ void publisher.publishChunk(chunk2).catch((err) => {
12634
+ log.warn("acpRunner", `publishChunk failed: ${err instanceof Error ? err.message : String(err)}`);
12635
+ });
12563
12636
  }
12564
12637
  },
12565
12638
  onRequestPermission: async (request) => {
@@ -12579,8 +12652,7 @@ async function runAcpSession(opts) {
12579
12652
  }
12580
12653
  return { outcome: { outcome: "selected", optionId } };
12581
12654
  },
12582
- onStderr: (line) => {
12583
- log.trace("acpAdapter", line);
12655
+ onStderr: (_line) => {
12584
12656
  },
12585
12657
  onUnexpectedExit: (code, signal) => {
12586
12658
  log.warn("acpRunner", `adapter died code=${code} signal=${signal}; shutting down session`);
@@ -12599,10 +12671,13 @@ async function runAcpSession(opts) {
12599
12671
  "acpRunner",
12600
12672
  `adapter handshake ok protocolVersion=${initialize.protocolVersion} sessionId=${acpSessionId.slice(0, 8)}`
12601
12673
  );
12674
+ showSuccess(`${opts.agent} online (ACP) \u2014 awaiting prompts from mobile.`);
12675
+ const runtime = createInteractiveAgentStrategy(opts.agent, createOsStrategy());
12676
+ const models = await runtime.listModels();
12602
12677
  const relay = new CommandRelayService(
12603
12678
  opts.pluginId,
12604
12679
  async (cmd) => {
12605
- await handleCommand(cmd, client2);
12680
+ await handleCommand(cmd, client2, relay, acpSessionId, models);
12606
12681
  },
12607
12682
  { id: opts.agent, name: opts.agent, displayName: opts.agent }
12608
12683
  );
@@ -12619,19 +12694,24 @@ async function runAcpSession(opts) {
12619
12694
  await new Promise(() => {
12620
12695
  });
12621
12696
  }
12622
- async function handleCommand(cmd, client2) {
12697
+ async function handleCommand(cmd, client2, relay, acpSessionId, models) {
12623
12698
  switch (cmd.type) {
12624
12699
  case "start_task": {
12625
12700
  const payload = cmd.payload;
12626
12701
  const prompt = payload?.prompt?.trim();
12627
12702
  if (!prompt) {
12628
12703
  log.warn("acpRunner", "start_task with empty prompt; ignoring");
12704
+ await relay.sendResult(cmd.id, "failed", { error: "empty prompt" });
12629
12705
  return;
12630
12706
  }
12707
+ log.info("acpRunner", `start_task \u2192 forwarding prompt chars=${prompt.length} id=${cmd.id.slice(0, 8)}`);
12631
12708
  try {
12632
- await client2.prompt(prompt);
12709
+ const reply = await client2.prompt(prompt);
12710
+ log.info("acpRunner", `start_task \u2190 done stopReason=${reply.stopReason ?? "?"} id=${cmd.id.slice(0, 8)}`);
12711
+ await relay.sendResult(cmd.id, "completed", { stopReason: reply.stopReason });
12633
12712
  } catch (err) {
12634
12713
  log.warn("acpRunner", `prompt failed: ${describeError(err)}`);
12714
+ await relay.sendResult(cmd.id, "failed", { error: describeError(err) });
12635
12715
  }
12636
12716
  return;
12637
12717
  }
@@ -12639,13 +12719,31 @@ async function handleCommand(cmd, client2) {
12639
12719
  case "escape_key": {
12640
12720
  try {
12641
12721
  await client2.cancel();
12722
+ await relay.sendResult(cmd.id, "completed", {});
12642
12723
  } catch (err) {
12643
12724
  log.warn("acpRunner", `cancel failed: ${describeError(err)}`);
12725
+ await relay.sendResult(cmd.id, "failed", { error: describeError(err) });
12644
12726
  }
12645
12727
  return;
12646
12728
  }
12729
+ case "get_conversation": {
12730
+ await relay.sendResult(cmd.id, "completed", { conversationId: acpSessionId });
12731
+ return;
12732
+ }
12733
+ case "list_models": {
12734
+ await relay.sendResult(cmd.id, "completed", { models });
12735
+ return;
12736
+ }
12737
+ case "set_keep_alive":
12738
+ case "get_context": {
12739
+ await relay.sendResult(cmd.id, "completed", {});
12740
+ return;
12741
+ }
12647
12742
  default:
12648
12743
  log.trace("acpRunner", `command type "${cmd.type}" not supported in Phase 1 ACP mode`);
12744
+ await relay.sendResult(cmd.id, "failed", {
12745
+ error: `Command "${cmd.type}" is not supported in Phase 1 ACP mode.`
12746
+ });
12649
12747
  return;
12650
12748
  }
12651
12749
  }
@@ -14816,7 +14914,7 @@ async function discoverRepos(workingDir, maxDepth = 4) {
14816
14914
  // src/services/turn-files/files-outbox.ts
14817
14915
  var fs24 = __toESM(require("fs/promises"));
14818
14916
  var path29 = __toESM(require("path"));
14819
- var import_os7 = require("os");
14917
+ var import_os8 = require("os");
14820
14918
  var HOME_OUTBOX_DIR = ".codeam/outbox";
14821
14919
  var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
14822
14920
  var BACKOFF_STEPS_MS = [
@@ -14983,7 +15081,7 @@ function applyJitter(ms) {
14983
15081
  return Math.round(ms * factor);
14984
15082
  }
14985
15083
  function homeDir() {
14986
- return process.env.HOME ?? process.env.USERPROFILE ?? (0, import_os7.tmpdir)();
15084
+ return process.env.HOME ?? process.env.USERPROFILE ?? (0, import_os8.tmpdir)();
14987
15085
  }
14988
15086
 
14989
15087
  // src/services/turn-files/turn-file-aggregator.ts
@@ -16932,11 +17030,11 @@ async function linkDryRunPreflight(ctx) {
16932
17030
  var import_promises = require("dns/promises");
16933
17031
  var import_fs = require("fs");
16934
17032
  var import_promises2 = __toESM(require("fs/promises"));
16935
- var import_os8 = __toESM(require("os"));
17033
+ var import_os9 = __toESM(require("os"));
16936
17034
  var import_path4 = __toESM(require("path"));
16937
17035
  var import_promises3 = require("stream/promises");
16938
17036
  var import_which = __toESM(require("which"));
16939
- var CACHED_BINARY = import_path4.default.join(import_os8.default.homedir(), ".codeam", "bin", "cloudflared");
17037
+ var CACHED_BINARY = import_path4.default.join(import_os9.default.homedir(), ".codeam", "bin", "cloudflared");
16940
17038
  async function waitForCloudflaredReady(url, timeoutMs = 6e4) {
16941
17039
  const hostname3 = new URL(url).hostname;
16942
17040
  const resolver = new import_promises.Resolver();
@@ -21132,7 +21230,7 @@ function checkChokidar() {
21132
21230
  }
21133
21231
  async function doctor(args2 = []) {
21134
21232
  const json = args2.includes("--json");
21135
- const cliVersion = true ? "2.27.5" : "0.0.0-dev";
21233
+ const cliVersion = true ? "2.27.7" : "0.0.0-dev";
21136
21234
  const apiBase = resolveApiBaseUrl();
21137
21235
  const diagnosticId = (0, import_node_crypto8.randomUUID)();
21138
21236
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -21331,7 +21429,7 @@ async function completion(args2) {
21331
21429
  // src/commands/version.ts
21332
21430
  var import_picocolors13 = __toESM(require("picocolors"));
21333
21431
  function version2() {
21334
- const v = true ? "2.27.5" : "unknown";
21432
+ const v = true ? "2.27.7" : "unknown";
21335
21433
  console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
21336
21434
  }
21337
21435
 
@@ -21559,7 +21657,7 @@ function checkForUpdates() {
21559
21657
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
21560
21658
  if (process.env.CI) return;
21561
21659
  if (!process.stdout.isTTY) return;
21562
- const current = true ? "2.27.5" : null;
21660
+ const current = true ? "2.27.7" : null;
21563
21661
  if (!current) return;
21564
21662
  const cache = readCache();
21565
21663
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.27.5",
3
+ "version": "2.27.7",
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",