codeam-cli 2.71.0 → 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 +16 -0
  2. package/dist/index.js +152 -19
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ 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
+
17
+ ## [2.71.0] — 2026-08-29
18
+
19
+ ### Added
20
+
21
+ - **cli:** Decir que base de datos falta, y con que variable se arregla (#668)
22
+
7
23
  ## [2.70.0] — 2026-08-29
8
24
 
9
25
  ### Fixed
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.0" : "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.0",
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",
@@ -8689,6 +8700,22 @@ async function postBatonEvent(input) {
8689
8700
  };
8690
8701
  }
8691
8702
  }
8703
+ async function postTurnEvent(input) {
8704
+ try {
8705
+ await _transport.postJsonAuthed(
8706
+ `${API_BASE}/api/commands/turn-events`,
8707
+ {
8708
+ pluginId: input.pluginId,
8709
+ commandId: input.commandId,
8710
+ phase: input.phase,
8711
+ agentId: input.agentId,
8712
+ errorCode: input.errorCode
8713
+ },
8714
+ input.pluginAuthToken
8715
+ );
8716
+ } catch {
8717
+ }
8718
+ }
8692
8719
  async function postHeadroomEvent(input) {
8693
8720
  try {
8694
8721
  await _transport.postJsonAuthed(
@@ -9473,9 +9500,14 @@ var CommandRelayService = class _CommandRelayService {
9473
9500
  // evicted (a command that old is long gone from the server queue anyway).
9474
9501
  processedIds = /* @__PURE__ */ new Set();
9475
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;
9476
9506
  /** Polling backoff state (only used on the fallback). */
9477
9507
  pollTimer = null;
9478
9508
  pollFailures = 0;
9509
+ /** Consecutive ack POST failures — drives the visibility escalation below. */
9510
+ ackFailures = 0;
9479
9511
  // Successive polls that returned no commands. Drives an idle
9480
9512
  // backoff so a CLI sitting on the polling fallback (SSE was
9481
9513
  // unavailable) doesn't keep hammering the API every 2 s when
@@ -9840,13 +9872,49 @@ var CommandRelayService = class _CommandRelayService {
9840
9872
  }
9841
9873
  }
9842
9874
  /** Confirm receipt of command ids so the backend removes them from the queue
9843
- * (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
+ */
9844
9889
  ackCommands(ids) {
9845
9890
  const commandIds = ids.filter((id) => typeof id === "string" && id.length > 0);
9846
9891
  if (commandIds.length === 0) return;
9847
9892
  void _postJson(`${API_BASE2}/api/commands/ack`, { pluginId: this.pluginId, commandIds }, {
9848
9893
  ...this.pollSecretHeader()
9849
- }).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
+ });
9850
9918
  }
9851
9919
  // ─── Heartbeat + agents ──────────────────────────────────────────
9852
9920
  async sendHeartbeat(online) {
@@ -9859,7 +9927,7 @@ var CommandRelayService = class _CommandRelayService {
9859
9927
  // fresh + clear the "CLI update available" banner after a self-update
9860
9928
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
9861
9929
  // pair/reconnect). Older backends ignore the extra field.
9862
- ..."2.71.0" ? { ideVersion: "2.71.0" } : {}
9930
+ ..."2.71.2" ? { ideVersion: "2.71.2" } : {}
9863
9931
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
9864
9932
  }
9865
9933
  /**
@@ -22118,7 +22186,7 @@ async function autoUpgradeBeforeCriticalCommand() {
22118
22186
  if (process.env.NODE_ENV === "test") return;
22119
22187
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
22120
22188
  if (process.env.CI) return;
22121
- const current2 = true ? "2.71.0" : null;
22189
+ const current2 = true ? "2.71.2" : null;
22122
22190
  if (!current2) return;
22123
22191
  const cache = readCache();
22124
22192
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -22135,7 +22203,7 @@ function checkForUpdates() {
22135
22203
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
22136
22204
  if (process.env.CI) return;
22137
22205
  if (!process.stdout.isTTY) return;
22138
- const current2 = true ? "2.71.0" : null;
22206
+ const current2 = true ? "2.71.2" : null;
22139
22207
  if (!current2) return;
22140
22208
  const cache = readCache();
22141
22209
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -22158,7 +22226,7 @@ var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
22158
22226
  var SELF_UPDATE_LS_TIMEOUT_MS = 15e3;
22159
22227
  var SUDO_PREFLIGHT_TIMEOUT_MS = 5e3;
22160
22228
  function currentCliVersion() {
22161
- return true ? "2.71.0" : null;
22229
+ return true ? "2.71.2" : null;
22162
22230
  }
22163
22231
  async function installedVersion(deps) {
22164
22232
  const ls = await deps.run(
@@ -24709,6 +24777,14 @@ var REQUIRED_FIELDS2 = [
24709
24777
  ];
24710
24778
  function safeParseDetection(raw) {
24711
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) {
24712
24788
  let parsed = tryParseObject(raw.trim());
24713
24789
  if (!parsed) {
24714
24790
  const stripped = raw.replace(/^```(?:json)?\s*/m, "").replace(/\s*```\s*$/m, "").trim();
@@ -24720,12 +24796,7 @@ function safeParseDetection(raw) {
24720
24796
  const candidate = extractFirstJsonObject(raw);
24721
24797
  if (candidate) parsed = tryParseObject(candidate);
24722
24798
  }
24723
- if (!parsed) return null;
24724
- const obj = parsed;
24725
- for (const field of REQUIRED_FIELDS2) {
24726
- if (!(field in obj)) return null;
24727
- }
24728
- return obj;
24799
+ return parsed ?? null;
24729
24800
  }
24730
24801
  function tryParseObject(s) {
24731
24802
  try {
@@ -24777,6 +24848,38 @@ function parseExpoUrl(stdout) {
24777
24848
  const match = stdout.match(EXPO_URL_RE);
24778
24849
  return match ? match[0] : null;
24779
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
+ }
24780
24883
 
24781
24884
  // src/services/preview/port-ready.ts
24782
24885
  var net2 = __toESM(require("net"));
@@ -27870,7 +27973,9 @@ function dispatchPrompt(ctx, prompt) {
27870
27973
  }
27871
27974
  var startTask = async (ctx, cmd, parsed) => {
27872
27975
  const { prompt, files } = parsed;
27976
+ reportTurn(ctx, cmd.id, "received");
27873
27977
  if (parsed.agentId && parsed.agentId !== ctx.agentId) {
27978
+ reportTurn(ctx, cmd.id, "failed", "AGENT_SWITCH_UNSUPPORTED");
27874
27979
  await ctx.relay.sendResult(cmd.id, "failed", {
27875
27980
  error: "Switching agents isn't supported on this session."
27876
27981
  });
@@ -27890,8 +27995,23 @@ var startTask = async (ctx, cmd, parsed) => {
27890
27995
  }, 12e4);
27891
27996
  } else if (effectivePrompt) {
27892
27997
  dispatchPrompt(ctx, effectivePrompt);
27998
+ } else {
27999
+ reportTurn(ctx, cmd.id, "failed", "EMPTY_PROMPT");
28000
+ return;
27893
28001
  }
28002
+ reportTurn(ctx, cmd.id, "started");
27894
28003
  };
28004
+ function reportTurn(ctx, commandId, phase, errorCode) {
28005
+ if (!ctx.pluginId || !ctx.pluginAuthToken) return;
28006
+ void postTurnEvent({
28007
+ pluginId: ctx.pluginId,
28008
+ pluginAuthToken: ctx.pluginAuthToken,
28009
+ commandId,
28010
+ phase,
28011
+ agentId: ctx.agentId,
28012
+ errorCode
28013
+ });
28014
+ }
27895
28015
  var provideInput = (ctx, _cmd, parsed) => {
27896
28016
  if (parsed.input) dispatchPrompt(ctx, parsed.input);
27897
28017
  };
@@ -29106,7 +29226,13 @@ var requestPreviewDetectH = (ctx) => {
29106
29226
  const tookMs = Date.now() - startedAt;
29107
29227
  const detection = safeParseDetection(raw);
29108
29228
  if (!detection) {
29109
- 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
+ );
29110
29236
  emitPreviewEvent({
29111
29237
  sessionId: ctx.sessionId,
29112
29238
  pluginId: ctx.pluginId,
@@ -29114,7 +29240,9 @@ var requestPreviewDetectH = (ctx) => {
29114
29240
  type: USER_EVENTS.PREVIEW_ERROR,
29115
29241
  payload: {
29116
29242
  stage: "detection",
29117
- 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."
29118
29246
  }
29119
29247
  });
29120
29248
  return;
@@ -29182,7 +29310,12 @@ function prewarmPreviewDetection(runtime) {
29182
29310
  if (await readPreviewConfig(cwd)) return;
29183
29311
  const raw = await runtime.generateOneShot(PREVIEW_DETECT_PROMPT).catch(() => null);
29184
29312
  const detection = safeParseDetection(raw);
29185
- 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;
29186
29319
  await writePreviewConfig(cwd, detection);
29187
29320
  log.info(
29188
29321
  "preview",
@@ -46726,7 +46859,7 @@ function checkChokidar() {
46726
46859
  }
46727
46860
  async function doctor(args2 = []) {
46728
46861
  const json = args2.includes("--json");
46729
- const cliVersion = true ? "2.71.0" : "0.0.0-dev";
46862
+ const cliVersion = true ? "2.71.2" : "0.0.0-dev";
46730
46863
  const apiBase2 = resolveApiBaseUrl();
46731
46864
  const diagnosticId = (0, import_node_crypto13.randomUUID)();
46732
46865
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -47117,7 +47250,7 @@ async function mcpRun(args2) {
47117
47250
  // src/commands/version.ts
47118
47251
  var import_picocolors15 = __toESM(require("picocolors"));
47119
47252
  function version2() {
47120
- const v = true ? "2.71.0" : "unknown";
47253
+ const v = true ? "2.71.2" : "unknown";
47121
47254
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
47122
47255
  }
47123
47256
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.71.0",
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",