codeam-cli 2.27.8 → 2.27.10

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,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.9] — 2026-06-06
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** ACP publishes to /api/commands/output (mobile's chat pipe)
12
+
13
+ ## [2.27.8] — 2026-06-06
14
+
15
+ ### Fixed
16
+
17
+ - **cli:** ACP — accumulate cumulative content per chunkId, isFinal on prompt-end
18
+
7
19
  ## [2.27.7] — 2026-06-06
8
20
 
9
21
  ### Fixed
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.8",
501
+ version: "2.27.10",
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.8" : "0.0.0-dev",
5876
+ cliVersion: true ? "2.27.10" : "0.0.0-dev",
5877
5877
  nodeVersion: process.version,
5878
5878
  platform: process.platform,
5879
5879
  arch: process.arch,
@@ -12049,9 +12049,6 @@ function getAcpAdapter(agent) {
12049
12049
  return factory ? factory() : null;
12050
12050
  }
12051
12051
 
12052
- // src/agents/acp/runner.ts
12053
- var import_node_crypto6 = require("crypto");
12054
-
12055
12052
  // src/agents/acp/client.ts
12056
12053
  var import_node_child_process11 = require("child_process");
12057
12054
  var fs21 = __toESM(require("fs/promises"));
@@ -12130,7 +12127,11 @@ var AcpClient = class {
12130
12127
  mcpServers: []
12131
12128
  });
12132
12129
  this.sessionId = newSession.sessionId;
12133
- log.info("acpClient", `newSession \u2190 ok sessionId=${newSession.sessionId.slice(0, 8)}`);
12130
+ const newSessionMeta = newSession;
12131
+ log.info(
12132
+ "acpClient",
12133
+ `newSession \u2190 ok sessionId=${newSession.sessionId.slice(0, 8)} model=${newSessionMeta.currentModelId ?? "?"} tier=${newSessionMeta.currentServiceTier ?? "?"}`
12134
+ );
12134
12135
  return { sessionId: newSession.sessionId, initialize };
12135
12136
  }
12136
12137
  /**
@@ -12157,15 +12158,15 @@ var AcpClient = class {
12157
12158
  sessionId: this.sessionId,
12158
12159
  prompt: [{ type: "text", text }]
12159
12160
  });
12161
+ let timeoutId;
12160
12162
  const timeout = new Promise((_resolve, reject) => {
12161
- const id = setTimeout(() => {
12163
+ timeoutId = setTimeout(() => {
12162
12164
  reject(
12163
12165
  new Error(
12164
12166
  `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
12167
  )
12166
12168
  );
12167
12169
  }, PROMPT_TIMEOUT_MS);
12168
- void send.finally(() => clearTimeout(id));
12169
12170
  });
12170
12171
  try {
12171
12172
  const result = await Promise.race([send, timeout]);
@@ -12180,6 +12181,8 @@ var AcpClient = class {
12180
12181
  `prompt \u2190 failed elapsedMs=${Date.now() - t0} err=${err instanceof Error ? err.message : String(err)}`
12181
12182
  );
12182
12183
  throw err;
12184
+ } finally {
12185
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
12183
12186
  }
12184
12187
  }
12185
12188
  /**
@@ -12374,39 +12377,51 @@ var AcpPublisher = class {
12374
12377
  apiBase;
12375
12378
  headers;
12376
12379
  /**
12377
- * Wrap the event with `sessionId` + `pluginId` at the top level.
12380
+ * Wrap the body with `sessionId` + `pluginId` at the top level.
12378
12381
  * 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.
12382
+ * body even when `X-Plugin-Auth-Token` is set on the header.
12383
12383
  */
