@adhdev/daemon-core 0.9.82-rc.362 → 0.9.82-rc.364

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.
@@ -83,7 +83,7 @@ export declare class CliStateEngine {
83
83
  * seq) is always a real, distinct approval and must be written.
84
84
  */
85
85
  approvalEntrySeq: number;
86
- private lastResolvedEntrySeq;
86
+ lastResolvedEntrySeq: number;
87
87
  /**
88
88
  * When the engine previously held a modal but the latest parse failed
89
89
  * to extract one, we record the timestamp here and only drop the modal
@@ -36,6 +36,16 @@ export interface CliSessionStatus {
36
36
  * seq is the only reliable discriminator.
37
37
  */
38
38
  approvalEntrySeq?: number;
39
+ /**
40
+ * The approval entry seq that the engine's last resolveModal() handled. When
41
+ * `lastResolvedEntrySeq >= approvalEntrySeq` (and approvalEntrySeq > 0) it is positive
42
+ * evidence that the current/latest approval entry was resolved through ADHDev (auto-approve
43
+ * fire, dashboard / mesh_approve, or dev-cli-debug — all route through resolveModal). The
44
+ * completion finalization gate uses this to avoid confirming a waiting_approval→idle
45
+ * transition for which no resolution actually occurred (a false idle: the spec's text-based
46
+ * approval→idle rule tripped while the modal is still on screen).
47
+ */
48
+ lastResolvedEntrySeq?: number;
39
49
  activeInteractivePrompt?: InteractivePrompt | null;
40
50
  pendingOutboundCount?: number;
