@evident-ai/cli 3.5.0 → 3.5.1-dev.4a593b3

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/dist/index.js CHANGED
@@ -1158,7 +1158,7 @@ import ora4 from "ora";
1158
1158
  import { select as select4 } from "@inquirer/prompts";
1159
1159
 
1160
1160
  // src/lib/telemetry.ts
1161
- var CLI_VERSION = (true ? "3.5.0" : void 0) ?? process.env.npm_package_version ?? "unknown";
1161
+ var CLI_VERSION = (true ? "3.5.1-dev.4a593b3" : void 0) ?? process.env.npm_package_version ?? "unknown";
1162
1162
  function getCliVersion() {
1163
1163
  return CLI_VERSION;
1164
1164
  }
@@ -6399,6 +6399,7 @@ var DEFAULT_PAUSED_POLL_INTERVAL_MS = 2e3;
6399
6399
  var DEFAULT_PAUSED_MAX_WAIT_MS = 10 * 60 * 1e3;
6400
6400
  var DEFAULT_STUCK_QUEUED_MS = 6e4;
6401
6401
  var HEARTBEAT_MS = 6e4;
6402
+ var ALIVE_WITHHELD_THRESHOLD_MS = 3 * HEARTBEAT_MS;
6402
6403
  var ABSOLUTE_MAX_PROCESSING_MS = 6 * 60 * 60 * 1e3;
6403
6404
  var B2_ABANDONMENT_MIN_PINNED_MS = 3 * 6e4;
6404
6405
  var AMBIGUOUS_FINISH_MAX_PINNED_MS = 3 * 6e4;
@@ -7603,7 +7604,7 @@ var ChannelDriver = class _ChannelDriver {
7603
7604
  return "dispatch";
7604
7605
  }
7605
7606
  }
7606
- return this.reattachRedrive(conv, sessionId, message, ocId);
7607
+ return this.reattachRedrive(conv, sessionId, message, ocId, state);
7607
7608
  }
