@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.
package/dist/index.mjs CHANGED
@@ -311,10 +311,10 @@ function readInjected(value) {
311
311
  }
312
312
  function getDaemonBuildInfo() {
313
313
  if (cached) return cached;
314
- const commit = readInjected(true ? "4b7c1b0d781f263663369085c4dde301d3b5cd2e" : void 0) ?? "unknown";
315
- const commitShort = readInjected(true ? "4b7c1b0d" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
316
- const version = readInjected(true ? "0.9.82-rc.362" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
317
- const builtAt = readInjected(true ? "2026-06-23T12:54:22.426Z" : void 0);
314
+ const commit = readInjected(true ? "1428e9d027beefe860a77f34dea34d202db80f00" : void 0) ?? "unknown";
315
+ const commitShort = readInjected(true ? "1428e9d0" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
316
+ const version = readInjected(true ? "0.9.82-rc.364" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
317
+ const builtAt = readInjected(true ? "2026-06-24T00:05:49.524Z" : void 0);
318
318
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
319
319
  return cached;
320
320
  }
@@ -8588,6 +8588,13 @@ function readEventTimestampValue(value) {
8588
8588
  }
8589
8589
  return 0;
8590
8590
  }
8591
+ function isWeakCompletionMetadata(metadataEvent) {
8592
+ const evidenceLevel = readNonEmptyString2(metadataEvent.evidenceLevel);
8593
+ if (evidenceLevel === "insufficient" || evidenceLevel === "weak") return true;
8594
+ if (metadataEvent.reviewRecommended === true) return true;
8595
+ const diag = readRecord4(metadataEvent.completionDiagnostic);
8596
+ return diag?.finalAssistantPresent === false || diag?.blockReason === "missing_final_assistant";
8597
+ }
8591
8598
  function formatCompletionMetadata(event) {
8592
8599
  const completionDiagnostic = event.completionDiagnostic && typeof event.completionDiagnostic === "object" ? event.completionDiagnostic : null;
8593
8600
  const diagnosticReason = completionDiagnostic ? readNonEmptyString2(completionDiagnostic.blockReason) || "present" : "";
@@ -8610,17 +8617,19 @@ function buildMeshSystemMessage(args) {
8610
8617
  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.`;
8611
8618
  }
8612
8619
  const reviewRecommended = args.metadataEvent.reviewRecommended === true;
8620
+ const weakCompletion = isWeakCompletionMetadata(args.metadataEvent);
8621
+ 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.";
8613
8622
  const completionSummary = readMeshCompletionSummary(args.metadataEvent);
8614
8623
  if (completionSummary) {
8615
8624
  const truncationSuffix = "\n\u2026[truncated \u2014 call mesh_read_chat once for the full transcript]";
8616
8625
  const surfaced = completionSummary.length > MESH_COMPLETION_SURFACE_MAX_CHARS ? `${completionSummary.slice(0, MESH_COMPLETION_SURFACE_MAX_CHARS - truncationSuffix.length)}${truncationSuffix}` : completionSummary;
8617
- const verifyNote = reviewRecommended ? " Completion evidence is insufficient \u2014 verify via git status or provider_session_id before assuming the task is done." : "";
8626
+ const verifyNote = reviewRecommended ? " Completion evidence is insufficient \u2014 verify via git status or provider_session_id before assuming the task is done." : weakCompletion ? verifyTextNote : "";
8618
8627
  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}
8619
8628
 
8620
8629
  --- ${args.nodeLabel} final summary ---
8621
8630
  ${surfaced}`;
8622
8631
  }
8623
- 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.";
8632
+ 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.";
8624
8633
  return `[System] ${args.nodeLabel} has completed its task and is now idle${metadata}. This completion came from the agent status event path;${reviewNote}`;
8625
8634
  }
8626
8635
  if (args.event === "agent:waiting_approval") {
@@ -8758,7 +8767,7 @@ function hasPendingRefineTerminalEventDuplicate(event) {
8758
8767
  (pending) => pending.event === event.event && readRefineJobId2(pending) === jobId
8759
8768
  );
8760
8769
  }
8761
- function isWeakCompletionMetadata(metadata) {
8770
+ function isWeakCompletionMetadata2(metadata) {
8762
8771
  const evidenceLevel = readNonEmptyString2(metadata.evidenceLevel);
8763
8772
  if (evidenceLevel === "insufficient" || evidenceLevel === "weak") return true;
8764
8773
  if (metadata.reviewRecommended === true) return true;
@@ -8777,7 +8786,7 @@ function buildPendingEventFingerprint(event) {
8777
8786
  event.meshId,
8778
8787
  event.event,
8779
8788
  terminalTaskId,
8780
- isWeakCompletionMetadata(metadata) ? "weak" : "genuine"
8789
+ isWeakCompletionMetadata2(metadata) ? "weak" : "genuine"
8781
8790
  ].join("::");
8782
8791
  }
8783
8792
  }
@@ -12860,9 +12869,22 @@ function deliverTaskToSession(dispatchThunk, ctx) {
12860
12869
  }
12861
12870
  });
12862
12871
  }
