codeam-cli 2.27.8 → 2.27.9

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,12 @@ 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.8] — 2026-06-06
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** ACP — accumulate cumulative content per chunkId, isFinal on prompt-end
12
+
7
13
  ## [2.27.7] — 2026-06-06
8
14
 
9
15
  ### 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.9",
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.9" : "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"));
@@ -12374,39 +12371,51 @@ var AcpPublisher = class {
12374
12371
  apiBase;
12375
12372
  headers;
12376
12373
  /**
12377
- * Wrap the event with `sessionId` + `pluginId` at the top level.
12374
+ * Wrap the body with `sessionId` + `pluginId` at the top level.
12378
12375
  * 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.
12376
+ * body even when `X-Plugin-Auth-Token` is set on the header.
12383
12377
  */
12384
- envelope(event) {
12378
+ envelope(body) {
12385
12379
  return JSON.stringify({
12386
12380
  sessionId: this.opts.sessionId,
12387
12381
  pluginId: this.opts.pluginId,
12388
- ...event
12382
+ ...body
12389
12383
  });
12390
12384
  }
12391
12385
  /**
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.
12386
+ * POST one event to the legacy chat-render pipeline at
12387
+ * `/api/commands/output`. Mobile reads this feed for the chat
12388
+ * surface every "Thinking…" reply done bubble flows through
12389
+ * here. Accepts arbitrary body shapes (the legacy emitter is a
12390
+ * thin pipe; mobile branches on `type`):
12391
+ *
12392
+ * { type: 'clear' } wipe screen
12393
+ * { type: 'new_turn', done: false } "Agent is typing…"
12394
+ * { type: 'text', content: '…', done: false } streaming delta
12395
+ * { type: 'text', content: '…', done: true } turn complete
12396
+ *
12397
+ * Errors are logged but never thrown — a missed chunk shouldn't
12398
+ * bring down the whole session.
12396
12399
  */
12397
- async publishChunk(event) {
12398
- const url = `${this.apiBase}/api/sessions/${encodeURIComponent(this.opts.sessionId)}/streaming-chunk`;
12400
+ async publishOutput(body) {
12401
+ const url = `${this.apiBase}/api/commands/output`;
12399
12402
  try {
12400
- const { statusCode, body } = await _transport2.post(
12403
+ const { statusCode, body: resBody } = await _transport2.post(
12401
12404
  url,
12402
12405
  this.headers,
12403
- this.envelope(event)
12406
+ this.envelope(body)
12404
12407
  );
12405
12408
  if (statusCode < 200 || statusCode >= 300) {
12406
- log.warn("acpPublisher", `chunk status=${statusCode} body=${body.slice(0, 200)}`);
12409
+ log.warn(
12410
+ "acpPublisher",
12411
+ `output type=${String(body.type)} done=${body.done === true} status=${statusCode} body=${resBody.slice(0, 200)}`
12412
+ );
12407
12413
  }
12408
12414
  } catch (err) {
12409
- log.trace("acpPublisher", "chunk post failed", err);
12415
+ log.warn(
12416
+ "acpPublisher",
12417
+ `output type=${String(body.type)} post failed: ${err instanceof Error ? err.message : String(err)}`
12418
+ );
12410
12419
  }
12411
12420
  }
12412
12421
  /**
@@ -12612,6 +12621,18 @@ var StreamingState = class {
12612
12621
  }
12613
12622
  publisher;
12614
12623
  open = /* @__PURE__ */ new Map();
12624
+ /**
12625
+ * Boundary events emitted at the start of every turn so mobile
12626
+ * wipes the previous reply and shows "Agent is typing…". Mirrors
12627
+ * the legacy `outputSvc.newTurn()` — `critical: true` on those
12628
+ * sends is implicit here (publishOutput retries on transient
12629
+ * failures so the boundary always lands).
12630
+ */
12631
+ async beginTurn() {
12632
+ this.open.clear();
12633
+ await this.publisher.publishOutput({ type: "clear" });
12634
+ await this.publisher.publishOutput({ type: "new_turn", done: false });
12635
+ }
12615
12636
  append(delta) {
12616
12637
  const existing = this.open.get(delta.chunkId);
12617
12638
  const content = (existing?.content ?? "") + delta.delta;
@@ -12622,32 +12643,33 @@ var StreamingState = class {
12622
12643
  );
12623
12644
  }
12624
12645
  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
- });
12646
+ void this.publisher.publishOutput({ type: "text", content, done: false });
12631
12647
  }
12632
12648
  /**
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).
12649
+ * Flush every open buffer with `done: true` so mobile flips out
12650
+ * of "Thinking…". Idempotent — safe to call multiple times per
12651
+ * turn (prompt-completed, cancel, adapter-exit).
12652
+ *
12653
+ * Also emits an empty `{type:'text', content:'', done:true}` when
12654
+ * the turn produced no text at all (e.g. Claude responded with
12655
+ * tool calls only) so mobile doesn't sit on "Thinking…" forever
12656
+ * waiting for content that will never arrive.
12636
12657
  */
12637
12658
  async closeAll() {
12638
- if (this.open.size === 0) return;
12639
- const closing = Array.from(this.open.entries());
12659
+ if (this.open.size === 0) {
12660
+ await this.publisher.publishOutput({ type: "text", content: "", done: true });
12661
+ return;
12662
+ }
12663
+ const closing = Array.from(this.open.values());
12640
12664
  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
- );
12665
+ for (let i = 0; i < closing.length; i += 1) {
12666
+ const isLast = i === closing.length - 1;
12667
+ await this.publisher.publishOutput({
12668
+ type: "text",
12669
+ content: closing[i].content,
12670
+ done: isLast
12671
+ });
12672
+ }
12651
12673
  }
