codeam-cli 2.76.29 → 2.76.31

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,27 @@ 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.76.30] — 2026-10-14
8
+
9
+ ### CI
10
+
11
+ - **release:** Build the Box image only after npm serves the version
12
+
13
+ ### Documentation
14
+
15
+ - **release:** Describe the next → canary → latest release gate in the README
16
+
17
+ ### Fixed
18
+
19
+ - **cli:** Preview prewarm skips an empty scratch project
20
+ - **cli:** Identify with the current plan from /reconnect, not the pair-time one
21
+
22
+ ## [2.76.29] — 2026-10-13
23
+
24
+ ### Fixed
25
+
26
+ - **cli:** Run a queued message only after the running turn closes
27
+
7
28
  ## [2.76.28] — 2026-10-13
8
29
 
9
30
  ### Added
package/dist/index.js CHANGED
@@ -10627,7 +10627,7 @@ function readAnonId() {
10627
10627
  }
10628
10628
  function superProperties() {
10629
10629
  return {
10630
- cliVersion: true ? "2.76.29" : "0.0.0-dev",
10630
+ cliVersion: true ? "2.76.31" : "0.0.0-dev",
10631
10631
  nodeVersion: process.version,
10632
10632
  platform: process.platform,
10633
10633
  arch: process.arch,
@@ -10876,7 +10876,7 @@ var http = __toESM(require("http"));
10876
10876
  // package.json
10877
10877
  var package_default = {
10878
10878
  name: "codeam-cli",
10879
- version: "2.76.29",
10879
+ version: "2.76.31",
10880
10880
  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.",
10881
10881
  type: "commonjs",
10882
10882
  main: "dist/index.js",
@@ -11064,7 +11064,9 @@ async function fetchCurrentPluginAuthToken(sessionId, pluginId, pollSecret, onUs
11064
11064
  const data = result?.data;
11065
11065
  if (!data?.paired) return null;
11066
11066
  if (onUserId && typeof data.userId === "string" && data.userId.length > 0) {
11067
- onUserId(data.userId);
11067
+ const user = data.user;
11068
+ const plan = typeof user === "object" && user !== null && "plan" in user && typeof user.plan === "string" ? user.plan : void 0;
11069
+ onUserId(data.userId, plan);
11068
11070
  }
11069
11071
  const token = data.pluginAuthToken;
11070
11072
  return typeof token === "string" && token.length > 0 ? token : null;
@@ -12239,7 +12241,7 @@ var CommandRelayService = class _CommandRelayService {
12239
12241
  // fresh + clear the "CLI update available" banner after a self-update
12240
12242
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
12241
12243
  // pair/reconnect). Older backends ignore the extra field.
12242
- ..."2.76.29" ? { ideVersion: "2.76.29" } : {}
12244
+ ..."2.76.31" ? { ideVersion: "2.76.31" } : {}
12243
12245
  };
12244
12246
  try {
12245
12247
  const res = await _postJson(`${API_BASE2}/api/plugin/heartbeat`, body, this.pollSecretHeader());
@@ -19487,7 +19489,7 @@ var CodexRuntimeStrategy = class {
19487
19489
  async generateOneShot(prompt, opts) {
19488
19490
  const binary = this.os.findInPath("codex");
19489
19491
  if (!binary) return null;
19490
- const launch = this.os.buildLaunch(binary, ["exec", prompt]);
19492
+ const launch = this.os.buildLaunch(binary, ["exec", "--skip-git-repo-check", prompt]);
19491
19493
  const runOnce = () => {
19492
19494
  let stderr = "";
19493
19495
  return spawnAndCapture(launch.cmd, launch.args, {
@@ -27278,7 +27280,12 @@ async function startDevServer(ctx, extraEnv = {}) {
27278
27280
  emitProgress("BOOT_SEQUENCE", `${spawnable.command} ${spawnable.args.join(" ")}`);
27279
27281
  const devServer = (0, import_child_process20.spawn)(spawnable.command, spawnable.args, {
27280
27282
  cwd,
27281
- env: { ...process.env, ...spawnable.env ?? {}, ...extraEnv },
27283
+ // PYTHONUNBUFFERED: Python block-buffers stdout into a pipe, so a
27284
+ // `python3 -m http.server` (what the detector picks for a plain HTML site)
27285
+ // never shows its "Serving HTTP on … (http://0.0.0.0:8000/)" line while it
27286
+ // already serves — every such preview ended in a 120 s ready_timeout
27287
+ // (codeagent-nczt.9). First, so the project's own env can still override it.
27288
+ env: { PYTHONUNBUFFERED: "1", ...process.env, ...spawnable.env ?? {}, ...extraEnv },
27282
27289
  stdio: ["ignore", "pipe", "pipe"],
27283
27290
  // POSIX: lead a new process group so teardown can SIGTERM the whole
27284
27291
  // tree. Dev servers fork worker children that bind the port; killing
@@ -30341,6 +30348,7 @@ function prewarmPreviewDetection(runtime) {
30341
30348
  try {
30342
30349
  const cwd = process.cwd();
30343
30350
  if (await readPreviewConfig(cwd)) return;
30351
+ if (isEmptyScratchProject(cwd)) return;
30344
30352
  const { raw } = await runDetectOneShot(generateOneShot);
30345
30353
  const detection = safeParseDetection(raw);
30346
30354
  if (!detection) {
@@ -38903,6 +38911,26 @@ function boundStreamingChunkContent(content, max = STREAMING_CHUNK_SNAPSHOT_MAX_
38903
38911
  truncated: true
38904
38912
  };
38905
38913
  }
38914
+ var CHAT_TEXT_MAX_BYTES = 192 * 1024;
38915
+ var CHAT_TEXT_FALLBACK_MAX_BYTES = 32 * 1024;
38916
+ function boundChatText(content, maxBytes = CHAT_TEXT_MAX_BYTES) {
38917
+ const bytes = Buffer.from(content, "utf8");
38918
+ if (bytes.length <= maxBytes) return { content, truncated: false };
38919
+ const budget = Math.max(0, maxBytes - 256);
38920
+ const headBytes = Math.floor(budget * 0.4);
38921
+ const tailBytes = budget - headBytes;
38922
+ const head = bytes.subarray(0, headBytes).toString("utf8").replace(/\uFFFD+$/, "");
38923
+ const tail = bytes.subarray(bytes.length - tailBytes).toString("utf8").replace(/^\uFFFD+/, "");
38924
+ const omittedKb = Math.max(1, Math.round((bytes.length - headBytes - tailBytes) / 1024));
38925
+ return {
38926
+ content: `${head}
38927
+
38928
+ \u2026 [${omittedKb} KB of this reply left out \u2014 too long to show in full] \u2026
38929
+
38930
+ ${tail}`,
38931
+ truncated: true
38932
+ };
38933
+ }
38906
38934
  var AcpPublisher = class {
38907
38935
  constructor(opts) {
38908
38936
  this.opts = opts;
@@ -38921,6 +38949,10 @@ var AcpPublisher = class {
38921
38949
  * (2026-06-28 incident: a dead-token publisher spammed 401 ×34 while
38922
38950
  * the agent's replies silently never reached the phone). */
38923
38951
  pairingInvalid = false;
38952
+ /** Set when the server refused a streaming `text` frame with 413: further
38953
+ * partial frames of the turn would be refused too, so they are dropped
38954
+ * until the next `new_turn` / `clear`. The terminal frame still goes. */
38955
+ oversizeTextTurn = false;
38924
38956
  authHeaders() {
38925
38957
  return {
38926
38958
  "Content-Type": "application/json",
@@ -39002,8 +39034,29 @@ var AcpPublisher = class {
39002
39034
  */
39003
39035
  async publishOutput(body) {
39004
39036
  const url2 = `${this.apiBase}/api/commands/output`;
39005
- try {
39006
- const { statusCode, body: resBody } = await this.postWithReauth(url2, this.envelope(body));
39037
+ const isText = body.type === "text" && typeof body.content === "string";
39038
+ const isFinal = body.done === true;
39039
+ if (body.type === "new_turn" || body.type === "clear") this.oversizeTextTurn = false;
39040
+ if (isText && !isFinal && this.oversizeTextTurn) return;
39041
+ if (isText) {
39042
+ const bounded = boundChatText(body.content);
39043
+ if (bounded.truncated) body = { ...body, content: bounded.content };
39044
+ }
39045
+ try {
39046
+ let { statusCode, body: resBody } = await this.postWithReauth(url2, this.envelope(body));
39047
+ if (statusCode === 413 && isText) {
39048
+ if (!isFinal) {
39049
+ this.oversizeTextTurn = true;
39050
+ log.warn("acpPublisher", "output text frame refused 413 \u2014 dropping partial frames until the turn ends");
39051
+ return;
39052
+ }
39053
+ const fallback = boundChatText(body.content, CHAT_TEXT_FALLBACK_MAX_BYTES);
39054
+ log.warn("acpPublisher", "output done:true refused 413 \u2014 retrying once with a shortened reply");
39055
+ ({ statusCode, body: resBody } = await this.postWithReauth(
39056
+ url2,
39057
+ this.envelope({ ...body, content: fallback.content })
39058
+ ));
39059
+ }
39007
39060
  if (statusCode < 200 || statusCode >= 300) {
39008
39061
  log.warn(
39009
39062
  "acpPublisher",
@@ -42058,7 +42111,11 @@ function describeResult(raw) {
42058
42111
  return {
42059
42112
  // The agent once answered a failed spawn with "the preview is open, here
42060
42113
  // is the link" (2026-10-05). Say plainly that nothing is serving.
42061
- text: `Preview failed at ${String(r.stage)}: ${String(r.message)}. The preview is NOT running \u2014 do not give the user a preview link; tell them what failed.`,
42114
+ text: `Preview failed at ${String(r.stage)}: ${String(r.message).replace(/\.+$/, "")}. The preview is NOT running \u2014 do not give the user a preview link.` + (r.output ? ` The dev server's last output:
42115
+ \`\`\`
42116
+ ${String(r.output)}
42117
+ \`\`\`
42118
+ If it shows a mistake in the command (a wrong flag, a missing file), call start_preview again with the fixed command; otherwise tell the user what failed.` : " Tell the user what failed."),
42062
42119
  isError: true
42063
42120
  };
42064
42121
  default:
@@ -42391,6 +42448,7 @@ var MAX_AGENT_RESTARTS = 3;
42391
42448
  var AGENT_RESTART_WINDOW_MS = 10 * 6e4;
42392
42449
  var MAX_SELECTOR = 500;
42393
42450
  var MAX_LABEL = 80;
42451
+ var AGENT_OUTPUT_TAIL = 2e3;
42394
42452
  var AgentPreviewBridge = class {
42395
42453
  server = null;
42396
42454
  listening = null;
@@ -42596,9 +42654,11 @@ var AgentPreviewBridge = class {
42596
42654
  origin: "agent",
42597
42655
  onEvent: (type, payload) => {
42598
42656
  if (type === USER_EVENTS.PREVIEW_ERROR) {
42657
+ const output = String(payload.stderrTail ?? "").trim().slice(-AGENT_OUTPUT_TAIL);
42599
42658
  failure = {
42600
42659
  stage: String(payload.stage ?? "spawn"),
42601
- message: String(payload.message ?? "The preview failed to start.")
42660
+ message: String(payload.message ?? "The preview failed to start."),
42661
+ ...output ? { output } : {}
42602
42662
  };
42603
42663
  }
42604
42664
  }
@@ -47792,12 +47852,14 @@ async function start(requestedAgent, presetSession) {
47792
47852
  `);
47793
47853
  const cwd = process.cwd();
47794
47854
  let reconnectUserId;
47855
+ let reconnectPlan;
47795
47856
  const refreshed = await fetchCurrentPluginAuthToken(
47796
47857
  session.id,
47797
47858
  pluginId,
47798
47859
  session.pollSecret,
47799
- (userId) => {
47860
+ (userId, plan) => {
47800
47861
  reconnectUserId = userId;
47862
+ reconnectPlan = plan;
47801
47863
  }
47802
47864
  );
47803
47865
  if (refreshed && refreshed !== session.pluginAuthToken) {
@@ -47807,8 +47869,11 @@ async function start(requestedAgent, presetSession) {
47807
47869
  } else if (refreshed) {
47808
47870
  showInfo("Reconnected previous session.");
47809
47871
  }
47810
- if (reconnectUserId && reconnectUserId !== session.userId) {
47811
- session.userId = reconnectUserId;
47872
+ const staleUserId = !!reconnectUserId && reconnectUserId !== session.userId;
47873
+ const stalePlan = !!reconnectPlan && reconnectPlan !== session.plan;
47874
+ if (staleUserId || stalePlan) {
47875
+ if (reconnectUserId) session.userId = reconnectUserId;
47876
+ if (reconnectPlan) session.plan = reconnectPlan;
47812
47877
  addSession({ ...session });
47813
47878
  }
47814
47879
  if (session.userId) {
@@ -51413,7 +51478,7 @@ async function autoUpgradeBeforeCriticalCommand() {
51413
51478
  if (process.env.NODE_ENV === "test") return;
51414
51479
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
51415
51480
  if (process.env.CI) return;
51416
- const current2 = true ? "2.76.29" : null;
51481
+ const current2 = true ? "2.76.31" : null;
51417
51482
  if (!current2) return;
51418
51483
  const cache = readCache();
51419
51484
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -51430,7 +51495,7 @@ function checkForUpdates() {
51430
51495
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
51431
51496
  if (process.env.CI) return;
51432
51497
  if (!process.stdout.isTTY) return;
51433
- const current2 = true ? "2.76.29" : null;
51498
+ const current2 = true ? "2.76.31" : null;
51434
51499
  if (!current2) return;
51435
51500
  const cache = readCache();
51436
51501
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -51713,7 +51778,7 @@ var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
51713
51778
  var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
51714
51779
  var SELF_UPDATE_LS_TIMEOUT_MS = 15e3;
51715
51780
  function currentCliVersion() {
51716
- return true ? "2.76.29" : null;
51781
+ return true ? "2.76.31" : null;
51717
51782
  }
51718
51783
  async function installedVersion(deps) {
51719
51784
  const ls = await deps.run(
@@ -51924,7 +51989,7 @@ function signalHostReady(send = process.send?.bind(process)) {
51924
51989
  }
51925
51990
  function defaultDeps() {
51926
51991
  return {
51927
- ownVersion: true ? "2.76.29" : null,
51992
+ ownVersion: true ? "2.76.31" : null,
51928
51993
  root: cliVersionsRoot(),
51929
51994
  args: process.argv.slice(3),
51930
51995
  execPath: process.execPath,
@@ -54462,7 +54527,7 @@ function checkChokidar() {
54462
54527
  }
54463
54528
  async function doctor(args2 = []) {
54464
54529
  const json = args2.includes("--json");
54465
- const cliVersion = true ? "2.76.29" : "0.0.0-dev";
54530
+ const cliVersion = true ? "2.76.31" : "0.0.0-dev";
54466
54531
  const apiBase2 = resolveApiBaseUrl();
54467
54532
  const diagnosticId = (0, import_node_crypto17.randomUUID)();
54468
54533
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -54998,7 +55063,7 @@ async function mcpWarm(argv = [], opts = {}) {
54998
55063
  // src/commands/version.ts
54999
55064
  var import_picocolors15 = __toESM(require("picocolors"));
55000
55065
  function version2() {
55001
- const v = true ? "2.76.29" : "unknown";
55066
+ const v = true ? "2.76.31" : "unknown";
55002
55067
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
55003
55068
  }
55004
55069
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.76.29",
3
+ "version": "2.76.31",
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",