12872
+ function normalizeMeshWorkspaceForCompare(dir) {
12873
+ if (typeof dir !== "string") return "";
12874
+ return dir.trim().replace(/[\\/]+/g, "/").replace(/\/+$/, "").toLowerCase();
12875
+ }
12863
12876
  function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType) {
12864
12877
  const mesh = getMeshWithCache(components, meshId);
12865
12878
  const node = mesh?.nodes.find((n) => readMeshNodeId(n) === nodeId);
12879
+ const localClaimAdapter = components.cliManager?.adapters?.get(sessionId);
12880
+ if (localClaimAdapter) {
12881
+ const sessionWorkspace = normalizeMeshWorkspaceForCompare(localClaimAdapter.workingDir);
12882
+ const nodeWorkspace = normalizeMeshWorkspaceForCompare(readNonEmptyString2(node?.workspace));
12883
+ if (sessionWorkspace && nodeWorkspace && sessionWorkspace !== nodeWorkspace) {
12884
+ LOG.info("MeshQueue", `WTCLAIM: refusing claim for node ${nodeId} (${sessionId}) \u2014 session workspace "${sessionWorkspace}" \u2260 node workspace "${nodeWorkspace}" (cross-workspace dispatch blocked)`);
12885
+ return false;
12886
+ }
12887
+ }
12866
12888
  const capabilityTags = buildMeshNodeCapabilityTags(node, providerType);
12867
12889
  const providerMaxParallel = resolveProviderMaxParallel(node?.policy, providerType);
12868
12890
  const task = claimNextTask(meshId, nodeId, sessionId, capabilityTags, {
@@ -17373,6 +17395,12 @@ var init_cli_state_engine = __esm({
17373
17395
  * seq) is always a real, distinct approval and must be written.
17374
17396
  */
17375
17397
  approvalEntrySeq = 0;
17398
+ // Records which approval entry the last resolveModal() handled. Set unconditionally on
17399
+ // every resolveModal (auto-approve fire, dashboard/mesh_approve, dev-cli-debug), so
17400
+ // `lastResolvedEntrySeq >= approvalEntrySeq` is positive, ADHDev-side proof that the
17401
+ // current/latest approval entry was actually resolved. Exposed via getStatus so the
17402
+ // completion finalization gate (FALSEIDLE-a) can require resolution evidence before
17403
+ // confirming a waiting_approval→idle transition. Public (read-only by convention).
17376
17404
  lastResolvedEntrySeq = -1;
17377
17405
  /**
17378
17406
  * When the engine previously held a modal but the latest parse failed
@@ -19089,6 +19117,7 @@ ${lastSnapshot}`;
19089
19117
  workingDir: this.workingDir,
19090
19118
  activeModal: effectiveModal,
19091
19119
  approvalEntrySeq: this.engine.approvalEntrySeq,
19120
+ lastResolvedEntrySeq: this.engine.lastResolvedEntrySeq,
19092
19121
  pendingOutboundCount: this.pendingOutboundQueue.length,
19093
19122
  pendingOutboundMessages: this.pendingOutboundQueue.map((message) => ({
19094
19123
  id: message.id,
@@ -34694,6 +34723,11 @@ var FsmDriver = class {
34694
34723
  lastWin32WriteAt = 0;
34695
34724
  /** Pending paced chunk-write timer for a large win32 body (see writeWin32Body). */
34696
34725
  win32WriteTimer = null;
34726
+ /** Timer driving the win32 verification-based modal-confirm CR resend loop (see
34727
+ * scheduleWin32ModalConfirm). A lone CR that confirms an approval/picker choice
34728
+ * is absorbed by ConPTY the same way a send_message submit CR is, so the confirm
34729
+ * must be resent until the modal actually resolves (status leaves 'approval'). */
34730
+ win32ModalConfirmTimer = null;
34697
34731
  currentEval = null;
34698
34732
  stateHistory = [];
34699
34733
  prevStateAt = 0;
@@ -34818,6 +34852,10 @@ var FsmDriver = class {
34818
34852
  clearTimeout(this.win32WriteTimer);
34819
34853
  this.win32WriteTimer = null;
34820
34854
  }
34855
+ if (this.win32ModalConfirmTimer) {
34856
+ clearTimeout(this.win32ModalConfirmTimer);
34857
+ this.win32ModalConfirmTimer = null;
34858
+ }
34821
34859
  this.specWatcher?.close();
34822
34860
  this.adapter.kill();
34823
34861
  }
@@ -35413,10 +35451,61 @@ var FsmDriver = class {
35413
35451
  const nav = step.repeat(Math.abs(delta));
35414
35452
  const confirm = (rule.key_for_index || "\r").replace(/\{index\}/g, "") || "\r";
35415
35453
  if (nav) this.adapter.send_keys(nav);
35416
- this.adapter.send_keys(confirm);
35454
+ this.submitModalConfirm(confirm);
35455
+ return;
35456
+ }
35457
+ this.submitModalConfirm(btn.key);
35458
+ }
35459
+ /**
35460
+ * Submit a modal-confirm key sequence (the choice key + its trailing CR).
35461
+ *
35462
+ * On win32 the trailing CR is the SAME lone-CR-swallow case as a send_message
35463
+ * submit: ConPTY can absorb a single CR as a literal newline instead of a
35464
+ * confirm, so the approval/picker modal never resolves and the FSM flaps
35465
+ * approval↔busy while auto-approve keeps firing into the void (APPROVESTUCK).
35466
+ * So we split any non-CR prefix (e.g. the "1" of "1\r") off, write it once, and
35467
+ * resend the CR on a fixed cadence until the modal actually resolves (status
35468
+ * leaves 'approval'). Non-win32 keeps the single direct write — its CR submits
35469
+ * on the first try.
35470
+ */
35471
+ submitModalConfirm(keys) {
35472
+ if (process.platform !== "win32") {
35473
+ this.adapter.send_keys(keys);
35417
35474
  return;
35418
35475
  }
35419
- this.adapter.send_keys(btn.key);
35476
+ const m = /^([\s\S]*?)([\r\n]+)$/.exec(keys);
35477
+ const prefix = m ? m[1] : keys;
35478
+ const cr = m ? m[2] : "";
35479
+ if (prefix) this.adapter.send_keys(prefix);
35480
+ if (!cr) return;
35481
+ this.scheduleWin32ModalConfirm(cr);
35482
+ }
35483
+ /**
35484
+ * win32 modal-confirm CR resend loop. Mirrors scheduleWin32Submit's phase-2
35485
+ * verified resend, but gated on still being IN a modal (status 'approval')
35486
+ * rather than still idle: the first CR fires immediately, then resends every
35487
+ * WIN32_SUBMIT_RESEND_GAP_MS while the FSM is still showing the modal, up to
35488
+ * WIN32_SUBMIT_MAX_RESENDS. The instant the modal resolves (status flips to
35489
+ * generating/idle) we stop, so no stray CR leaks into the next turn's composer.
35490
+ */
35491
+ scheduleWin32ModalConfirm(submitKey) {
35492
+ if (this.win32ModalConfirmTimer) {
35493
+ clearTimeout(this.win32ModalConfirmTimer);
35494
+ this.win32ModalConfirmTimer = null;
35495
+ }
35496
+ const fire = (attempt) => {
35497
+ this.win32ModalConfirmTimer = null;
35498
+ this.adapter.send_keys(submitKey);
35499
+ if (attempt + 1 >= WIN32_SUBMIT_MAX_RESENDS) return;
35500
+ this.win32ModalConfirmTimer = setTimeout(() => {
35501
+ if (this.currentStatus() !== "approval") {
35502
+ this.win32ModalConfirmTimer = null;
35503
+ return;
35504
+ }
35505
+ fire(attempt + 1);
35506
+ }, WIN32_SUBMIT_RESEND_GAP_MS);
35507
+ };
35508
+ fire(0);
35420
35509
  }
35421
35510
  handleAttachImage(blob, mime) {
35422
35511
  const ctl = (this.spec.control_bar ?? []).find((c) => c.action.type === "attach_image");
@@ -38284,6 +38373,8 @@ var CliProviderInstance = class _CliProviderInstance {
38284
38373
  };
38285
38374
  }
38286
38375
  }
38376
+ const approvalResolutionBlock = this.approvalResolutionFinalizationBlock(pending);
38377
+ if (approvalResolutionBlock) return approvalResolutionBlock;
38287
38378
  try {
38288
38379
  const screenText = typeof this.adapter.getScreenText === "function" ? String(this.adapter.getScreenText() || "") : "";
38289
38380
  if (screenText) {
@@ -38296,6 +38387,44 @@ var CliProviderInstance = class _CliProviderInstance {
38296
38387
  }
38297
38388
  return null;
38298
38389
  }
38390
+ // (FALSEIDLE-a) Positive, structural proof that the latest approval entry was resolved
38391
+ // through ADHDev. resolveModal() — driven by auto-approve, dashboard/mesh_approve, and
38392
+ // dev-cli-debug alike — advances the engine's lastResolvedEntrySeq to the current
38393
+ // approvalEntrySeq. So `lastResolvedEntrySeq >= approvalEntrySeq` (with a real entry,
38394
+ // approvalEntrySeq > 0) means the modal we last saw was actually answered. Absence of this
38395
+ // evidence after a waiting_approval→idle transition means the idle is suspect: the spec's
38396
+ // text-based approval→idle rule false-tripped while the modal is still unresolved.
38397
+ // Fails OPEN (returns true) when the seq fields are unavailable, so the gate can never wedge
38398
+ // a session on a provider/adapter that does not surface the counters.
38399
+ hasApprovalResolutionEvidence() {
38400
+ try {
38401
+ const status = this.adapter.getStatus({ allowParse: false });
38402
+ const entrySeq = typeof status?.approvalEntrySeq === "number" ? status.approvalEntrySeq : 0;
38403
+ if (entrySeq <= 0) return true;
38404
+ const resolvedSeq = typeof status?.lastResolvedEntrySeq === "number" ? status.lastResolvedEntrySeq : void 0;
38405
+ if (resolvedSeq === void 0) return true;
38406
+ return resolvedSeq >= entrySeq;
38407
+ } catch {
38408
+ return true;
38409
+ }
38410
+ }
38411
+ // (FALSEIDLE-a) Hold a completion that is the anomalous DIRECT waiting_approval→idle
38412
+ // transition with no positive resolution evidence. A genuinely resolved approval routes
38413
+ // through resolveModal → setStatus('generating'), so its completion's previousStatus is
38414
+ // 'generating' (not 'waiting_approval') and this gate never fires for it. Scoped to
38415
+ // delegated mesh/coordinator sessions — whose only modal-resolution path is auto-approve /
38416
+ // mesh_approve (both advance lastResolvedEntrySeq) — so an interactive local session, where
38417
+ // a human may answer the PTY prompt directly and leave no resolveModal record, is untouched.
38418
+ // Non-terminal: the hold is bounded by COMPLETED_FINALIZATION_MAX_WAIT_MS (30s), giving a
38419
+ // settling auto-approve time to fire and advance the seq, and guaranteeing no permanent wedge
38420
+ // if resolution ever happens via a path that does not record evidence.
38421
+ approvalResolutionFinalizationBlock(pending) {
38422
+ if (pending.previousStatus !== "waiting_approval") return null;
38423
+ const meshContext = !!(this.settings.meshNodeFor || this.settings.meshActiveTaskId || this.settings.launchedByCoordinator);
38424
+ if (!meshContext) return null;
38425
+ if (this.hasApprovalResolutionEvidence()) return null;
38426
+ return { reason: "approval_resolution_unconfirmed", terminal: false };
38427
+ }
38299
38428
  scheduleCompletedDebounceFlush(delayMs) {
38300
38429
  if (this.completedDebounceTimer) clearTimeout(this.completedDebounceTimer);
38301
38430
  this.completedDebounceTimer = setTimeout(() => this.flushCompletedDebounceIfFinalized(), delayMs);