12384
- envelope(event) {
12384
+ envelope(body) {
12385
12385
  return JSON.stringify({
12386
12386
  sessionId: this.opts.sessionId,
12387
12387
  pluginId: this.opts.pluginId,
12388
- ...event
12388
+ ...body
12389
12389
  });
12390
12390
  }
12391
12391
  /**
12392
- * Fire-and-forget chunk POST. The backend's per-user SSE bus
12393
- * forwards each chunk to mobile/landing within ~20 ms (PRO) /
12394
- * ~80 ms (FREE). Errors are logged but never thrown a missed
12395
- * chunk shouldn't bring down the whole session.
12392
+ * POST one event to the legacy chat-render pipeline at
12393
+ * `/api/commands/output`. Mobile reads this feed for the chat
12394
+ * surface every "Thinking…" reply done bubble flows through
12395
+ * here. Accepts arbitrary body shapes (the legacy emitter is a
12396
+ * thin pipe; mobile branches on `type`):
12397
+ *
12398
+ * { type: 'clear' } wipe screen
12399
+ * { type: 'new_turn', done: false } "Agent is typing…"
12400
+ * { type: 'text', content: '…', done: false } streaming delta
12401
+ * { type: 'text', content: '…', done: true } turn complete
12402
+ *
12403
+ * Errors are logged but never thrown — a missed chunk shouldn't
12404
+ * bring down the whole session.
12396
12405
  */
12397
- async publishChunk(event) {
12398
- const url = `${this.apiBase}/api/sessions/${encodeURIComponent(this.opts.sessionId)}/streaming-chunk`;
12406
+ async publishOutput(body) {
12407
+ const url = `${this.apiBase}/api/commands/output`;
12399
12408
  try {
12400
- const { statusCode, body } = await _transport2.post(
12409
+ const { statusCode, body: resBody } = await _transport2.post(
12401
12410
  url,
12402
12411
  this.headers,
12403
- this.envelope(event)
12412
+ this.envelope(body)
12404
12413
  );
12405
12414
  if (statusCode < 200 || statusCode >= 300) {
12406
- log.warn("acpPublisher", `chunk status=${statusCode} body=${body.slice(0, 200)}`);
12415
+ log.warn(
12416
+ "acpPublisher",
12417
+ `output type=${String(body.type)} done=${body.done === true} status=${statusCode} body=${resBody.slice(0, 200)}`
12418
+ );
12407
12419
  }
12408
12420
  } catch (err) {
12409
- log.trace("acpPublisher", "chunk post failed", err);
12421
+ log.warn(
12422
+ "acpPublisher",
12423
+ `output type=${String(body.type)} post failed: ${err instanceof Error ? err.message : String(err)}`
12424
+ );
12410
12425
  }
12411
12426
  }
12412
12427
  /**
@@ -12611,43 +12626,31 @@ var StreamingState = class {
12611
12626
  this.publisher = publisher;
12612
12627
  }
12613
12628
  publisher;
12614
- open = /* @__PURE__ */ new Map();
12629
+ text = "";
12630
+ /**
12631
+ * Boundary events emitted at the start of every turn so mobile
12632
+ * wipes the previous reply and shows "Agent is typing…". Mirrors
12633
+ * the legacy `outputSvc.newTurn()`.
12634
+ */
12635
+ async beginTurn() {
12636
+ this.text = "";
12637
+ await this.publisher.publishOutput({ type: "clear" });
12638
+ await this.publisher.publishOutput({ type: "new_turn", done: false });
12639
+ }
12615
12640
  append(delta) {
12616
- const existing = this.open.get(delta.chunkId);
12617
- const content = (existing?.content ?? "") + delta.delta;
12618
- if (existing && existing.kind !== delta.kind) {
12619
- log.warn(
12620
- "acpRunner",
12621
- `chunk kind flip detected chunkId=${delta.chunkId.slice(0, 8)} from=${existing.kind} to=${delta.kind} \u2014 using new kind`
12622
- );
12623
- }
12624
- this.open.set(delta.chunkId, { kind: delta.kind, content });
12625
- void this.publisher.publishChunk({ chunkId: delta.chunkId, kind: delta.kind, content, isFinal: false }).catch((err) => {
12626
- log.warn(
12627
- "acpRunner",
12628
- `publishChunk (streaming) failed: ${err instanceof Error ? err.message : String(err)}`
12629
- );
12630
- });
12641
+ if (delta.kind !== "text") return;
12642
+ this.text += delta.delta;
12643
+ void this.publisher.publishOutput({ type: "text", content: this.text, done: false });
12631
12644
  }
12632
12645
  /**
12633
- * Close every open chunk with `isFinal: true`. Idempotent safe
12634
- * to call multiple times per turn (e.g. once on prompt-completed,
12635
- * once on cancel, once on adapter-exit).
12646
+ * Flip the chat out of "Thinking…" with one final cumulative
12647
+ * `done: true`. Idempotent safe to call from happy + error +
12648
+ * adapter-exit paths.
12636
12649
  */
12637
12650
  async closeAll() {
12638
- if (this.open.size === 0) return;
12639
- const closing = Array.from(this.open.entries());
12640
- this.open.clear();
12641
- await Promise.all(
12642
- closing.map(
12643
- ([chunkId, { kind, content }]) => this.publisher.publishChunk({ chunkId, kind, content, isFinal: true }).catch((err) => {
12644
- log.warn(
12645
- "acpRunner",
12646
- `publishChunk (closing) failed: ${err instanceof Error ? err.message : String(err)}`
12647
- );
12648
- })
12649
- )
12650
- );
12651
+ const finalText = this.text;
12652
+ this.text = "";
12653
+ await this.publisher.publishOutput({ type: "text", content: finalText, done: true });
12651
12654
  }
12652
12655
  };
12653
12656
  var ANSWER_POLL_MS = 1500;
@@ -12696,13 +12699,13 @@ async function runAcpSession(opts) {
12696
12699
  },
12697
12700
  onUnexpectedExit: (code, signal) => {
12698
12701
  log.warn("acpRunner", `adapter died code=${code} signal=${signal}; shutting down session`);
12699
- void streaming.closeAll();
12700
- void publisher.publishChunk({
12701
- chunkId: (0, import_node_crypto6.randomUUID)(),
12702
- kind: "text",
12703
- content: `Agent adapter exited unexpectedly (code=${code ?? "null"} signal=${signal ?? "null"}).`,
12704
- isFinal: true
12705
- });
12702
+ void streaming.closeAll().then(
12703
+ () => publisher.publishOutput({
12704
+ type: "text",
12705
+ content: `Agent adapter exited unexpectedly (code=${code ?? "null"} signal=${signal ?? "null"}).`,
12706
+ done: true
12707
+ })
12708
+ );
12706
12709
  process.exit(1);
12707
12710
  }
12708
12711
  });
