codeam-cli 2.27.7 → 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.
Files changed (3) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/index.js +167 -100
  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.8] — 2026-06-06
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** ACP — accumulate cumulative content per chunkId, isFinal on prompt-end
12
+
13
+ ## [2.27.7] — 2026-06-06
14
+
15
+ ### Fixed
16
+
17
+ - **cli:** ACP — wire-level instrumentation + prompt timeout
18
+
7
19
  ## [2.27.6] — 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.7",
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",
@@ -889,8 +889,8 @@ async function _postJsonAuthed(url, body, pluginAuthToken) {
889
889
  (res) => {
890
890
  res.on("error", reject);
891
891
  let responseBody = "";
892
- res.on("data", (chunk2) => {
893
- responseBody += chunk2.toString();
892
+ res.on("data", (chunk) => {
893
+ responseBody += chunk.toString();
894
894
  });
895
895
  res.on("end", () => {
896
896
  if (res.statusCode && res.statusCode >= 400) {
@@ -937,8 +937,8 @@ async function _postJson(url, body) {
937
937
  (res) => {
938
938
  res.on("error", reject);
939
939
  let body2 = "";
940
- res.on("data", (chunk2) => {
941
- body2 += chunk2.toString();
940
+ res.on("data", (chunk) => {
941
+ body2 += chunk.toString();
942
942
  });
943
943
  res.on("end", () => {
944
944
  if (res.statusCode && res.statusCode >= 400) {
@@ -978,8 +978,8 @@ async function _getJson(url) {
978
978
  (res) => {
979
979
  res.on("error", reject);
980
980
  let body = "";
981
- res.on("data", (chunk2) => {
982
- body += chunk2.toString();
981
+ res.on("data", (chunk) => {
982
+ body += chunk.toString();
983
983
  });
984
984
  res.on("end", () => {
985
985
  if (res.statusCode && res.statusCode >= 400) {
@@ -5873,7 +5873,7 @@ function readAnonId() {
5873
5873
  }
5874
5874
  function superProperties() {
5875
5875
  return {
5876
- cliVersion: true ? "2.27.7" : "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,
@@ -6080,9 +6080,9 @@ var CommandRelayService = class {
6080
6080
  this.armSseWatchdog();
6081
6081
  let buffer = "";
6082
6082
  res.setEncoding("utf8");
6083
- res.on("data", (chunk2) => {
6083
+ res.on("data", (chunk) => {
6084
6084
  this.sseLastByteAt = Date.now();
6085
- buffer += chunk2;
6085
+ buffer += chunk;
6086
6086
  let frameEnd;
6087
6087
  while ((frameEnd = buffer.indexOf("\n\n")) !== -1) {
6088
6088
  const frame = buffer.slice(0, frameEnd);
@@ -6751,9 +6751,9 @@ var UnixPtyStrategy = class {
6751
6751
  );
6752
6752
  process.exit(1);
6753
6753
  });
6754
- this.proc.stdout?.on("data", (chunk2) => {
6755
- process.stdout.write(chunk2);
6756
- this.opts.onData(chunk2.toString("utf8"));
6754
+ this.proc.stdout?.on("data", (chunk) => {
6755
+ process.stdout.write(chunk);
6756
+ this.opts.onData(chunk.toString("utf8"));
6757
6757
  });
6758
6758
  if (process.stdin.isTTY) process.stdin.setRawMode(true);
6759
6759
  process.stdin.resume();
@@ -6845,8 +6845,8 @@ var UnixPtyStrategy = class {
6845
6845
  }
6846
6846
  }
6847
6847
  }
6848
- stdinHandler = (chunk2) => {
6849
- this.proc?.stdin?.write(chunk2);
6848
+ stdinHandler = (chunk) => {
6849
+ this.proc?.stdin?.write(chunk);
6850
6850
  };
6851
6851
  handleResize = () => {
6852
6852
  if (this.proc?.pid) {
@@ -7032,8 +7032,8 @@ var WindowsConPtyStrategy = class _WindowsConPtyStrategy {
7032
7032
  }
7033
7033
  this.pty = null;
7034
7034
  }
7035
- stdinHandler = (chunk2) => {
7036
- this.pty?.write(chunk2.toString("utf8"));
7035
+ stdinHandler = (chunk) => {
7036
+ this.pty?.write(chunk.toString("utf8"));
7037
7037
  };
7038
7038
  };
7039
7039
 
@@ -7066,9 +7066,9 @@ var WindowsPtyStrategy = class {
7066
7066
  );
7067
7067
  process.exit(1);
7068
7068
  });
7069
- this.proc.stdout?.on("data", (chunk2) => {
7070
- process.stdout.write(chunk2);
7071
- this.opts.onData(chunk2.toString("utf8"));
7069
+ this.proc.stdout?.on("data", (chunk) => {
7070
+ process.stdout.write(chunk);
7071
+ this.opts.onData(chunk.toString("utf8"));
7072
7072
  });
7073
7073
  this.proc.stdin?.write("");
7074
7074
  if (process.stdin.isTTY) process.stdin.setRawMode(true);
@@ -7095,8 +7095,8 @@ var WindowsPtyStrategy = class {
7095
7095
  }
7096
7096
  }
7097
7097
  }
7098
- stdinHandler = (chunk2) => {
7099
- this.proc?.stdin?.write(chunk2);
7098
+ stdinHandler = (chunk) => {
7099
+ this.proc?.stdin?.write(chunk);
7100
7100
  };
7101
7101
  };
7102
7102
 
@@ -9379,8 +9379,8 @@ async function fetchClaudeQuota() {
9379
9379
  });
9380
9380
  let output = "";
9381
9381
  let resolved = false;
9382
- proc.stdout?.on("data", (chunk2) => {
9383
- output += chunk2.toString("utf8");
9382
+ proc.stdout?.on("data", (chunk) => {
9383
+ output += chunk.toString("utf8");
9384
9384
  });
9385
9385
  setTimeout(() => {
9386
9386
  proc.stdin?.write("/usage\r");
@@ -9453,11 +9453,11 @@ async function spawnAndCapture(cmd, args2, opts = {}) {
9453
9453
  }
9454
9454
  activeChildren.add(child);
9455
9455
  let stdout = "";
9456
- child.stdout?.on("data", (chunk2) => {
9457
- stdout += chunk2.toString("utf8");
9456
+ child.stdout?.on("data", (chunk) => {
9457
+ stdout += chunk.toString("utf8");
9458
9458
  });
9459
- child.stderr?.on("data", (chunk2) => {
9460
- opts.onStderr?.(chunk2.toString("utf8"));
9459
+ child.stderr?.on("data", (chunk) => {
9460
+ opts.onStderr?.(chunk.toString("utf8"));
9461
9461
  });
9462
9462
  const timer = setTimeout(() => {
9463
9463
  try {
@@ -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"));
@@ -12091,8 +12088,8 @@ var AcpClient = class {
12091
12088
  });
12092
12089
  this.child = child;
12093
12090
  child.stderr?.setEncoding("utf8");
12094
- child.stderr?.on("data", (chunk2) => {
12095
- for (const line of chunk2.split(/\r?\n/)) {
12091
+ child.stderr?.on("data", (chunk) => {
12092
+ for (const line of chunk.split(/\r?\n/)) {
12096
12093
  const trimmed = line.trim();
12097
12094
  if (trimmed) {
12098
12095
  log.info("acpAdapter", trimmed);
@@ -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
  /**
@@ -12483,17 +12492,17 @@ function mapSessionUpdate(notification) {
12483
12492
  case "agent_message_chunk": {
12484
12493
  const text = extractText2(update.content);
12485
12494
  if (!text) return [];
12486
- return [chunk(messageChunkId(update.messageId), "text", text)];
12495
+ return [{ chunkId: messageChunkId(update.messageId), kind: "text", delta: text }];
12487
12496
  }
12488
12497
  case "agent_thought_chunk": {
12489
12498
  const text = extractText2(update.content);
12490
12499
  if (!text) return [];
12491
- return [chunk(messageChunkId(update.messageId), "thinking", text)];
12500
+ return [{ chunkId: messageChunkId(update.messageId), kind: "thinking", delta: text }];
12492
12501
  }
12493
12502
  case "tool_call": {
12494
12503
  const summary = describeToolCall(update);
12495
12504
  if (!summary) return [];
12496
- return [chunk(update.toolCallId, "tool_use", summary)];
12505
+ return [{ chunkId: update.toolCallId, kind: "tool_use", delta: summary }];
12497
12506
  }
12498
12507
  case "tool_call_update": {
12499
12508
  if (update.status !== "completed" && update.status !== "failed") {
@@ -12502,7 +12511,7 @@ function mapSessionUpdate(notification) {
12502
12511
  const body = describeToolCallUpdate(update);
12503
12512
  if (!body) return [];
12504
12513
  const prefix = update.status === "failed" ? "[failed] " : "";
12505
- return [chunk(update.toolCallId, "tool_result", prefix + body)];
12514
+ return [{ chunkId: update.toolCallId, kind: "tool_result", delta: prefix + body }];
12506
12515
  }
12507
12516
  case "user_message_chunk":
12508
12517
  return [];
@@ -12541,9 +12550,6 @@ function mapPermissionRequest(request) {
12541
12550
  kindByLabel
12542
12551
  };
12543
12552
  }
12544
- function chunk(chunkId, kind, content) {
12545
- return { chunkId, kind, content, isFinal: true };
12546
- }
12547
12553
  function messageChunkId(messageId) {
12548
12554
  if (typeof messageId === "string" && messageId.length > 0) return messageId;
12549
12555
  return (0, import_node_crypto5.randomUUID)();
@@ -12609,6 +12615,63 @@ function humanizeKind(kind) {
12609
12615
  }
12610
12616
 
12611
12617
  // src/agents/acp/runner.ts
12618
+ var StreamingState = class {
12619
+ constructor(publisher) {
12620
+ this.publisher = publisher;
12621
+ }
12622
+ publisher;
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
+ }
12636
+ append(delta) {
12637
+ const existing = this.open.get(delta.chunkId);
12638
+ const content = (existing?.content ?? "") + delta.delta;
12639
+ if (existing && existing.kind !== delta.kind) {
12640
+ log.warn(
12641
+ "acpRunner",
12642
+ `chunk kind flip detected chunkId=${delta.chunkId.slice(0, 8)} from=${existing.kind} to=${delta.kind} \u2014 using new kind`
12643
+ );
12644
+ }
12645
+ this.open.set(delta.chunkId, { kind: delta.kind, content });
12646
+ void this.publisher.publishOutput({ type: "text", content, done: false });
12647
+ }
12648
+ /**
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.
12657
+ */
12658
+ async closeAll() {
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());
12664
+ this.open.clear();
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
+ }
12673
+ }
12674
+ };
12612
12675
  var ANSWER_POLL_MS = 1500;
12613
12676
  var PERMISSION_TIMEOUT_MS = 5 * 60 * 1e3;
12614
12677
  async function runAcpSession(opts) {
@@ -12617,6 +12680,7 @@ async function runAcpSession(opts) {
12617
12680
  pluginId: opts.pluginId,
12618
12681
  pluginAuthToken: opts.pluginAuthToken
12619
12682
  });
12683
+ const streaming = new StreamingState(publisher);
12620
12684
  let updateCount = 0;
12621
12685
  const client2 = new AcpClient({
12622
12686
  adapter: opts.adapter,
@@ -12624,15 +12688,13 @@ async function runAcpSession(opts) {
12624
12688
  onSessionUpdate: (notification) => {
12625
12689
  updateCount += 1;
12626
12690
  const variant = notification.update?.sessionUpdate ?? "unknown";
12627
- const chunks = mapSessionUpdate(notification);
12691
+ const deltas = mapSessionUpdate(notification);
12628
12692
  log.info(
12629
12693
  "acpRunner",
12630
- `update #${updateCount} variant=${variant} mappedChunks=${chunks.length}`
12694
+ `update #${updateCount} variant=${variant} mappedDeltas=${deltas.length}`
12631
12695
  );
12632
- for (const chunk2 of chunks) {
12633
- void publisher.publishChunk(chunk2).catch((err) => {
12634
- log.warn("acpRunner", `publishChunk failed: ${err instanceof Error ? err.message : String(err)}`);
12635
- });
12696
+ for (const delta of deltas) {
12697
+ streaming.append(delta);
12636
12698
  }
12637
12699
  },
12638
12700
  onRequestPermission: async (request) => {
@@ -12656,12 +12718,13 @@ async function runAcpSession(opts) {
12656
12718
  },
12657
12719
  onUnexpectedExit: (code, signal) => {
12658
12720
  log.warn("acpRunner", `adapter died code=${code} signal=${signal}; shutting down session`);
12659
- void publisher.publishChunk({
12660
- chunkId: (0, import_node_crypto6.randomUUID)(),
12661
- kind: "text",
12662
- content: `Agent adapter exited unexpectedly (code=${code ?? "null"} signal=${signal ?? "null"}).`,
12663
- isFinal: true
12664
- });
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
+ );
12665
12728
  process.exit(1);
12666
12729
  }
12667
12730
  });
@@ -12677,7 +12740,7 @@ async function runAcpSession(opts) {
12677
12740
  const relay = new CommandRelayService(
12678
12741
  opts.pluginId,
12679
12742
  async (cmd) => {
12680
- await handleCommand(cmd, client2, relay, acpSessionId, models);
12743
+ await handleCommand(cmd, client2, relay, acpSessionId, models, streaming);
12681
12744
  },
12682
12745
  { id: opts.agent, name: opts.agent, displayName: opts.agent }
12683
12746
  );
@@ -12694,7 +12757,7 @@ async function runAcpSession(opts) {
12694
12757
  await new Promise(() => {
12695
12758
  });
12696
12759
  }
12697
- async function handleCommand(cmd, client2, relay, acpSessionId, models) {
12760
+ async function handleCommand(cmd, client2, relay, acpSessionId, models, streaming) {
12698
12761
  switch (cmd.type) {
12699
12762
  case "start_task": {
12700
12763
  const payload = cmd.payload;
@@ -12705,11 +12768,14 @@ async function handleCommand(cmd, client2, relay, acpSessionId, models) {
12705
12768
  return;
12706
12769
  }
12707
12770
  log.info("acpRunner", `start_task \u2192 forwarding prompt chars=${prompt.length} id=${cmd.id.slice(0, 8)}`);
12771
+ await streaming.beginTurn();
12708
12772
  try {
12709
12773
  const reply = await client2.prompt(prompt);
12774
+ await streaming.closeAll();
12710
12775
  log.info("acpRunner", `start_task \u2190 done stopReason=${reply.stopReason ?? "?"} id=${cmd.id.slice(0, 8)}`);
12711
12776
  await relay.sendResult(cmd.id, "completed", { stopReason: reply.stopReason });
12712
12777
  } catch (err) {
12778
+ await streaming.closeAll();
12713
12779
  log.warn("acpRunner", `prompt failed: ${describeError(err)}`);
12714
12780
  await relay.sendResult(cmd.id, "failed", { error: describeError(err) });
12715
12781
  }
@@ -12719,6 +12785,7 @@ async function handleCommand(cmd, client2, relay, acpSessionId, models) {
12719
12785
  case "escape_key": {
12720
12786
  try {
12721
12787
  await client2.cancel();
12788
+ await streaming.closeAll();
12722
12789
  await relay.sendResult(cmd.id, "completed", {});
12723
12790
  } catch (err) {
12724
12791
  log.warn("acpRunner", `cancel failed: ${describeError(err)}`);
@@ -15188,13 +15255,13 @@ var TurnFileAggregator = class {
15188
15255
  return;
15189
15256
  }
15190
15257
  const chunks = chunkArray(novel, MAX_BATCH_SIZE);
15191
- for (const chunk2 of chunks) {
15258
+ for (const chunk of chunks) {
15192
15259
  const entry = {
15193
15260
  turnId: (0, import_crypto2.randomUUID)(),
15194
15261
  sessionId: this.opts.sessionId,
15195
15262
  pluginId: this.opts.pluginId,
15196
15263
  enqueuedAt: Date.now(),
15197
- files: chunk2
15264
+ files: chunk
15198
15265
  };
15199
15266
  await this.outbox.enqueue(entry);
15200
15267
  }
@@ -15355,11 +15422,11 @@ var StreamingEmitterService = class {
15355
15422
  }
15356
15423
  if (this.activeChunk) {
15357
15424
  const finalContent = this.activeChunk.currentContent;
15358
- const chunk2 = this.activeChunk;
15425
+ const chunk = this.activeChunk;
15359
15426
  this.activeChunk = null;
15360
15427
  await this.postChunk({
15361
- chunkId: chunk2.chunkId,
15362
- kind: chunk2.kind,
15428
+ chunkId: chunk.chunkId,
15429
+ kind: chunk.kind,
15363
15430
  content: finalContent,
15364
15431
  isFinal: true
15365
15432
  });
@@ -15431,17 +15498,17 @@ var StreamingEmitterService = class {
15431
15498
  this.maybeFlushActive(false);
15432
15499
  }
15433
15500
  maybeFlushActive(force) {
15434
- const chunk2 = this.activeChunk;
15435
- if (!chunk2) return;
15501
+ const chunk = this.activeChunk;
15502
+ if (!chunk) return;
15436
15503
  const now = Date.now();
15437
- if (!force && now - chunk2.lastEmitAt < TICK_MS) return;
15438
- if (chunk2.currentContent === chunk2.emittedContent) return;
15439
- chunk2.emittedContent = chunk2.currentContent;
15440
- chunk2.lastEmitAt = now;
15504
+ if (!force && now - chunk.lastEmitAt < TICK_MS) return;
15505
+ if (chunk.currentContent === chunk.emittedContent) return;
15506
+ chunk.emittedContent = chunk.currentContent;
15507
+ chunk.lastEmitAt = now;
15441
15508
  void this.postChunk({
15442
- chunkId: chunk2.chunkId,
15443
- kind: chunk2.kind,
15444
- content: chunk2.currentContent,
15509
+ chunkId: chunk.chunkId,
15510
+ kind: chunk.kind,
15511
+ content: chunk.currentContent,
15445
15512
  isFinal: false
15446
15513
  });
15447
15514
  }
@@ -16655,7 +16722,7 @@ function findGitRoot2(startDir) {
16655
16722
  }
16656
16723
 
16657
16724
  // src/commands/link.ts
16658
- var import_node_crypto7 = require("crypto");
16725
+ var import_node_crypto6 = require("crypto");
16659
16726
  var fs28 = __toESM(require("fs"));
16660
16727
  var path34 = __toESM(require("path"));
16661
16728
  var import_chokidar = __toESM(require("chokidar"));
@@ -16744,7 +16811,7 @@ async function link(args2 = []) {
16744
16811
  await linkDryRunPreflight(ctx);
16745
16812
  return;
16746
16813
  }
16747
- const pluginId = (0, import_node_crypto7.randomUUID)();
16814
+ const pluginId = (0, import_node_crypto6.randomUUID)();
16748
16815
  const spin = dist_exports.spinner();
16749
16816
  spin.start("Requesting pairing code...");
16750
16817
  const pairing = await requestCode(pluginId);
@@ -17978,8 +18045,8 @@ var previewStartH = (ctx, _cmd, parsed) => {
17978
18045
  let readyMatched = false;
17979
18046
  let expoUrl = null;
17980
18047
  const readyRe = new RegExp(detection.ready_pattern);
17981
- const onChunk = (chunk2) => {
17982
- const s = chunk2.toString();
18048
+ const onChunk = (chunk) => {
18049
+ const s = chunk.toString();
17983
18050
  if (!readyMatched && readyRe.test(s)) readyMatched = true;
17984
18051
  if (!expoUrl && detection.framework === "Expo") expoUrl = parseExpoUrl(s);
17985
18052
  };
@@ -18102,8 +18169,8 @@ var previewStartH = (ctx, _cmd, parsed) => {
18102
18169
  stdio: ["ignore", "pipe", "pipe"]
18103
18170
  });
18104
18171
  let parsedUrl = null;
18105
- const onTunnelChunk = (chunk2) => {
18106
- const s = chunk2.toString();
18172
+ const onTunnelChunk = (chunk) => {
18173
+ const s = chunk.toString();
18107
18174
  if (!parsedUrl) parsedUrl = parseCloudflaredUrl(s);
18108
18175
  const trimmed = s.replace(/\n+$/g, "");
18109
18176
  if (trimmed.length > 0) log.info("preview", `cloudflared: ${trimmed}`);
@@ -18202,8 +18269,8 @@ function runOnce(cmd, args2, cwd, env) {
18202
18269
  stdio: ["ignore", "pipe", "pipe"]
18203
18270
  });
18204
18271
  const tag = `setup:${cmd}`;
18205
- const onChunk = (chunk2) => {
18206
- const text = chunk2.toString().replace(/\n+$/g, "");
18272
+ const onChunk = (chunk) => {
18273
+ const text = chunk.toString().replace(/\n+$/g, "");
18207
18274
  if (text.length === 0) return;
18208
18275
  log.info("preview", `${tag}: ${text}`);
18209
18276
  };
@@ -21060,7 +21127,7 @@ async function stopWorkspaceFromLocal(target) {
21060
21127
  // src/commands/doctor.ts
21061
21128
  var import_node_dns = require("dns");
21062
21129
  var import_node_util4 = require("util");
21063
- var import_node_crypto8 = require("crypto");
21130
+ var import_node_crypto7 = require("crypto");
21064
21131
  var fs34 = __toESM(require("fs"));
21065
21132
  var path43 = __toESM(require("path"));
21066
21133
  var import_picocolors12 = __toESM(require("picocolors"));
@@ -21230,9 +21297,9 @@ function checkChokidar() {
21230
21297
  }
21231
21298
  async function doctor(args2 = []) {
21232
21299
  const json = args2.includes("--json");
21233
- const cliVersion = true ? "2.27.7" : "0.0.0-dev";
21300
+ const cliVersion = true ? "2.27.9" : "0.0.0-dev";
21234
21301
  const apiBase = resolveApiBaseUrl();
21235
- const diagnosticId = (0, import_node_crypto8.randomUUID)();
21302
+ const diagnosticId = (0, import_node_crypto7.randomUUID)();
21236
21303
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
21237
21304
  const [dns, health] = await Promise.all([
21238
21305
  checkDns(apiBase),
@@ -21429,7 +21496,7 @@ async function completion(args2) {
21429
21496
  // src/commands/version.ts
21430
21497
  var import_picocolors13 = __toESM(require("picocolors"));
21431
21498
  function version2() {
21432
- const v = true ? "2.27.7" : "unknown";
21499
+ const v = true ? "2.27.9" : "unknown";
21433
21500
  console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
21434
21501
  }
21435
21502
 
@@ -21616,8 +21683,8 @@ function fetchLatest() {
21616
21683
  }
21617
21684
  let buf = "";
21618
21685
  res.setEncoding("utf8");
21619
- res.on("data", (chunk2) => {
21620
- buf += chunk2;
21686
+ res.on("data", (chunk) => {
21687
+ buf += chunk;
21621
21688
  });
21622
21689
  res.on("end", () => {
21623
21690
  try {
@@ -21657,7 +21724,7 @@ function checkForUpdates() {
21657
21724
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
21658
21725
  if (process.env.CI) return;
21659
21726
  if (!process.stdout.isTTY) return;
21660
- const current = true ? "2.27.7" : null;
21727
+ const current = true ? "2.27.9" : null;
21661
21728
  if (!current) return;
21662
21729
  const cache = readCache();
21663
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.7",
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",