@adhdev/daemon-standalone 0.9.82-rc.532 → 0.9.82-rc.534

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
@@ -30208,10 +30208,10 @@ var require_dist3 = __commonJS({
30208
30208
  }
30209
30209
  function getDaemonBuildInfo() {
30210
30210
  if (cached2) return cached2;
30211
- const commit = readInjected(true ? "2dcb1909a41f2358986e408601c8a1284c6f3685" : void 0) ?? "unknown";
30212
- const commitShort = readInjected(true ? "2dcb1909" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
30213
- const version2 = readInjected(true ? "0.9.82-rc.532" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
30214
- const builtAt = readInjected(true ? "2026-07-15T07:15:24.212Z" : void 0);
30211
+ const commit = readInjected(true ? "7b6411ebc267c71484a3f7d16c392b5bd3e71bd8" : void 0) ?? "unknown";
30212
+ const commitShort = readInjected(true ? "7b6411eb" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
30213
+ const version2 = readInjected(true ? "0.9.82-rc.534" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
30214
+ const builtAt = readInjected(true ? "2026-07-15T08:30:49.700Z" : void 0);
30215
30215
  cached2 = builtAt ? { commit, commitShort, version: version2, builtAt } : { commit, commitShort, version: version2 };
30216
30216
  return cached2;
30217
30217
  }
@@ -35486,12 +35486,16 @@ Valid status values: \`completed\` | \`failed\` | \`blocked\` | \`partial\`.`;
35486
35486
  return [];
35487
35487
  }
35488
35488
  }
35489
+ function consumeSessionDelivery(meshId, sessionId, status, taskId) {
35490
+ try {
35491
+ return MeshRuntimeStore.getInstance().consumeSessionDelivery(meshId, sessionId, status, taskId);
35492
+ } catch {
35493
+ return 0;
35494
+ }
35495
+ }
35489
35496
  function markSessionDeliveriesTerminal(meshId, sessionId, terminalStatus) {
35490
35497
  try {
35491
- const active = MeshRuntimeStore.getInstance().getActiveSessionDeliveries(meshId, sessionId);
35492
- for (const delivery of active) {
35493
- MeshRuntimeStore.getInstance().updateSessionDeliveryStatus(delivery.id, terminalStatus);
35494
- }
35498
+ MeshRuntimeStore.getInstance().markOpenSessionDeliveriesTerminal(meshId, sessionId, terminalStatus);
35495
35499
  } catch {
35496
35500
  }
35497
35501
  }
@@ -37556,6 +37560,25 @@ Valid status values: \`completed\` | \`failed\` | \`blocked\` | \`partial\`.`;
37556
37560
  });
37557
37561
  this.maybeCheckpointWal();
37558
37562
  }
