codeam-cli 2.71.1 → 2.71.2

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 +10 -0
  2. package/dist/index.js +119 -19
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ 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.71.1] — 2026-08-30
8
+
9
+ ### Added
10
+
11
+ - **cli:** Reportar en qué fase va cada turno — la mitad del embudo que el servidor no ve (#670)
12
+
13
+ ### Tests
14
+
15
+ - **cli:** Dar plazo realista a los dos tests que conducen start() de verdad (#669)
16
+
7
17
  ## [2.71.0] — 2026-08-29
8
18
 
9
19
  ### Added
package/dist/index.js CHANGED
@@ -2909,6 +2909,17 @@ var USER_EVENTS = {
2909
2909
  SELF_HOSTED_HOST_STATUS: "self_hosted_host_status",
2910
2910
  SELF_HOSTED_HOST_REMOVED: "self_hosted_host_removed",
2911
2911
  SELF_HOSTED_HOST_TELEMETRY: "self_hosted_host_telemetry",
2912
+ /**
2913
+ * Una orden que NINGUN CLI recogio y que expiro de la cola.
2914
+ *
2915
+ * La cola `commands:<pluginId>` caduca a los 600 s y Redis no avisa a nadie:
2916
+ * la tarea deja de existir en silencio. Medido el 2026-08-30 en un usuario
2917
+ * real, SIETE de sus 17 `start_task` acabaron asi — no recibio nada y no
2918
+ * habia forma de saber que se habian descartado. Lo publica el barrido
2919
+ * `command-expiry-sweep` del backend; nadie puede notificar su propio
2920
+ * silencio.
2921
+ */
2922
+ COMMAND_EXPIRED: "command_expired",
2912
2923
  SELF_HOSTED_HOST_METRICS: "self_hosted_host_metrics",
2913
2924
  SELF_HOSTED_HOST_SESSIONS: "self_hosted_host_sessions",
2914
2925
  SELF_HOSTED_DEPLOY_PROGRESS: "self_hosted_deploy_progress",
@@ -8081,7 +8092,7 @@ function readAnonId() {
8081
8092
  }
8082
8093
  function superProperties() {
8083
8094
  return {
8084
- cliVersion: true ? "2.71.1" : "0.0.0-dev",
8095
+ cliVersion: true ? "2.71.2" : "0.0.0-dev",
8085
8096
  nodeVersion: process.version,
8086
8097
  platform: process.platform,
8087
8098
  arch: process.arch,
@@ -8323,7 +8334,7 @@ var http = __toESM(require("http"));
8323
8334
  // package.json
8324
8335
  var package_default = {
8325
8336
  name: "codeam-cli",
8326
- version: "2.71.1",
8337
+ version: "2.71.2",
8327
8338
  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.",
8328
8339
  type: "commonjs",
8329
8340
  main: "dist/index.js",
@@ -9489,9 +9500,14 @@ var CommandRelayService = class _CommandRelayService {
9489
9500
  // evicted (a command that old is long gone from the server queue anyway).
9490
9501
  processedIds = /* @__PURE__ */ new Set();
9491
9502
  static PROCESSED_ID_CAP = 1e3;
9503
+ /** After the first, re-warn every Nth consecutive ack failure — loud enough
9504
+ * to notice, quiet enough not to drown the log during a network outage. */
9505
+ static ACK_WARN_EVERY = 20;
9492
9506
  /** Polling backoff state (only used on the fallback). */
9493
9507
  pollTimer = null;
9494
9508
  pollFailures = 0;
9509
+ /** Consecutive ack POST failures — drives the visibility escalation below. */
9510
+ ackFailures = 0;
9495
9511
  // Successive polls that returned no commands. Drives an idle
9496
9512
  // backoff so a CLI sitting on the polling fallback (SSE was
9497
9513
  // unavailable) doesn't keep hammering the API every 2 s when
@@ -9856,13 +9872,49 @@ var CommandRelayService = class _CommandRelayService {
9856
9872
  }
9857
9873
  }
9858
9874
  /** Confirm receipt of command ids so the backend removes them from the queue
9859
- * (the at-least-once delivery guarantee). Best-effort — never throws. */
9875
+ * (the at-least-once delivery guarantee). Best-effort — never throws.
9876
+ *
9877
+ * ⚠️ A ONE-OFF failure here is harmless (the command redelivers and we dedupe),
9878
+ * but a PERSISTENT one is not: the queue never drains, so every batch is
9879
+ * redelivered forever and the host looks perfectly healthy from outside —
9880
+ * heartbeats land, commands still arrive, `status` stays `online`. That is
9881
+ * exactly how the fleet host ran from 2026-07-15 to 2026-08-31 with NO poll
9882
+ * secret enrolled, 401-ing every single ack, with nobody the wiser: the
9883
+ * failure lived in `log.trace`, below the level anyone reads.
9884
+ *
9885
+ * So: keep it best-effort, but make a SUSTAINED failure impossible to miss,
9886
+ * and name `PLUGIN_SECRET_REQUIRED` explicitly — it is not a network blip,
9887
+ * it is a host that must be re-enrolled and will never recover on its own.
9888
+ */
9860
9889
  ackCommands(ids) {
9861
9890
  const commandIds = ids.filter((id) => typeof id === "string" && id.length > 0);
9862
9891
  if (commandIds.length === 0) return;
9863
9892
  void _postJson(`${API_BASE2}/api/commands/ack`, { pluginId: this.pluginId, commandIds }, {
9864
9893
  ...this.pollSecretHeader()
9865
- }).catch((err) => log.trace("relay", "ack post failed (will redeliver+dedupe)", err));
9894
+ }).then(() => {
9895
+ if (this.ackFailures > 0) {
9896
+ log.warn("relay", `ack recovered after ${this.ackFailures} consecutive failure(s)`);
9897
+ this.ackFailures = 0;
9898
+ }
9899
+ }).catch((err) => {
9900
+ this.ackFailures += 1;
9901
+ const reason = err instanceof Error ? err.message : String(err);
9902
+ if (/PLUGIN_SECRET_REQUIRED|INVALID_PLUGIN_SECRET/.test(reason)) {
9903
+ log.warn(
9904
+ "relay",
9905
+ `ack REJECTED (${reason}) \u2014 this host needs to be re-paired; commands will be redelivered forever until it is`
9906
+ );
9907
+ return;
9908
+ }
9909
+ if (this.ackFailures === 1 || this.ackFailures % _CommandRelayService.ACK_WARN_EVERY === 0) {
9910
+ log.warn(
9911
+ "relay",
9912
+ `ack failed ${this.ackFailures}x in a row \u2014 the backend queue is not draining: ${reason}`
9913
+ );
9914
+ return;
9915
+ }
9916
+ log.trace("relay", "ack post failed (will redeliver+dedupe)", err);
9917
+ });
9866
9918
  }
9867
9919
  // ─── Heartbeat + agents ──────────────────────────────────────────
9868
9920
  async sendHeartbeat(online) {
@@ -9875,7 +9927,7 @@ var CommandRelayService = class _CommandRelayService {
9875
9927
  // fresh + clear the "CLI update available" banner after a self-update
9876
9928
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
9877
9929
  // pair/reconnect). Older backends ignore the extra field.
9878
- ..."2.71.1" ? { ideVersion: "2.71.1" } : {}
9930
+ ..."2.71.2" ? { ideVersion: "2.71.2" } : {}
9879
9931
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
9880
9932
  }
9881
9933
  /**
@@ -22134,7 +22186,7 @@ async function autoUpgradeBeforeCriticalCommand() {
22134
22186
  if (process.env.NODE_ENV === "test") return;
22135
22187
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
22136
22188
  if (process.env.CI) return;
22137
- const current2 = true ? "2.71.1" : null;
22189
+ const current2 = true ? "2.71.2" : null;
22138
22190
  if (!current2) return;
22139
22191
  const cache = readCache();
22140
22192
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -22151,7 +22203,7 @@ function checkForUpdates() {
22151
22203
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
22152
22204
  if (process.env.CI) return;
22153
22205
  if (!process.stdout.isTTY) return;
22154
- const current2 = true ? "2.71.1" : null;
22206
+ const current2 = true ? "2.71.2" : null;
22155
22207
  if (!current2) return;
22156
22208
  const cache = readCache();
22157
22209
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -22174,7 +22226,7 @@ var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
22174
22226
  var SELF_UPDATE_LS_TIMEOUT_MS = 15e3;
22175
22227
  var SUDO_PREFLIGHT_TIMEOUT_MS = 5e3;
22176
22228
  function currentCliVersion() {
22177
- return true ? "2.71.1" : null;
22229
+ return true ? "2.71.2" : null;
22178
22230
  }
22179
22231
  async function installedVersion(deps) {
22180
22232
  const ls = await deps.run(
@@ -24725,6 +24777,14 @@ var REQUIRED_FIELDS2 = [
24725
24777
  ];
24726
24778
  function safeParseDetection(raw) {
24727
24779
  if (!raw) return null;
24780
+ const parsed = parseDetectionObject(raw);
24781
+ if (!parsed) return null;
24782
+ for (const field of REQUIRED_FIELDS2) {
24783
+ if (!(field in parsed)) return null;
24784
+ }
24785
+ return parsed;
24786
+ }
24787
+ function parseDetectionObject(raw) {
24728
24788
  let parsed = tryParseObject(raw.trim());
24729
24789
  if (!parsed) {
24730
24790
  const stripped = raw.replace(/^```(?:json)?\s*/m, "").replace(/\s*```\s*$/m, "").trim();
@@ -24736,12 +24796,7 @@ function safeParseDetection(raw) {
24736
24796
  const candidate = extractFirstJsonObject(raw);
24737
24797
  if (candidate) parsed = tryParseObject(candidate);
24738
24798
  }
24739
- if (!parsed) return null;
24740
- const obj = parsed;
24741
- for (const field of REQUIRED_FIELDS2) {
24742
- if (!(field in obj)) return null;
24743
- }
24744
- return obj;
24799
+ return parsed ?? null;
24745
24800
  }
24746
24801
  function tryParseObject(s) {
24747
24802
  try {
@@ -24793,6 +24848,38 @@ function parseExpoUrl(stdout) {
24793
24848
  const match = stdout.match(EXPO_URL_RE);
24794
24849
  return match ? match[0] : null;
24795
24850
  }
24851
+ var RAW_EXCERPT_LIMIT = 1e3;
24852
+ function describeDetectionFailure(raw) {
24853
+ const text = raw?.trim() ?? "";
24854
+ const rawExcerpt = text.slice(0, RAW_EXCERPT_LIMIT);
24855
+ if (!text) {
24856
+ return {
24857
+ reason: "no_output",
24858
+ message: "The agent didn't return anything for this project. Try again, or add a .codeam/preview.json override.",
24859
+ rawExcerpt
24860
+ };
24861
+ }
24862
+ const parsed = parseDetectionObject(text);
24863
+ if (!parsed) {
24864
+ return {
24865
+ reason: "no_json",
24866
+ message: "The agent replied without a JSON block. Try again, or add a .codeam/preview.json override.",
24867
+ rawExcerpt
24868
+ };
24869
+ }
24870
+ const missing = REQUIRED_FIELDS2.filter((f) => !(f in parsed)).map(String);
24871
+ if (missing.length > 0) {
24872
+ return {
24873
+ reason: "missing_fields",
24874
+ // Decir CUALES: es lo unico con lo que alguien puede escribir el
24875
+ // override a mano y desbloquearse sin esperar a nadie.
24876
+ message: `The agent's answer is missing ${missing.join(", ")}. Try again, or add a .codeam/preview.json override.`,
24877
+ missing,
24878
+ rawExcerpt
24879
+ };
24880
+ }
24881
+ return null;
24882
+ }
24796
24883
 
24797
24884
  // src/services/preview/port-ready.ts
24798
24885
  var net2 = __toESM(require("net"));
@@ -29139,7 +29226,13 @@ var requestPreviewDetectH = (ctx) => {
29139
29226
  const tookMs = Date.now() - startedAt;
29140
29227
  const detection = safeParseDetection(raw);
29141
29228
  if (!detection) {
29142
- log.info("preview", `detect: invalid agent output after ${tookMs}ms`);
29229
+ const failure = describeDetectionFailure(raw);
29230
+ log.info(
29231
+ "preview",
29232
+ `detect: failed after ${tookMs}ms reason=${failure?.reason ?? "unknown"}` + (failure?.missing ? ` missing=${failure.missing.join(",")}` : "") + `
29233
+ --- salida cruda del agente (acotada) ---
29234
+ ${failure?.rawExcerpt ?? ""}`
29235
+ );
29143
29236
  emitPreviewEvent({
29144
29237
  sessionId: ctx.sessionId,
29145
29238
  pluginId: ctx.pluginId,
@@ -29147,7 +29240,9 @@ var requestPreviewDetectH = (ctx) => {
29147
29240
  type: USER_EVENTS.PREVIEW_ERROR,
29148
29241
  payload: {
29149
29242
  stage: "detection",
29150
- message: "Agent returned invalid JSON. Try again, or add a .codeam/preview.json override."
29243
+ // El mensaje sale del diagnostico: decir "JSON invalido" cuando el
29244
+ // agente no contesto manda al usuario a mirar un JSON que no existe.
29245
+ message: failure?.message ?? "Agent returned invalid JSON. Try again, or add a .codeam/preview.json override."
29151
29246
  }
29152
29247
  });
29153
29248
  return;
@@ -29215,7 +29310,12 @@ function prewarmPreviewDetection(runtime) {
29215
29310
  if (await readPreviewConfig(cwd)) return;
29216
29311
  const raw = await runtime.generateOneShot(PREVIEW_DETECT_PROMPT).catch(() => null);
29217
29312
  const detection = safeParseDetection(raw);
29218
- if (!detection || detection.framework === "unsupported") return;
29313
+ if (!detection) {
29314
+ const failure = describeDetectionFailure(raw);
29315
+ log.info("preview", `prewarm: detection failed reason=${failure?.reason ?? "unknown"}`);
29316
+ return;
29317
+ }
29318
+ if (detection.framework === "unsupported") return;
29219
29319
  await writePreviewConfig(cwd, detection);
29220
29320
  log.info(
29221
29321
  "preview",
@@ -46759,7 +46859,7 @@ function checkChokidar() {
46759
46859
  }
46760
46860
  async function doctor(args2 = []) {
46761
46861
  const json = args2.includes("--json");
46762
- const cliVersion = true ? "2.71.1" : "0.0.0-dev";
46862
+ const cliVersion = true ? "2.71.2" : "0.0.0-dev";
46763
46863
  const apiBase2 = resolveApiBaseUrl();
46764
46864
  const diagnosticId = (0, import_node_crypto13.randomUUID)();
46765
46865
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -47150,7 +47250,7 @@ async function mcpRun(args2) {
47150
47250
  // src/commands/version.ts
47151
47251
  var import_picocolors15 = __toESM(require("picocolors"));
47152
47252
  function version2() {
47153
- const v = true ? "2.71.1" : "unknown";
47253
+ const v = true ? "2.71.2" : "unknown";
47154
47254
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
47155
47255
  }
47156
47256
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.71.1",
3
+ "version": "2.71.2",
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",