7608
7609
  if (ongoing === false) {
7609
7610
  if (state === "running" && isAmbiguousFinishPinnedRunning(messages, ocId ?? "")) {
@@ -7620,11 +7621,12 @@ var ChannelDriver = class _ChannelDriver {
7620
7621
  return "dispatch";
7621
7622
  }
7622
7623
  /**
7623
- * The `reattached` outcome (Task 3.3): the prior turn is STILL ONGOING per
7624
- * opencode's own status map — undo the false reclaim instead of starting a
7625
- * second turn.
7624
+ * The `reattached` outcome (Task 3.3): opencode's own status map says the session
7625
+ * is ongoing, so undo the false reclaim instead of starting a second turn. `state`
7626
+ * carries the correlated message's observed state so the outcome does not claim
7627
+ * that a queued message's own turn was already running.
7626
7628
  */
7627
- async reattachRedrive(conv, sessionId, message, ocId) {
7629
+ async reattachRedrive(conv, sessionId, message, ocId, state) {
7628
7630
  let anchorMs;
7629
7631
  const parsed = message.processing_started_at ? Date.parse(message.processing_started_at) : NaN;
7630
7632
  if (!Number.isNaN(parsed)) {
@@ -7633,7 +7635,7 @@ var ChannelDriver = class _ChannelDriver {
7633
7635
  anchorMs = this.now();
7634
7636
  this.log({
7635
7637
  level: "warn",
7636
- message: `Re-drive: message ${message.id.slice(0, 8)} has null/unparseable processing_started_at (${String(message.processing_started_at)}) \u2014 this is expected when OpenCode accepted the turn but never started it; anchoring the watcher's absolute-age ceiling to now`,
7638
+ message: `Re-drive: message ${message.id.slice(0, 8)} has no usable processing_started_at (${String(message.processing_started_at)}) \u2014 anchoring the watcher's absolute-age ceiling to now`,
7637
7639
  conversation_id: conv.id,
7638
7640
  message_id: message.id
7639
7641
  });
@@ -7662,17 +7664,19 @@ var ChannelDriver = class _ChannelDriver {
7662
7664
  return bound === "abandoned" ? "abandoned" : "unresolved";
7663
7665
  }
7664
7666
  this.clearRedriveUnresolved(message.id);
7665
- this.registerReadopted(conv, sessionId, message, ocId ?? "", anchorMs);
7667
+ this.registerReadopted(conv, sessionId, message, ocId ?? "", anchorMs, this.now());
7666
7668
  this.dispatched.add(message.id);
7667
7669
  this.readopted.add(message.id);
7668
- this.ensureWatcherRunning(sessionId);
7670
+ const watcherState = this.ensureWatcherRunning(sessionId);
7669
7671
  const watchedForMs = this.now() - anchorMs;
7670
7672
  void this.postSignal(conv.id, message.id, "redrive_reattached", {
7671
- watched_for_ms: watchedForMs
7673
+ watched_for_ms: watchedForMs,
7674
+ run_state: state,
7675
+ watcher_state: watcherState
7672
7676
  });
7673
7677
  this.log({
7674
7678
  level: "warn",
7675
- message: `Re-drive: message ${message.id.slice(0, 8)} (session ${sessionId.slice(0, 8)}) was wrongly reclaimed to pending while its turn was still running (watched ${watchedForMs}ms) \u2014 restored to processing instead of re-dispatching`,
7679
+ message: state === "running" ? `Re-drive: message ${message.id.slice(0, 8)} (session ${sessionId.slice(0, 8)}) was wrongly reclaimed to pending while its turn was still running (watched ${watchedForMs}ms) \u2014 restored to processing instead of re-dispatching` : `Re-drive: message ${message.id.slice(0, 8)} (session ${sessionId.slice(0, 8)}) was reclaimed to pending while its own turn had not started and the session was busy (watched ${watchedForMs}ms) \u2014 restored to processing instead of re-dispatching to avoid a duplicate`,
7676
7680
  conversation_id: conv.id,
7677
7681
  message_id: message.id
7678
7682
  });
@@ -8279,6 +8283,7 @@ var ChannelDriver = class _ChannelDriver {
8279
8283
  stuckReported: false,
8280
8284
  lastAliveAt: 0,
8281
8285
  aliveInFlight: false,
8286
+ aliveWithheldSignalled: false,
8282
8287
  titleSynced: false,
8283
8288
  titleSyncInFlight: false,
8284
8289
  awaitingHumanLatched: false,
@@ -8338,9 +8343,12 @@ var ChannelDriver = class _ChannelDriver {
8338
8343
  /**
8339
8344
  * Register a RE-ADOPTED `processing` message with its session watcher
8340
8345
  * (ADR-0046, WI-4). Mirrors `registerInFlight` but anchors the give-up
8341
- * `deadline` to the row's SERVER-SIDE `processed_at` (Invariant 1), NEVER to
8342
- * `now`, so the paused/queued/unreachable cases settle on the same wall-clock a
8343
- * fresh dispatch would (10 min after `processed_at`, not 10 min from now).
8346
+ * `deadline` to the attempt's SERVER-SIDE `processed_at` (Invariant 1), while
8347
+ * `processingAnchorMs` keeps the absolute-age ceiling tied to the original turn.
8348
+ * The deadline tracks THIS attempt; the ceiling tracks THE TURN. For a fresh
8349
+ * dispatch both anchors are `now`; a restart re-adopt passes the server's
8350
+ * `processed_at` for both, while a re-drive passes the newly stamped attempt time
8351
+ * only for the deadline.
8344
8352
  *
8345
8353
  * This re-attaches into the SAME watcher, so the ADR-0047 progressing-vs-paused
8346
8354
  * give-up (`serviceInFlightMessage`) applies unchanged: a re-adopted turn
@@ -8361,7 +8369,7 @@ var ChannelDriver = class _ChannelDriver {
8361
8369
  * server already flipped to `processing`; the running/done transitions still
8362
8370
  * fire from the watcher's normal branches.
8363
8371
  */
8364
- registerReadopted(conv, sessionId, message, opencodeMessageId, processedAtMs) {
8372
+ registerReadopted(conv, sessionId, message, opencodeMessageId, processedAtMs, watchDeadlineAnchorMs) {
8365
8373
  let watcher = this.watchers.get(sessionId);
8366
8374
  if (!watcher) {
8367
8375
  watcher = this.newSessionWatcher(conv);
@@ -8372,11 +8380,11 @@ var ChannelDriver = class _ChannelDriver {
8372
8380
  opencodeMessageId,
8373
8381
  message,
8374
8382
  dispatchedAt: this.now(),
8375
- // Anchor the absolute-age ceiling to the SERVER-SIDE `processed_at` (the same
8376
- // value seeding `deadline`), NOT `dispatchedAt` — so a re-adopted zombie's age
8377
- // reflects the real turn duration and the ceiling fires on the ORIGINAL turn.
8383
+ // Anchor the absolute-age ceiling to the SERVER-SIDE `processed_at`, NOT
8384
+ // `dispatchedAt` — so a re-adopted zombie's age reflects the real turn duration
8385
+ // and the ceiling fires on the ORIGINAL turn.
8378
8386
  processingAnchorMs: processedAtMs,
8379
- deadline: processedAtMs + this.pausedMaxWaitMs,
8387
+ deadline: watchDeadlineAnchorMs + this.pausedMaxWaitMs,
8380
8388
  // The server row is ALREADY `processing`; do not re-fire markProcessing.
8381
8389
  started: true,
8382
8390
  done: false,
@@ -8393,6 +8401,7 @@ var ChannelDriver = class _ChannelDriver {
8393
8401
  // with no extra `re_adopted` signal needed (folds old WI-6).
8394
8402
  lastAliveAt: 0,
8395
8403
  aliveInFlight: false,
8404
+ aliveWithheldSignalled: false,
8396
8405
  titleSynced: false,
8397
8406
  titleSyncInFlight: false,
8398
8407
  awaitingHumanLatched: false,
@@ -8509,6 +8518,8 @@ var ChannelDriver = class _ChannelDriver {
8509
8518
  * tracked on the watcher and cleared when it settles; it never rejects (fully
8510
8519
  * guarded), so a failed poll/callback can never crash the run loop — the cron
8511
8520
  * stays as the safety net.
8521
+ * Returns the branch taken so re-attach telemetry can show whether it left a
8522
+ * ticking loop behind.
8512
8523
  *
8513
8524
  * The generation started here (#1618) is captured in the `.finally` closure
8514
8525
  * so a RETIRED loop settling late — after `reconcileWatchers` has already
@@ -8522,7 +8533,7 @@ var ChannelDriver = class _ChannelDriver {
8522
8533
  level: "debug",
8523
8534
  message: `No watcher entry exists for session ${sessionId} \u2014 nothing to do`
8524
8535
  });
8525
- return;
8536
+ return "no_watcher";
8526
8537
  }
8527
8538
  this.ensureSessionErrorStream();
8528
8539
  if (watcher.loop) {
@@ -8531,7 +8542,7 @@ var ChannelDriver = class _ChannelDriver {
8531
8542
  message: `Watcher loop already running for session ${sessionId}; no-op with ${watcher.inFlight.size} message(s) in flight`,
8532
8543
  conversation_id: watcher.conv.id
8533
8544
  });
8534
- return;
8545
+ return "already_running";
8535
8546
  }
8536
8547
  if (watcher.inFlight.size === 0) {
8537
8548
  this.log({
@@ -8540,7 +8551,7 @@ var ChannelDriver = class _ChannelDriver {
8540
8551
  conversation_id: watcher.conv.id
8541
8552
  });
8542
8553
  this.watchers.delete(sessionId);
8543
- return;
8554
+ return "no_in_flight";
8544
8555
  }
8545
8556
  const generation = watcher.generation;
8546
8557
  this.log({
@@ -8556,6 +8567,7 @@ var ChannelDriver = class _ChannelDriver {
8556
8567
  }
8557
8568
  });
8558
8569
  watcher.loop = loop;
8570
+ return "started";
8559
8571
  }
8560
8572
  ensureSessionErrorStream() {
8561
8573
  if (this.sessionErrorStream || this.stopped) return;
@@ -9081,7 +9093,8 @@ var ChannelDriver = class _ChannelDriver {
9081
9093
  this.removeInFlight(watcher, inFlight.evidentMessageId);
9082
9094
  return;
9083
9095
  }
9084
- if (activelyRunning && !inFlight.awaitingHumanLatched && !inFlight.aliveInFlight && this.now() - inFlight.lastAliveAt >= HEARTBEAT_MS) {
9096
+ const heartbeatDue = activelyRunning && !inFlight.awaitingHumanLatched && !inFlight.aliveInFlight && this.now() - inFlight.lastAliveAt >= HEARTBEAT_MS;
9097
+ if (heartbeatDue) {
9085
9098
  inFlight.aliveInFlight = true;
9086
9099
  this.log({
9087
9100
  level: "debug",
@@ -9091,7 +9104,10 @@ var ChannelDriver = class _ChannelDriver {
9091
9104
  });
9092
9105
  void this.postSignal(conv.id, inFlight.evidentMessageId, "alive").then((ok) => {
9093
9106
  inFlight.aliveInFlight = false;
9094
- if (ok) inFlight.lastAliveAt = this.now();
9107
+ if (ok) {
9108
+ inFlight.lastAliveAt = this.now();
9109
+ inFlight.aliveWithheldSignalled = false;
9110
+ }
9095
9111
  this.log({
9096
9112
  level: "debug",
9097
9113
  message: `Heartbeat POST for message ${inFlight.evidentMessageId} in session ${sessionId} completed with ok=${ok}`,
@@ -9143,6 +9159,16 @@ var ChannelDriver = class _ChannelDriver {
9143
9159
  inFlight.pausedOnPermission = false;
9144
9160
  inFlight.pausedClearConfirmed = false;
9145
9161
  }
9162
+ const watchedForMs = this.now() - Math.max(inFlight.lastAliveAt, inFlight.dispatchedAt);
9163
+ if (!heartbeatDue && !inFlight.aliveWithheldSignalled && watchedForMs >= ALIVE_WITHHELD_THRESHOLD_MS) {
9164
+ inFlight.aliveWithheldSignalled = true;
9165
+ const reason = inFlight.awaitingHumanLatched ? "awaiting_human" : inFlight.aliveInFlight ? "alive_in_flight" : "not_running";
9166
+ void this.postSignal(conv.id, inFlight.evidentMessageId, "alive_withheld", {
9167
+ reason,
9168
+ run_state: state,
9169
+ watched_for_ms: watchedForMs
9170
+ });
9171
+ }
9146
9172
  const siblingPaused = (sib) => openQuestions.has(sib.evidentMessageId) || openPermissions.has(sib.evidentMessageId) || sib.awaitingHumanLatched || sib.pausedOnQuestion || sib.pausedOnPermission;
9147
9173
  const hasActivelyRunningSibling = [...watcher.inFlight.values()].some(
9148
9174
  (sib) => sib.evidentMessageId !== inFlight.evidentMessageId && messageRunState(messages, sib.opencodeMessageId) === "running" && !siblingPaused(sib)
@@ -9153,15 +9179,15 @@ var ChannelDriver = class _ChannelDriver {
9153
9179
  const attempts = this.recordNeverStartedAttempt(id, sessionId);
9154
9180
  if (attempts >= MAX_NEVER_STARTED_ATTEMPTS) {
9155
9181
  inFlight.neverStartedResolved = true;
9156
- const watchedForMs = this.now() - inFlight.dispatchedAt;
9182
+ const watchedForMs2 = this.now() - inFlight.dispatchedAt;
9157
9183
  this.log({
9158
9184
  level: "error",
9159
- message: `Message ${id.slice(0, 8)} never started after ${attempts} attempts (${watchedForMs}ms watched) \u2014 reporting the run failed; the accepted OpenCode copy was not cancelled`,
9185
+ message: `Message ${id.slice(0, 8)} never started after ${attempts} attempts (${watchedForMs2}ms watched) \u2014 reporting the run failed; the accepted OpenCode copy was not cancelled`,
9160
9186
  conversation_id: conv.id,
9161
9187
  message_id: id
9162
9188
  });
9163
9189
  void this.postSignal(conv.id, id, "never_started_failed", {
9164
- watched_for_ms: watchedForMs,
9190
+ watched_for_ms: watchedForMs2,
9165
9191
  attempts
9166
9192
  });
9167
9193
  await this.settleMessageNeverStarted(sessionId, watcher, inFlight, attempts);
@@ -9175,7 +9201,8 @@ var ChannelDriver = class _ChannelDriver {
9175
9201
  message_id: inFlight.evidentMessageId
9176
9202
  });
9177
9203
  void this.postSignal(conv.id, inFlight.evidentMessageId, "gave_up", {
9178
- watched_for_ms: this.now() - inFlight.dispatchedAt
9204
+ watched_for_ms: this.now() - inFlight.dispatchedAt,
9205
+ run_state: state
9179
9206
  });
9180
9207
  this.recordReleasedOpencodeId(
9181
9208
  inFlight.evidentMessageId,
@@ -9632,7 +9659,8 @@ var ChannelDriver = class _ChannelDriver {
9632
9659
  if ((state === "running" || state === "queued") && ocId) {
9633
9660
  const conv = this.convForRow(sessionId, row);
9634
9661
  const message = this.queuedMessageForRow(row);
9635
- this.registerReadopted(conv, sessionId, message, ocId, this.processedAtMs(row));
9662
+ const processedAtMs = this.processedAtMs(row);
9663
+ this.registerReadopted(conv, sessionId, message, ocId, processedAtMs, processedAtMs);
9636
9664
  this.dispatched.add(row.id);
9637
9665
  this.readopted.add(row.id);
9638
9666
  this.ensureWatcherRunning(sessionId);
@@ -9927,7 +9955,15 @@ var ChannelDriver = class _ChannelDriver {
9927
9955
  return;
9928
9956
  }
9929
9957
  this.unconfirmedDispatchFailures.delete(row.id);
9930
- this.registerReadopted(readoptConv, sessionId, readoptMessage, ocId, this.processedAtMs(row));
9958
+ const processedAtMs = this.processedAtMs(row);
9959
+ this.registerReadopted(
9960
+ readoptConv,
9961
+ sessionId,
9962
+ readoptMessage,
9963
+ ocId,
9964
+ processedAtMs,
9965
+ processedAtMs
9966
+ );
9931
9967
  this.dispatched.add(row.id);
9932
9968
  this.readopted.add(row.id);
9933
9969
  this.awaitingReadopt.delete(row.id);