37563
+ // DELIVERED-NOT-CONSUMED-REDRIVE monotonic FSM: the forward-progress lifecycle of a
37564
+ // delivery is a strictly increasing rank — a status may only advance, never regress.
37565
+ // The redrive bug was a NON-monotonic FSM: the transport-confirm callback
37566
+ // (mesh-queue-assignment :384) writes 'delivered' unconditionally by PK, so when the
37567
+ // worker's agent:generating_started raced AHEAD of the confirm and already flipped the
37568
+ // row 'delivering'→'acked', the late confirm CLOBBERED 'acked' back to 'delivered'.
37569
+ // taskDeliveryConsumed() (which keys on 'acked'/'completed') then read false forever,
37570
+ // and the short-grace re-drive re-opened an already-consumed task. Enforcing the rank
37571
+ // ordering here makes the two event orders converge on the same monotone terminal state
37572
+ // regardless of arrival order, so a late confirm can never demote a consumed delivery.
37573
+ // 'failed'/'expired'/'cancelled' are absorbing OUTCOMES, not progress ranks — they are
37574
+ // always allowed (a genuine dispatch failure must be recordable even from 'acked').
37575
+ static DELIVERY_PROGRESS_RANK = {
37576
+ queued: 0,
37577
+ delivering: 1,
37578
+ delivered: 2,
37579
+ acked: 3,
37580
+ completed: 4
37581
+ };
37559
37582
  updateSessionDeliveryStatus(id, status, opts) {
37560
37583
  const now = (/* @__PURE__ */ new Date()).toISOString();
37561
37584
  if (opts?.incrementAttempt) {
@@ -37564,13 +37587,75 @@ Valid status values: \`completed\` | \`failed\` | \`blocked\` | \`partial\`.`;
37564
37587
  SET status = @status, last_error = @lastError, attempt_count = attempt_count + 1, updated_at = @updatedAt
37565
37588
  WHERE id = @id
37566
37589
  `).run({ id, status, lastError: opts?.lastError ?? null, updatedAt: now });
37567
- } else {
37590
+ return;
37591
+ }
37592
+ const targetRank = _MeshRuntimeStore.DELIVERY_PROGRESS_RANK[status];
37593
+ if (targetRank === void 0) {
37568
37594
  this.db.prepare(`
37569
37595
  UPDATE mesh_session_delivery
37570
37596
  SET status = @status, last_error = @lastError, updated_at = @updatedAt
37571
37597
  WHERE id = @id
37572
37598
  `).run({ id, status, lastError: opts?.lastError ?? null, updatedAt: now });
37599
+ return;
37573
37600
  }
37601
+ this.db.prepare(`
37602
+ UPDATE mesh_session_delivery
37603
+ SET status = @status, last_error = @lastError, updated_at = @updatedAt
37604
+ WHERE id = @id AND (@targetRank >= CASE status
37605
+ WHEN 'queued' THEN 0 WHEN 'delivering' THEN 1 WHEN 'delivered' THEN 2
37606
+ WHEN 'acked' THEN 3 WHEN 'completed' THEN 4 ELSE 99 END)
37607
+ `).run({ id, status, lastError: opts?.lastError ?? null, updatedAt: now, targetRank });
37608
+ }
37609
+ /**
37610
+ * DELIVERED-NOT-CONSUMED-REDRIVE consume path. Advance a task's delivery record(s) to a
37611
+ * CONSUMED status ('acked' or 'completed'), matching on mesh + session (+ taskId when the
37612
+ * event names one) and INCLUDING rows already in 'delivered'/'acked'/'delivering'.
37613
+ *
37614
+ * The ack/terminal callers previously routed through getActiveSessionDeliveries(), whose SQL
37615
+ * EXCLUDES 'delivered' — so in the normal event order (transport confirm flips 'delivered'
37616
+ * BEFORE the worker's generating_started fires) the ack matched zero rows and the delivery
37617
+ * was stranded 'delivered', never 'acked'. This finds the row by (mesh, session[, task])
37618
+ * directly and relies on updateSessionDeliveryStatus's monotonic guard to only advance it.
37619
+ * Returns the number of rows advanced.
37620
+ */
37621
+ consumeSessionDelivery(meshId, sessionId, status, taskId) {
37622
+ const rows = this.db.prepare(
37623
+ taskId ? `SELECT id, session_id FROM mesh_session_delivery
37624
+ WHERE mesh_id = ? AND task_id = ?
37625
+ AND status IN ('queued','delivering','delivered','acked')` : `SELECT id, session_id FROM mesh_session_delivery
37626
+ WHERE mesh_id = ? AND session_id = ?
37627
+ AND status IN ('queued','delivering','delivered','acked')`
37628
+ ).all(meshId, taskId ?? sessionId);
37629
+ let advanced = 0;
37630
+ for (const r of rows) {
37631
+ if (!sessionIdsEquivalent(r.session_id ?? void 0, sessionId)) continue;
37632
+ this.updateSessionDeliveryStatus(r.id, status);
37633
+ advanced++;
37634
+ }
37635
+ return advanced;
37636
+ }
37637
+ /**
37638
+ * DELIVERED-NOT-CONSUMED-REDRIVE terminal path. Mark every OPEN delivery for a session
37639
+ * (queued/delivering/delivered/acked) terminal on task completion/failure. The prior
37640
+ * markSessionDeliveriesTerminal() routed through getActiveSessionDeliveries(), whose SQL
37641
+ * EXCLUDES 'delivered'/'completed' — so a 'delivered' row (the common case, since the
37642
+ * transport confirm flips it before the completion event) was never marked terminal and
37643
+ * stayed 'delivered', keeping taskDeliveryConsumed() false and feeding the false re-drive.
37644
+ * We match rows in OPEN states directly here. 'completed' advances monotonically (it is the
37645
+ * top progress rank); 'failed' is an absorbing outcome written unconditionally.
37646
+ */
37647
+ markOpenSessionDeliveriesTerminal(meshId, sessionId, terminalStatus) {
37648
+ const rows = this.db.prepare(
37649
+ `SELECT id, session_id FROM mesh_session_delivery
37650
+ WHERE mesh_id = ? AND status IN ('queued','delivering','delivered','acked')`
37651
+ ).all(meshId);
37652
+ let marked = 0;
37653
+ for (const r of rows) {
37654
+ if (!sessionIdsEquivalent(r.session_id ?? void 0, sessionId)) continue;
37655
+ this.updateSessionDeliveryStatus(r.id, terminalStatus);
37656
+ marked++;
37657
+ }
37658
+ return marked;
37574
37659
  }
37575
37660
  getActiveSessionDeliveries(meshId, sessionId) {
37576
37661
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -50570,17 +50655,7 @@ ${cleanBody}`;
50570
50655
  updateDirectDispatchStatus(args.meshId, sessionId, "acked", soleTaskId);
50571
50656
  }
50572
50657
  }