@@ -12746,6 +12749,7 @@ async function handleCommand(cmd, client2, relay, acpSessionId, models, streamin
12746
12749
  return;
12747
12750
  }
12748
12751
  log.info("acpRunner", `start_task \u2192 forwarding prompt chars=${prompt.length} id=${cmd.id.slice(0, 8)}`);
12752
+ await streaming.beginTurn();
12749
12753
  try {
12750
12754
  const reply = await client2.prompt(prompt);
12751
12755
  await streaming.closeAll();
@@ -16699,7 +16703,7 @@ function findGitRoot2(startDir) {
16699
16703
  }
16700
16704
 
16701
16705
  // src/commands/link.ts
16702
- var import_node_crypto7 = require("crypto");
16706
+ var import_node_crypto6 = require("crypto");
16703
16707
  var fs28 = __toESM(require("fs"));
16704
16708
  var path34 = __toESM(require("path"));
16705
16709
  var import_chokidar = __toESM(require("chokidar"));
@@ -16788,7 +16792,7 @@ async function link(args2 = []) {
16788
16792
  await linkDryRunPreflight(ctx);
16789
16793
  return;
16790
16794
  }
16791
- const pluginId = (0, import_node_crypto7.randomUUID)();
16795
+ const pluginId = (0, import_node_crypto6.randomUUID)();
16792
16796
  const spin = dist_exports.spinner();
16793
16797
  spin.start("Requesting pairing code...");
16794
16798
  const pairing = await requestCode(pluginId);
@@ -21104,7 +21108,7 @@ async function stopWorkspaceFromLocal(target) {
21104
21108
  // src/commands/doctor.ts
21105
21109
  var import_node_dns = require("dns");
21106
21110
  var import_node_util4 = require("util");
21107
- var import_node_crypto8 = require("crypto");
21111
+ var import_node_crypto7 = require("crypto");
21108
21112
  var fs34 = __toESM(require("fs"));
21109
21113
  var path43 = __toESM(require("path"));
21110
21114
  var import_picocolors12 = __toESM(require("picocolors"));
@@ -21274,9 +21278,9 @@ function checkChokidar() {
21274
21278
  }
21275
21279
  async function doctor(args2 = []) {
21276
21280
  const json = args2.includes("--json");
21277
- const cliVersion = true ? "2.27.8" : "0.0.0-dev";
21281
+ const cliVersion = true ? "2.27.10" : "0.0.0-dev";
21278
21282
  const apiBase = resolveApiBaseUrl();
21279
- const diagnosticId = (0, import_node_crypto8.randomUUID)();
21283
+ const diagnosticId = (0, import_node_crypto7.randomUUID)();
21280
21284
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
21281
21285
  const [dns, health] = await Promise.all([
21282
21286
  checkDns(apiBase),
@@ -21473,7 +21477,7 @@ async function completion(args2) {
21473
21477
  // src/commands/version.ts
21474
21478
  var import_picocolors13 = __toESM(require("picocolors"));
21475
21479
  function version2() {
21476
- const v = true ? "2.27.8" : "unknown";
21480
+ const v = true ? "2.27.10" : "unknown";
21477
21481
  console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
21478
21482
  }
21479
21483
 
@@ -21701,7 +21705,7 @@ function checkForUpdates() {
21701
21705
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
21702
21706
  if (process.env.CI) return;
21703
21707
  if (!process.stdout.isTTY) return;
21704
- const current = true ? "2.27.8" : null;
21708
+ const current = true ? "2.27.10" : null;
21705
21709
  if (!current) return;
21706
21710
  const cache = readCache();
21707
21711
  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.8",
3
+ "version": "2.27.10",
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",