codeam-cli 2.76.30 → 2.76.32

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,21 @@ 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
+
7
22
  ## [2.76.29] — 2026-10-13
8
23
 
9
24
  ### Fixed
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.30" : "0.0.0-dev",
10630
+ cliVersion: true ? "2.76.32" : "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.30",
10879
+ version: "2.76.32",
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",
@@ -11038,7 +11038,7 @@ async function requestCode(pluginId, pluginSecretHash) {
11038
11038
  return { ok: false, reason: "network" };
11039
11039
  }
11040
11040
  }
11041
- async function fetchCurrentPluginAuthToken(sessionId, pluginId, pollSecret, onUserId) {
11041
+ async function fetchCurrentPluginAuthToken(sessionId, pluginId, pollSecret, onUserId, onSessionGone) {
11042
11042
  try {
11043
11043
  const result = await _transport.postJson(
11044
11044
  `${API_BASE}/api/pairing/reconnect`,
@@ -11070,10 +11070,21 @@ async function fetchCurrentPluginAuthToken(sessionId, pluginId, pollSecret, onUs
11070
11070
  }
11071
11071
  const token = data.pluginAuthToken;
11072
11072
  return typeof token === "string" && token.length > 0 ? token : null;
11073
- } catch {
11073
+ } catch (err) {
11074
+ if (onSessionGone && isSessionNotFound(err)) onSessionGone();
11074
11075
  return null;
11075
11076
  }
11076
11077
  }
11078
+ function isSessionNotFound(err) {
11079
+ const e = err;
11080
+ if (!e || e.statusCode !== 404 || typeof e.body !== "string") return false;
11081
+ try {
11082
+ const parsed = JSON.parse(e.body);
11083
+ return parsed?.code === "SESSION_NOT_FOUND" || parsed?.error?.code === "SESSION_NOT_FOUND";
11084
+ } catch {
11085
+ return false;
11086
+ }
11087
+ }
11077
11088
  var _transport = {
11078
11089
  postJson: _postJson,
11079
11090
  getJson: _getJson,
@@ -12241,7 +12252,7 @@ var CommandRelayService = class _CommandRelayService {
12241
12252
  // fresh + clear the "CLI update available" banner after a self-update
12242
12253
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
12243
12254
  // pair/reconnect). Older backends ignore the extra field.
12244
- ..."2.76.30" ? { ideVersion: "2.76.30" } : {}
12255
+ ..."2.76.32" ? { ideVersion: "2.76.32" } : {}
12245
12256
  };
12246
12257
  try {
12247
12258
  const res = await _postJson(`${API_BASE2}/api/plugin/heartbeat`, body, this.pollSecretHeader());
@@ -19489,7 +19500,7 @@ var CodexRuntimeStrategy = class {
19489
19500
  async generateOneShot(prompt, opts) {
19490
19501
  const binary = this.os.findInPath("codex");
19491
19502
  if (!binary) return null;
19492
- const launch = this.os.buildLaunch(binary, ["exec", prompt]);
19503
+ const launch = this.os.buildLaunch(binary, ["exec", "--skip-git-repo-check", prompt]);
19493
19504
  const runOnce = () => {
19494
19505
  let stderr = "";
19495
19506
  return spawnAndCapture(launch.cmd, launch.args, {
@@ -27280,7 +27291,12 @@ async function startDevServer(ctx, extraEnv = {}) {
27280
27291
  emitProgress("BOOT_SEQUENCE", `${spawnable.command} ${spawnable.args.join(" ")}`);
27281
27292
  const devServer = (0, import_child_process20.spawn)(spawnable.command, spawnable.args, {
27282
27293
  cwd,
27283
- env: { ...process.env, ...spawnable.env ?? {}, ...extraEnv },
27294
+ // PYTHONUNBUFFERED: Python block-buffers stdout into a pipe, so a
27295
+ // `python3 -m http.server` (what the detector picks for a plain HTML site)
27296
+ // never shows its "Serving HTTP on … (http://0.0.0.0:8000/)" line while it
27297
+ // already serves — every such preview ended in a 120 s ready_timeout
27298
+ // (codeagent-nczt.9). First, so the project's own env can still override it.
27299
+ env: { PYTHONUNBUFFERED: "1", ...process.env, ...spawnable.env ?? {}, ...extraEnv },
27284
27300
  stdio: ["ignore", "pipe", "pipe"],
27285
27301
  // POSIX: lead a new process group so teardown can SIGTERM the whole
27286
27302
  // tree. Dev servers fork worker children that bind the port; killing
@@ -38906,6 +38922,26 @@ function boundStreamingChunkContent(content, max = STREAMING_CHUNK_SNAPSHOT_MAX_
38906
38922
  truncated: true
38907
38923
  };
38908
38924
  }
38925
+ var CHAT_TEXT_MAX_BYTES = 192 * 1024;
38926
+ var CHAT_TEXT_FALLBACK_MAX_BYTES = 32 * 1024;
38927
+ function boundChatText(content, maxBytes = CHAT_TEXT_MAX_BYTES) {
38928
+ const bytes = Buffer.from(content, "utf8");
38929
+ if (bytes.length <= maxBytes) return { content, truncated: false };
38930
+ const budget = Math.max(0, maxBytes - 256);
38931
+ const headBytes = Math.floor(budget * 0.4);
38932
+ const tailBytes = budget - headBytes;
38933
+ const head = bytes.subarray(0, headBytes).toString("utf8").replace(/\uFFFD+$/, "");
38934
+ const tail = bytes.subarray(bytes.length - tailBytes).toString("utf8").replace(/^\uFFFD+/, "");
38935
+ const omittedKb = Math.max(1, Math.round((bytes.length - headBytes - tailBytes) / 1024));
38936
+ return {
38937
+ content: `${head}
38938
+
38939
+ \u2026 [${omittedKb} KB of this reply left out \u2014 too long to show in full] \u2026
38940
+
38941
+ ${tail}`,
38942
+ truncated: true
38943
+ };
38944
+ }
38909
38945
  var AcpPublisher = class {
38910
38946
  constructor(opts) {
38911
38947
  this.opts = opts;
@@ -38924,6 +38960,10 @@ var AcpPublisher = class {
38924
38960
  * (2026-06-28 incident: a dead-token publisher spammed 401 ×34 while
38925
38961
  * the agent's replies silently never reached the phone). */
38926
38962
  pairingInvalid = false;
38963
+ /** Set when the server refused a streaming `text` frame with 413: further
38964
+ * partial frames of the turn would be refused too, so they are dropped
38965
+ * until the next `new_turn` / `clear`. The terminal frame still goes. */
38966
+ oversizeTextTurn = false;
38927
38967
  authHeaders() {
38928
38968
  return {
38929
38969
  "Content-Type": "application/json",
@@ -39005,8 +39045,29 @@ var AcpPublisher = class {
39005
39045
  */
39006
39046
  async publishOutput(body) {
39007
39047
  const url2 = `${this.apiBase}/api/commands/output`;
39008
- try {
39009
- const { statusCode, body: resBody } = await this.postWithReauth(url2, this.envelope(body));
39048
+ const isText = body.type === "text" && typeof body.content === "string";
39049
+ const isFinal = body.done === true;
39050
+ if (body.type === "new_turn" || body.type === "clear") this.oversizeTextTurn = false;
39051
+ if (isText && !isFinal && this.oversizeTextTurn) return;
39052
+ if (isText) {
39053
+ const bounded = boundChatText(body.content);
39054
+ if (bounded.truncated) body = { ...body, content: bounded.content };
39055
+ }
39056
+ try {
39057
+ let { statusCode, body: resBody } = await this.postWithReauth(url2, this.envelope(body));
39058
+ if (statusCode === 413 && isText) {
39059
+ if (!isFinal) {
39060
+ this.oversizeTextTurn = true;
39061
+ log.warn("acpPublisher", "output text frame refused 413 \u2014 dropping partial frames until the turn ends");
39062
+ return;
39063
+ }
39064
+ const fallback = boundChatText(body.content, CHAT_TEXT_FALLBACK_MAX_BYTES);
39065
+ log.warn("acpPublisher", "output done:true refused 413 \u2014 retrying once with a shortened reply");
39066
+ ({ statusCode, body: resBody } = await this.postWithReauth(
39067
+ url2,
39068
+ this.envelope({ ...body, content: fallback.content })
39069
+ ));
39070
+ }
39010
39071
  if (statusCode < 200 || statusCode >= 300) {
39011
39072
  log.warn(
39012
39073
  "acpPublisher",
@@ -42061,7 +42122,11 @@ function describeResult(raw) {
42061
42122
  return {
42062
42123
  // The agent once answered a failed spawn with "the preview is open, here
42063
42124
  // is the link" (2026-10-05). Say plainly that nothing is serving.
42064
- 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.`,
42125
+ 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:
42126
+ \`\`\`
42127
+ ${String(r.output)}
42128
+ \`\`\`
42129
+ 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."),
42065
42130
  isError: true
42066
42131
  };
42067
42132
  default:
@@ -42394,6 +42459,7 @@ var MAX_AGENT_RESTARTS = 3;
42394
42459
  var AGENT_RESTART_WINDOW_MS = 10 * 6e4;
42395
42460
  var MAX_SELECTOR = 500;
42396
42461
  var MAX_LABEL = 80;
42462
+ var AGENT_OUTPUT_TAIL = 2e3;
42397
42463
  var AgentPreviewBridge = class {
42398
42464
  server = null;
42399
42465
  listening = null;
@@ -42599,9 +42665,11 @@ var AgentPreviewBridge = class {
42599
42665
  origin: "agent",
42600
42666
  onEvent: (type, payload) => {
42601
42667
  if (type === USER_EVENTS.PREVIEW_ERROR) {
42668
+ const output = String(payload.stderrTail ?? "").trim().slice(-AGENT_OUTPUT_TAIL);
42602
42669
  failure = {
42603
42670
  stage: String(payload.stage ?? "spawn"),
42604
- message: String(payload.message ?? "The preview failed to start.")
42671
+ message: String(payload.message ?? "The preview failed to start."),
42672
+ ...output ? { output } : {}
42605
42673
  };
42606
42674
  }
42607
42675
  }
@@ -47796,6 +47864,7 @@ async function start(requestedAgent, presetSession) {
47796
47864
  const cwd = process.cwd();
47797
47865
  let reconnectUserId;
47798
47866
  let reconnectPlan;
47867
+ let sessionGone = false;
47799
47868
  const refreshed = await fetchCurrentPluginAuthToken(
47800
47869
  session.id,
47801
47870
  pluginId,
@@ -47803,8 +47872,19 @@ async function start(requestedAgent, presetSession) {
47803
47872
  (userId, plan) => {
47804
47873
  reconnectUserId = userId;
47805
47874
  reconnectPlan = plan;
47875
+ },
47876
+ () => {
47877
+ sessionGone = true;
47806
47878
  }
47807
47879
  );
47880
+ if (sessionGone) {
47881
+ showInfo("This session was deleted from the app \u2014 not resuming it.");
47882
+ try {
47883
+ removeSession(session.id);
47884
+ } catch {
47885
+ }
47886
+ process.exit(0);
47887
+ }
47808
47888
  if (refreshed && refreshed !== session.pluginAuthToken) {
47809
47889
  addSession({ ...session, pluginAuthToken: refreshed });
47810
47890
  session.pluginAuthToken = refreshed;
@@ -51421,7 +51501,7 @@ async function autoUpgradeBeforeCriticalCommand() {
51421
51501
  if (process.env.NODE_ENV === "test") return;
51422
51502
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
51423
51503
  if (process.env.CI) return;
51424
- const current2 = true ? "2.76.30" : null;
51504
+ const current2 = true ? "2.76.32" : null;
51425
51505
  if (!current2) return;
51426
51506
  const cache = readCache();
51427
51507
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -51438,7 +51518,7 @@ function checkForUpdates() {
51438
51518
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
51439
51519
  if (process.env.CI) return;
51440
51520
  if (!process.stdout.isTTY) return;
51441
- const current2 = true ? "2.76.30" : null;
51521
+ const current2 = true ? "2.76.32" : null;
51442
51522
  if (!current2) return;
51443
51523
  const cache = readCache();
51444
51524
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -51721,7 +51801,7 @@ var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
51721
51801
  var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
51722
51802
  var SELF_UPDATE_LS_TIMEOUT_MS = 15e3;
51723
51803
  function currentCliVersion() {
51724
- return true ? "2.76.30" : null;
51804
+ return true ? "2.76.32" : null;
51725
51805
  }
51726
51806
  async function installedVersion(deps) {
51727
51807
  const ls = await deps.run(
@@ -51932,7 +52012,7 @@ function signalHostReady(send = process.send?.bind(process)) {
51932
52012
  }
51933
52013
  function defaultDeps() {
51934
52014
  return {
51935
- ownVersion: true ? "2.76.30" : null,
52015
+ ownVersion: true ? "2.76.32" : null,
51936
52016
  root: cliVersionsRoot(),
51937
52017
  args: process.argv.slice(3),
51938
52018
  execPath: process.execPath,
@@ -54470,7 +54550,7 @@ function checkChokidar() {
54470
54550
  }
54471
54551
  async function doctor(args2 = []) {
54472
54552
  const json = args2.includes("--json");
54473
- const cliVersion = true ? "2.76.30" : "0.0.0-dev";
54553
+ const cliVersion = true ? "2.76.32" : "0.0.0-dev";
54474
54554
  const apiBase2 = resolveApiBaseUrl();
54475
54555
  const diagnosticId = (0, import_node_crypto17.randomUUID)();
54476
54556
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -55006,7 +55086,7 @@ async function mcpWarm(argv = [], opts = {}) {
55006
55086
  // src/commands/version.ts
55007
55087
  var import_picocolors15 = __toESM(require("picocolors"));
55008
55088
  function version2() {
55009
- const v = true ? "2.76.30" : "unknown";
55089
+ const v = true ? "2.76.32" : "unknown";
55010
55090
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
55011
55091
  }
55012
55092
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.76.30",
3
+ "version": "2.76.32",
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",