50573
- const activeDeliveries = (() => {
50574
- try {
50575
- return MeshRuntimeStore.getInstance().getActiveSessionDeliveries(args.meshId, sessionId);
50576
- } catch {
50577
- return [];
50578
- }
50579
- })();
50580
- const deliveriesToAck = startedTaskId ? activeDeliveries.filter((d) => d.taskId === startedTaskId) : activeDeliveries;
50581
- for (const d of deliveriesToAck) {
50582
- updateSessionDeliveryStatus(d.id, "acked");
50583
- }
50658
+ consumeSessionDelivery(args.meshId, sessionId, "acked", startedTaskId);
50584
50659
  }
50585
50660
  } else if (args.event === "agent:stopped") {
50586
50661
  const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
@@ -52015,6 +52090,9 @@ ${cleanBody}`;
52015
52090
  for (const key2 of [...deliveredNoTurnUnknownStreak.keys()]) {
52016
52091
  if (key2.startsWith(meshKeyPrefix) && !assignedKeys.has(key2)) deliveredNoTurnUnknownStreak.delete(key2);
52017
52092
  }
52093
+ for (const key2 of [...deliveredUnconsumedUnknownStreak.keys()]) {
52094
+ if (key2.startsWith(meshKeyPrefix) && !assignedKeys.has(key2)) deliveredUnconsumedUnknownStreak.delete(key2);
52095
+ }
52018
52096
  for (const row of assigned) {
52019
52097
  const dispatchedAtMs = Date.parse(row.dispatchTimestamp ?? "");
52020
52098
  if (!Number.isFinite(dispatchedAtMs)) continue;
@@ -52026,13 +52104,33 @@ ${cleanBody}`;
52026
52104
  updateTaskStatus(meshId, row.id, status);
52027
52105
  continue;
52028
52106
  }
52107
+ const shortStreakKey = `${meshId}::${row.id}`;
52029
52108
  const verdict = row.assignedSessionId ? resolveSessionBusyVerdict(components, row.assignedSessionId) : "IDLE_CONFIRMED";