41
51
  pendingOutboundMessages?: Array<{
package/dist/index.js CHANGED
@@ -316,10 +316,10 @@ function readInjected(value) {
316
316
  }
317
317
  function getDaemonBuildInfo() {
318
318
  if (cached) return cached;
319
- const commit = readInjected(true ? "4b7c1b0d781f263663369085c4dde301d3b5cd2e" : void 0) ?? "unknown";
320
- const commitShort = readInjected(true ? "4b7c1b0d" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
321
- const version = readInjected(true ? "0.9.82-rc.362" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
322
- const builtAt = readInjected(true ? "2026-06-23T12:54:22.426Z" : void 0);
319
+ const commit = readInjected(true ? "1428e9d027beefe860a77f34dea34d202db80f00" : void 0) ?? "unknown";
320
+ const commitShort = readInjected(true ? "1428e9d0" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
321
+ const version = readInjected(true ? "0.9.82-rc.364" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
322
+ const builtAt = readInjected(true ? "2026-06-24T00:05:49.524Z" : void 0);
323
323
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
324
324
  return cached;
325
325
  }
@@ -8594,6 +8594,13 @@ function readEventTimestampValue(value) {
8594
8594
  }
8595
8595
  return 0;
8596
8596
  }
8597
+ function isWeakCompletionMetadata(metadataEvent) {
8598
+ const evidenceLevel = readNonEmptyString2(metadataEvent.evidenceLevel);
8599
+ if (evidenceLevel === "insufficient" || evidenceLevel === "weak") return true;
8600
+ if (metadataEvent.reviewRecommended === true) return true;
8601
+ const diag = readRecord4(metadataEvent.completionDiagnostic);
8602
+ return diag?.finalAssistantPresent === false || diag?.blockReason === "missing_final_assistant";
8603
+ }
8597
8604
  function formatCompletionMetadata(event) {
8598
8605
  const completionDiagnostic = event.completionDiagnostic && typeof event.completionDiagnostic === "object" ? event.completionDiagnostic : null;
8599
8606
  const diagnosticReason = completionDiagnostic ? readNonEmptyString2(completionDiagnostic.blockReason) || "present" : "";
@@ -8616,17 +8623,19 @@ function buildMeshSystemMessage(args) {
8616
8623
  return `[System] ${args.nodeLabel} already has completion evidence${metadata}. The no-progress monitor reconciled the terminal handoff and marked the session complete; wait for the queued completion event/status refresh before doing any manual transcript check.`;
8617
8624
  }
8618
8625
  const reviewRecommended = args.metadataEvent.reviewRecommended === true;
8626
+ const weakCompletion = isWeakCompletionMetadata(args.metadataEvent);
8627
+ const verifyTextNote = " Completion evidence is weak \u2014 verify via mesh_read_chat or git status before declaring the task done; the worker may still be parked on an approval/modal.";
8619
8628
  const completionSummary = readMeshCompletionSummary(args.metadataEvent);
8620
8629
  if (completionSummary) {
8621
8630
  const truncationSuffix = "\n\u2026[truncated \u2014 call mesh_read_chat once for the full transcript]";
8622
8631
  const surfaced = completionSummary.length > MESH_COMPLETION_SURFACE_MAX_CHARS ? `${completionSummary.slice(0, MESH_COMPLETION_SURFACE_MAX_CHARS - truncationSuffix.length)}${truncationSuffix}` : completionSummary;
8623
- const verifyNote = reviewRecommended ? " Completion evidence is insufficient \u2014 verify via git status or provider_session_id before assuming the task is done." : "";
8632
+ const verifyNote = reviewRecommended ? " Completion evidence is insufficient \u2014 verify via git status or provider_session_id before assuming the task is done." : weakCompletion ? verifyTextNote : "";
8624
8633
  return `[System] ${args.nodeLabel} has completed its task and is now idle${metadata}. Its final summary is included below \u2014 read it directly and only call mesh_read_chat if you need the full transcript.${verifyNote}
8625
8634
 
8626
8635
  --- ${args.nodeLabel} final summary ---
8627
8636
  ${surfaced}`;
8628
8637
  }
8629
- const reviewNote = reviewRecommended ? " Completion evidence is insufficient \u2014 verify via git status or provider_session_id before assuming the task is done. Use mesh_read_chat once if needed, but do not poll repeatedly." : " Use mesh_read_chat once to review its final progress, but do not poll repeatedly.";
8638
+ const reviewNote = reviewRecommended ? " Completion evidence is insufficient \u2014 verify via git status or provider_session_id before assuming the task is done. Use mesh_read_chat once if needed, but do not poll repeatedly." : weakCompletion ? `${verifyTextNote} Use mesh_read_chat once if needed, but do not poll repeatedly.` : " Use mesh_read_chat once to review its final progress, but do not poll repeatedly.";
8630
8639
  return `[System] ${args.nodeLabel} has completed its task and is now idle${metadata}. This completion came from the agent status event path;${reviewNote}`;
8631
8640
  }
8632
8641
  if (args.event === "agent:waiting_approval") {
@@ -8761,7 +8770,7 @@ function hasPendingRefineTerminalEventDuplicate(event) {
8761
8770
  (pending) => pending.event === event.event && readRefineJobId2(pending) === jobId
8762
8771
  );
8763
8772
  }
8764
- function isWeakCompletionMetadata(metadata) {
8773
+ function isWeakCompletionMetadata2(metadata) {
8765
8774
  const evidenceLevel = readNonEmptyString2(metadata.evidenceLevel);
8766
8775
  if (evidenceLevel === "insufficient" || evidenceLevel === "weak") return true;
8767
8776
  if (metadata.reviewRecommended === true) return true;
@@ -8780,7 +8789,7 @@ function buildPendingEventFingerprint(event) {
8780
8789
  event.meshId,
8781
8790
  event.event,
8782
8791
  terminalTaskId,
8783
- isWeakCompletionMetadata(metadata) ? "weak" : "genuine"
8792
+ isWeakCompletionMetadata2(metadata) ? "weak" : "genuine"
8784
8793
  ].join("::");
8785
8794
  }
8786
8795
  }
@@ -12863,9 +12872,22 @@ function deliverTaskToSession(dispatchThunk, ctx) {
12863
12872
  }
12864
12873
  });
12865
12874
  }
12875
+ function normalizeMeshWorkspaceForCompare(dir) {
12876
+ if (typeof dir !== "string") return "";
12877
+ return dir.trim().replace(/[\\/]+/g, "/").replace(/\/+$/, "").toLowerCase();
12878
+ }
12866
12879
  function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType) {
12867
12880
  const mesh = getMeshWithCache(components, meshId);
12868
12881
  const node = mesh?.nodes.find((n) => readMeshNodeId(n) === nodeId);
12882
+ const localClaimAdapter = components.cliManager?.adapters?.get(sessionId);
12883
+ if (localClaimAdapter) {
12884
+ const sessionWorkspace = normalizeMeshWorkspaceForCompare(localClaimAdapter.workingDir);
12885
+ const nodeWorkspace = normalizeMeshWorkspaceForCompare(readNonEmptyString2(node?.workspace));
12886
+ if (sessionWorkspace && nodeWorkspace && sessionWorkspace !== nodeWorkspace) {
12887
+ LOG.info("MeshQueue", `WTCLAIM: refusing claim for node ${nodeId} (${sessionId}) \u2014 session workspace "${sessionWorkspace}" \u2260 node workspace "${nodeWorkspace}" (cross-workspace dispatch blocked)`);
12888
+ return false;
12889
+ }
12890
+ }
12869
12891
  const capabilityTags = buildMeshNodeCapabilityTags(node, providerType);
12870
12892
  const providerMaxParallel = resolveProviderMaxParallel(node?.policy, providerType);
12871
12893
  const task = claimNextTask(meshId, nodeId, sessionId, capabilityTags, {
@@ -17378,6 +17400,12 @@ var init_cli_state_engine = __esm({
17378
17400
  * seq) is always a real, distinct approval and must be written.
17379
17401
  */
17380
17402
  approvalEntrySeq = 0;
17403
+ // Records which approval entry the last resolveModal() handled. Set unconditionally on
17404
+ // every resolveModal (auto-approve fire, dashboard/mesh_approve, dev-cli-debug), so
17405
+ // `lastResolvedEntrySeq >= approvalEntrySeq` is positive, ADHDev-side proof that the
17406
+ // current/latest approval entry was actually resolved. Exposed via getStatus so the
17407
+ // completion finalization gate (FALSEIDLE-a) can require resolution evidence before
17408
+ // confirming a waiting_approval→idle transition. Public (read-only by convention).
17381
17409
  lastResolvedEntrySeq = -1;
17382
17410
  /**
17383
17411
  * When the engine previously held a modal but the latest parse failed
@@ -19095,6 +19123,7 @@ ${lastSnapshot}`;
19095
19123
  workingDir: this.workingDir,
19096
19124
  activeModal: effectiveModal,
19097
19125
  approvalEntrySeq: this.engine.approvalEntrySeq,
19126
+ lastResolvedEntrySeq: this.engine.lastResolvedEntrySeq,
19098
19127
  pendingOutboundCount: this.pendingOutboundQueue.length,
19099
19128
  pendingOutboundMessages: this.pendingOutboundQueue.map((message) => ({
19100
19129
  id: message.id,
@@ -35062,6 +35091,11 @@ var FsmDriver = class {
35062
35091
  lastWin32WriteAt = 0;
35063
35092
  /** Pending paced chunk-write timer for a large win32 body (see writeWin32Body). */
35064
35093
  win32WriteTimer = null;
35094
+ /** Timer driving the win32 verification-based modal-confirm CR resend loop (see
35095
+ * scheduleWin32ModalConfirm). A lone CR that confirms an approval/picker choice
35096
+ * is absorbed by ConPTY the same way a send_message submit CR is, so the confirm
35097
+ * must be resent until the modal actually resolves (status leaves 'approval'). */
35098
+ win32ModalConfirmTimer = null;
35065
35099
  currentEval = null;
35066
35100
  stateHistory = [];
35067
35101
  prevStateAt = 0;
@@ -35186,6 +35220,10 @@ var FsmDriver = class {
35186
35220
  clearTimeout(this.win32WriteTimer);
35187
35221
  this.win32WriteTimer = null;
35188
35222
  }
35223
+ if (this.win32ModalConfirmTimer) {
35224
+ clearTimeout(this.win32ModalConfirmTimer);
35225
+ this.win32ModalConfirmTimer = null;
35226
+ }
35189
35227
  this.specWatcher?.close();
35190
35228
  this.adapter.kill();
35191
35229
  }
@@ -35781,10 +35819,61 @@ var FsmDriver = class {
35781
35819
  const nav = step.repeat(Math.abs(delta));
35782
35820
  const confirm = (rule.key_for_index || "\r").replace(/\{index\}/g, "") || "\r";
35783
35821
  if (nav) this.adapter.send_keys(nav);
35784
- this.adapter.send_keys(confirm);
35822
+ this.submitModalConfirm(confirm);
35823
+ return;
35824
+ }
35825
+ this.submitModalConfirm(btn.key);
35826
+ }
35827
+ /**
35828
+ * Submit a modal-confirm key sequence (the choice key + its trailing CR).
35829
+ *
35830
+ * On win32 the trailing CR is the SAME lone-CR-swallow case as a send_message
35831
+ * submit: ConPTY can absorb a single CR as a literal newline instead of a
35832
+ * confirm, so the approval/picker modal never resolves and the FSM flaps
35833
+ * approval↔busy while auto-approve keeps firing into the void (APPROVESTUCK).
35834
+ * So we split any non-CR prefix (e.g. the "1" of "1\r") off, write it once, and
35835
+ * resend the CR on a fixed cadence until the modal actually resolves (status
35836
+ * leaves 'approval'). Non-win32 keeps the single direct write — its CR submits
35837
+ * on the first try.
35838
+ */
35839
+ submitModalConfirm(keys) {
35840
+ if (process.platform !== "win32") {
35841
+ this.adapter.send_keys(keys);
35785
35842
  return;
35786
35843
  }
35787
- this.adapter.send_keys(btn.key);
35844
+ const m = /^([\s\S]*?)([\r\n]+)$/.exec(keys);
35845
+ const prefix = m ? m[1] : keys;
35846
+ const cr = m ? m[2] : "";
35847
+ if (prefix) this.adapter.send_keys(prefix);
35848
+ if (!cr) return;
35849
+ this.scheduleWin32ModalConfirm(cr);
35850
+ }
35851
+ /**
35852
+ * win32 modal-confirm CR resend loop. Mirrors scheduleWin32Submit's phase-2
35853
+ * verified resend, but gated on still being IN a modal (status 'approval')
35854
+ * rather than still idle: the first CR fires immediately, then resends every
35855
+ * WIN32_SUBMIT_RESEND_GAP_MS while the FSM is still showing the modal, up to
35856
+ * WIN32_SUBMIT_MAX_RESENDS. The instant the modal resolves (status flips to
35857
+ * generating/idle) we stop, so no stray CR leaks into the next turn's composer.
35858
+ */
35859
+ scheduleWin32ModalConfirm(submitKey) {
35860
+ if (this.win32ModalConfirmTimer) {
35861
+ clearTimeout(this.win32ModalConfirmTimer);
35862
+ this.win32ModalConfirmTimer = null;
35863
+ }
35864
+ const fire = (attempt) => {
35865
+ this.win32ModalConfirmTimer = null;
35866
+ this.adapter.send_keys(submitKey);
35867
+ if (attempt + 1 >= WIN32_SUBMIT_MAX_RESENDS) return;
35868
+ this.win32ModalConfirmTimer = setTimeout(() => {
35869
+ if (this.currentStatus() !== "approval") {
35870
+ this.win32ModalConfirmTimer = null;
35871
+ return;
35872
+ }
35873
+ fire(attempt + 1);
35874
+ }, WIN32_SUBMIT_RESEND_GAP_MS);
35875
+ };
35876
+ fire(0);
35788
35877
  }
35789
35878
  handleAttachImage(blob, mime) {
35790
35879
  const ctl = (this.spec.control_bar ?? []).find((c) => c.action.type === "attach_image");
@@ -38652,6 +38741,8 @@ var CliProviderInstance = class _CliProviderInstance {
38652
38741
  };
38653
38742
  }
38654
38743
  }
38744
+ const approvalResolutionBlock = this.approvalResolutionFinalizationBlock(pending);
38745
+ if (approvalResolutionBlock) return approvalResolutionBlock;
38655
38746
  try {
38656
38747
  const screenText = typeof this.adapter.getScreenText === "function" ? String(this.adapter.getScreenText() || "") : "";
38657
38748
  if (screenText) {
@@ -38664,6 +38755,44 @@ var CliProviderInstance = class _CliProviderInstance {
38664
38755
  }
38665
38756
  return null;
38666
38757
  }
38758
+ // (FALSEIDLE-a) Positive, structural proof that the latest approval entry was resolved
38759
+ // through ADHDev. resolveModal() — driven by auto-approve, dashboard/mesh_approve, and
38760
+ // dev-cli-debug alike — advances the engine's lastResolvedEntrySeq to the current
38761
+ // approvalEntrySeq. So `lastResolvedEntrySeq >= approvalEntrySeq` (with a real entry,
38762
+ // approvalEntrySeq > 0) means the modal we last saw was actually answered. Absence of this
38763
+ // evidence after a waiting_approval→idle transition means the idle is suspect: the spec's
38764
+ // text-based approval→idle rule false-tripped while the modal is still unresolved.
38765
+ // Fails OPEN (returns true) when the seq fields are unavailable, so the gate can never wedge
38766
+ // a session on a provider/adapter that does not surface the counters.
38767
+ hasApprovalResolutionEvidence() {
38768
+ try {
38769
+ const status = this.adapter.getStatus({ allowParse: false });
38770
+ const entrySeq = typeof status?.approvalEntrySeq === "number" ? status.approvalEntrySeq : 0;
38771
+ if (entrySeq <= 0) return true;
38772
+ const resolvedSeq = typeof status?.lastResolvedEntrySeq === "number" ? status.lastResolvedEntrySeq : void 0;
38773
+ if (resolvedSeq === void 0) return true;
38774
+ return resolvedSeq >= entrySeq;
38775
+ } catch {
38776
+ return true;
38777
+ }
38778
+ }
38779
+ // (FALSEIDLE-a) Hold a completion that is the anomalous DIRECT waiting_approval→idle
38780
+ // transition with no positive resolution evidence. A genuinely resolved approval routes
38781
+ // through resolveModal → setStatus('generating'), so its completion's previousStatus is
38782
+ // 'generating' (not 'waiting_approval') and this gate never fires for it. Scoped to
38783
+ // delegated mesh/coordinator sessions — whose only modal-resolution path is auto-approve /
38784
+ // mesh_approve (both advance lastResolvedEntrySeq) — so an interactive local session, where
38785
+ // a human may answer the PTY prompt directly and leave no resolveModal record, is untouched.
38786
+ // Non-terminal: the hold is bounded by COMPLETED_FINALIZATION_MAX_WAIT_MS (30s), giving a
38787
+ // settling auto-approve time to fire and advance the seq, and guaranteeing no permanent wedge
38788
+ // if resolution ever happens via a path that does not record evidence.
38789
+ approvalResolutionFinalizationBlock(pending) {
38790
+ if (pending.previousStatus !== "waiting_approval") return null;
38791
+ const meshContext = !!(this.settings.meshNodeFor || this.settings.meshActiveTaskId || this.settings.launchedByCoordinator);
38792
+ if (!meshContext) return null;
38793
+ if (this.hasApprovalResolutionEvidence()) return null;
38794
+ return { reason: "approval_resolution_unconfirmed", terminal: false };
38795
+ }
38667
38796
  scheduleCompletedDebounceFlush(delayMs) {
38668
38797
  if (this.completedDebounceTimer) clearTimeout(this.completedDebounceTimer);
38669
38798
  this.completedDebounceTimer = setTimeout(() => this.flushCompletedDebounceIfFinalized(), delayMs);