12652
12674
  };
12653
12675
  var ANSWER_POLL_MS = 1500;
@@ -12696,13 +12718,13 @@ async function runAcpSession(opts) {
12696
12718
  },
12697
12719
  onUnexpectedExit: (code, signal) => {
12698
12720
  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
- });
12721
+ void streaming.closeAll().then(
12722
+ () => publisher.publishOutput({
12723
+ type: "text",
12724
+ content: `Agent adapter exited unexpectedly (code=${code ?? "null"} signal=${signal ?? "null"}).`,
12725
+ done: true
12726
+ })
12727
+ );
12706
12728
  process.exit(1);
12707
12729
  }
12708
12730
  });
@@ -12746,6 +12768,7 @@ async function handleCommand(cmd, client2, relay, acpSessionId, models, streamin
12746
12768
  return;
12747
12769
  }
12748
12770
  log.info("acpRunner", `start_task \u2192 forwarding prompt chars=${prompt.length} id=${cmd.id.slice(0, 8)}`);
12771
+ await streaming.beginTurn();
12749
12772
  try {
12750
12773
  const reply = await client2.prompt(prompt);
12751
12774
  await streaming.closeAll();
@@ -16699,7 +16722,7 @@ function findGitRoot2(startDir) {
16699
16722
  }
16700
16723
 
16701
16724
  // src/commands/link.ts
16702
- var import_node_crypto7 = require("crypto");
16725
+ var import_node_crypto6 = require("crypto");
16703
16726
  var fs28 = __toESM(require("fs"));
16704
16727
  var path34 = __toESM(require("path"));
16705
16728
  var import_chokidar = __toESM(require("chokidar"));
@@ -16788,7 +16811,7 @@ async function link(args2 = []) {
16788
16811
  await linkDryRunPreflight(ctx);
16789
16812
  return;
16790
16813
  }
16791
- const pluginId = (0, import_node_crypto7.randomUUID)();
16814
+ const pluginId = (0, import_node_crypto6.randomUUID)();
16792
16815
  const spin = dist_exports.spinner();
16793
16816
  spin.start("Requesting pairing code...");
16794
16817
  const pairing = await requestCode(pluginId);
@@ -21104,7 +21127,7 @@ async function stopWorkspaceFromLocal(target) {
21104
21127
  // src/commands/doctor.ts
21105
21128
  var import_node_dns = require("dns");
21106
21129
  var import_node_util4 = require("util");
21107
- var import_node_crypto8 = require("crypto");
21130
+ var import_node_crypto7 = require("crypto");
21108
21131
  var fs34 = __toESM(require("fs"));
21109
21132
  var path43 = __toESM(require("path"));
21110
21133
  var import_picocolors12 = __toESM(require("picocolors"));
@@ -21274,9 +21297,9 @@ function checkChokidar() {
21274
21297
  }
21275
21298
  async function doctor(args2 = []) {
21276
21299
  const json = args2.includes("--json");
21277
- const cliVersion = true ? "2.27.8" : "0.0.0-dev";
21300
+ const cliVersion = true ? "2.27.9" : "0.0.0-dev";
21278
21301
  const apiBase = resolveApiBaseUrl();
21279
- const diagnosticId = (0, import_node_crypto8.randomUUID)();
21302
+ const diagnosticId = (0, import_node_crypto7.randomUUID)();
21280
21303
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
21281
21304
  const [dns, health] = await Promise.all([
21282
21305
  checkDns(apiBase),
@@ -21473,7 +21496,7 @@ async function completion(args2) {
21473
21496
  // src/commands/version.ts
21474
21497
  var import_picocolors13 = __toESM(require("picocolors"));
21475
21498
  function version2() {
21476
- const v = true ? "2.27.8" : "unknown";
21499
+ const v = true ? "2.27.9" : "unknown";
21477
21500
  console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
21478
21501
  }
21479
21502
 
@@ -21701,7 +21724,7 @@ function checkForUpdates() {
21701
21724
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
21702
21725
  if (process.env.CI) return;
21703
21726
  if (!process.stdout.isTTY) return;
21704
- const current = true ? "2.27.8" : null;
21727
+ const current = true ? "2.27.9" : null;
21705
21728
  if (!current) return;
21706
21729
  const cache = readCache();
21707
21730
  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.9",
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",