52030
- if (verdict !== "GENERATING") {
52109
+ if (verdict === "GENERATING") {
52110
+ deliveredUnconsumedUnknownStreak.delete(shortStreakKey);
52111
+ } else {
52112
+ if (verdict === "IDLE_CONFIRMED") {
52113
+ deliveredUnconsumedUnknownStreak.delete(shortStreakKey);
52114
+ } else {
52115
+ const streak = (deliveredUnconsumedUnknownStreak.get(shortStreakKey) ?? 0) + 1;
52116
+ deliveredUnconsumedUnknownStreak.set(shortStreakKey, streak);
52117
+ if (streak < RECLAIM_UNKNOWN_GRACE_TICKS) {
52118
+ traceMeshEventDrop("short_redrive_deferred_unknown_verdict", {
52119
+ taskId: row.id,
52120
+ sessionId: row.assignedSessionId,
52121
+ nodeId: row.assignedNodeId,
52122
+ meshId,
52123
+ event: "agent:generating_started"
52124
+ }, `unknown ${streak}/${RECLAIM_UNKNOWN_GRACE_TICKS}`);
52125
+ continue;
52126
+ }
52127
+ }
52031
52128
  const redriven = reclaimStrandedAssignedTask(meshId, row.id, {
52032
52129
  reason: "delivered_not_consumed_redrive",
52033
52130
  ageMs
52034
52131
  });
52035
52132
  if (redriven) {
52133
+ deliveredUnconsumedUnknownStreak.delete(shortStreakKey);
52036
52134
  LOG2.warn("MeshReconcile", `Re-drove delivered-but-unconsumed task ${row.id} on mesh ${meshId} (node=${row.assignedNodeId ?? "?"} session=${row.assignedSessionId ?? "?"}, delivered but no generating_started in ${Math.round(ageMs / 1e3)}s, verdict ${verdict} \u2192 ${redriven.status})`);
52037
52135
  traceMeshEventDrop("assigned_delivered_not_consumed_redrive", {
52038
52136
  taskId: row.id,
@@ -52594,6 +52692,7 @@ ${cleanBody}`;
52594
52692
  var ASSIGNED_DELIVERED_UNCONSUMED_REDRIVE_MS;
52595
52693
  var RECLAIM_UNKNOWN_GRACE_TICKS;
52596
52694
  var deliveredNoTurnUnknownStreak;
52695
+ var deliveredUnconsumedUnknownStreak;
52597
52696
  var ZOMBIE_ASSIGNED_MIN_AGE_MS;
52598
52697
  var STRICT_SESSION_MATCH_TTL_MS;
52599
52698
  var unresolvedForwardRejectionCounts;
@@ -52634,6 +52733,7 @@ ${cleanBody}`;
52634
52733
  ASSIGNED_DELIVERED_UNCONSUMED_REDRIVE_MS = 25e3;
52635
52734
  RECLAIM_UNKNOWN_GRACE_TICKS = 3;
52636
52735
  deliveredNoTurnUnknownStreak = /* @__PURE__ */ new Map();
52736
+ deliveredUnconsumedUnknownStreak = /* @__PURE__ */ new Map();
52637
52737
  ZOMBIE_ASSIGNED_MIN_AGE_MS = 30 * 60 * 1e3;
52638
52738
  STRICT_SESSION_MATCH_TTL_MS = 6e4;
52639
52739
  unresolvedForwardRejectionCounts = /* @__PURE__ */ new Map();
@@ -56838,8 +56938,8 @@ ${lastSnapshot}`;
56838
56938
  const stableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
56839
56939
  if (stableMs < 2e3) return;
56840
56940
  const startupModal = this.runParseApproval(this.recentOutputBuffer);
56841
- const startupStatus = this.runDetectStatus(screenText || this.recentOutputBuffer);
56842
- if (!startupModal && startupStatus !== "idle") {
56941
+ const startupIdle = this.detectIdleHonoringOnNoMatch(screenText || this.recentOutputBuffer);
56942
+ if (!startupModal && !startupIdle) {
56843
56943
  this.scheduleStartupSettleCheck();
56844
56944
  return;
56845
56945
  }
@@ -56975,6 +57075,38 @@ ${lastSnapshot}`;
56975
57075
  tailScreen: buildCliScreenSnapshot(tail)
56976
57076
  });
56977
57077
  }
57078
+ /**
57079
+ * WRITE-READINESS ONNOMATCH (opencode): resolve whether the session is
57080
+ * genuinely idle *for the purpose of opening the PTY write gate*, honoring
57081
+ * the manifest's `dispatchOrder.onNoMatch` policy.
57082
+ *
57083
+ * The split-brain this closes: the engine's settled evaluation runs
57084
+ * detectStatus through parseSession, whose builder collapses a null verdict
57085
+ * to `'idle'` (buildParseSessionFromTui: `detectStatus(input) ?? 'idle'`),
57086
+ * so the dashboard status flips to idle via `script_detect`. But the
57087
+ * write-readiness gates (resolveStartupState / sendMessage recovery) call
57088
+ * `runDetectStatus` *directly* and require the literal `=== 'idle'` return —
57089
+ * they never apply the `onNoMatch` policy. opencode's only idle cue is the
57090
+ * `Ask anything` composer placeholder in the last-8-lines scope with
57091
+ * `onNoMatch: preserve-last`; when that placeholder is momentarily out of
57092
+ * frame the raw detector returns null, the gate stays shut, `this.ready`
57093
+ * never flips, and the first queued prompt sits in `not_ready_pending_prompt`
57094
+ * forever (no turn ever starts, so no turn-completion drain fires).
57095
+ *
57096
+ * The fix keeps the raw detector as the primary signal (unchanged behavior
57097
+ * for providers whose detector returns a literal idle) and only falls back
57098
+ * when BOTH (a) the manifest policy is idle-preserving (`preserve-last` or
57099
+ * `idle`) AND (b) the engine has *durably* settled to idle (no in-flight
57100
+ * turn, no modal, no parse error). That guard makes the fallback safe: it
57101
+ * cannot open the gate mid-turn or while a modal is up.
57102
+ */
57103
+ detectIdleHonoringOnNoMatch(text) {
57104
+ if (this.runDetectStatus(text) === "idle") return true;
57105
+ const dispatchOrder = this.provider.tui?.dispatchOrder;
57106
+ const onNoMatch = dispatchOrder?.onNoMatch;
57107
+ if (onNoMatch !== "preserve-last" && onNoMatch !== "idle") return false;
57108
+ return this.engine.currentStatus === "idle" && !this.engine.isWaitingForResponse && !this.engine.currentTurnScope && !this.engine.activeModal && !this.parseErrorMessage;
57109
+ }
56978
57110
  runParseApproval(tail) {
56979
57111
  const screenText = this.terminalScreen.getText();
56980
57112
  const buffer = screenText || this.accumulatedBuffer;
@@ -57507,7 +57639,7 @@ ${lastSnapshot}`;
57507
57639
  }
57508
57640
  if (!this.ready) {
57509
57641
  this.resolveStartupState("send_precheck");
57510
- if (this.runDetectStatus(this.recentOutputBuffer) === "idle") {
57642
+ if (!this.ready && this.detectIdleHonoringOnNoMatch(this.recentOutputBuffer)) {
57511
57643
  this.ready = true;
57512
57644
  this.startupParseGate = false;
57513
57645
  this.engine.setStatus("idle", "send_message_idle_prompt_recovery");