@adhdev/daemon-core 0.9.82-rc.389 → 0.9.82-rc.390

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.
@@ -62,6 +62,24 @@ export declare class CliProviderInstance implements ProviderInstance {
62
62
  * from scratch rather than firing on a stale timestamp.
63
63
  */
64
64
  private static readonly AUTO_APPROVE_GATE_HYSTERESIS_MS;
65
+ /**
66
+ * STATUS-MISMATCH: upper bound on how long the auto-approve→`generating` SURFACE
67
+ * mask may hide a worker's `waiting_approval` (status + activeModal) before we give
68
+ * up and surface the real prompt. The mask exists because auto-approve is expected
69
+ * to resolve the modal momentarily; but if it STALLS without ever calling
70
+ * resolveModal — the modal signature never settles for AUTO_APPROVE_SETTLE_MS (a
71
+ * perpetually-flapping/streaming prompt), no concrete modal is ever captured, or the
72
+ * modal is a picker/non-affirmative we never auto-pick — the mask would persist
73
+ * forever and read_chat / mesh_status / the dashboard would NEVER see the pending
74
+ * approval (the coordinator cannot mesh_approve what it cannot see). Once an episode
75
+ * exceeds this bound we stop masking. Generously larger than
76
+ * AUTO_APPROVE_SETTLE_MS (600) + AUTO_APPROVE_GATE_HYSTERESIS_MS (1500) so a
77
+ * legitimately slow-settling / blip-flapping prompt is never unmasked early; a
78
+ * genuine never-resolving stall surfaces within this window. The settle gate keeps
79
+ * running underneath, so a prompt that finally stabilises still auto-approves, and
80
+ * mesh_approve (raw FSM, unmasked) works throughout.
81
+ */
82
+ private static readonly AUTO_APPROVE_MASK_STALL_MS;
65
83
  private adapter;
66
84
  private context;
67
85
  private events;
@@ -80,6 +98,7 @@ export declare class CliProviderInstance implements ProviderInstance {
80
98
  private pendingAutoApprovalSince;
81
99
  private autoApproveSettleTimer;
82
100
  private autoApproveInactiveSince;
101
+ private autoApproveMaskSince;
83
102
  private readonly manualAttendance;
84
103
  private controlValues;
85
104
  private summaryMetadata;
@@ -249,6 +268,7 @@ export declare class CliProviderInstance implements ProviderInstance {
249
268
  * CLI-specific modal text.
250
269
  */
251
270
  private autoApproveEffectivelyActive;
271
+ private autoApproveMaskStalled;
252
272
  private recordAutoApproval;
253
273
  recordApprovalSelection(buttonText: string): void;
254
274
  private formatMarkerTimestamp;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.389",
3
+ "version": "0.9.82-rc.390",
4
4
  "description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -46,7 +46,7 @@
46
46
  "author": "vilmire",
47
47
  "license": "AGPL-3.0-or-later",
48
48
  "dependencies": {
49
- "@adhdev/mesh-shared": "0.9.82-rc.389",
49
+ "@adhdev/mesh-shared": "0.9.82-rc.390",
50
50
  "@adhdev/session-host-core": "*",
51
51
  "@agentclientprotocol/sdk": "^0.16.1",
52
52
  "ajv": "^8.20.0",
@@ -11,6 +11,7 @@ import {
11
11
  handleMeshForwardEvent,
12
12
  drainPendingMeshCoordinatorEvents,
13
13
  shouldHoldPendingDrainForBusyLocalCoordinator,
14
+ resolveCoordinatorDrainDeliverability,
14
15
  } from '../../mesh/mesh-events.js';
15
16
  import { normalizeInteractivePromptResponse } from '../../providers/types/interactive-prompt.js';
16
17
  import type { HighFamilyContext, HighFamilyHandler } from './types.js';
@@ -36,11 +37,31 @@ export const meshEventsHandlers: Record<string, HighFamilyHandler> = {
36
37
  // loop: return nothing, leaving the rows undrained for its idle-tick delivery. A
37
38
  // remote pull (foreign coordinatorDaemonId) or a pure stdio MCP coordinator (no live
38
39
  // CLI session) is NOT held — see shouldHoldPendingDrainForBusyLocalCoordinator.
40
+ // Surface whether THIS daemon has a live CLI coordinator for the mesh. The MCP
41
+ // (LLM) coordinator's pull (drainCoordinatorPendingEvents) needs this to decide its
42
+ // delivery surface: when there is NO live CLI coordinator (pure stdio MCP/LLM), the
43
+ // MCP tool result is the ONLY surface, so the puller must return the drained events
44
+ // to the LLM rather than re-forwarding them (a re-forward just re-queues with no PTY
45
+ // to inject into — the NOTIF-DROP transcript-reconcile drain-without-inject loop).
46
+ // When a live CLI coordinator exists, the reconcile loop owns PTY delivery and the
47
+ // puller keeps forwarding (unchanged). NOTE: this is observational only — it does not
48
+ // change what is drained here, so the idle full-drain and remote-pull paths are intact.
49
+ const hasLiveCliCoordinator = meshId
50
+ ? resolveCoordinatorDrainDeliverability(ctx.deps, meshId).hasLiveCliCoordinator
51
+ : false;
52
+ // DRAIN-WITHOUT-INJECT guard: when a LOCAL live CLI coordinator for this mesh is
53
+ // busy (generating / modal-parked), the reconcile loop is HOLDING its terminal
54
+ // events (drained=0) for the coordinator's next idle tick. Draining here would
55
+ // consume those held rows (drained=1) into an MCP tool result the busy coordinator
56
+ // never surfaces as a turn — losing the completion forever. Defer to the reconcile
57
+ // loop: return nothing, leaving the rows undrained for its idle-tick delivery. A
58
+ // remote pull (foreign coordinatorDaemonId) or a pure stdio MCP coordinator (no live
59
+ // CLI session) is NOT held — see shouldHoldPendingDrainForBusyLocalCoordinator.
39
60
  if (meshId && shouldHoldPendingDrainForBusyLocalCoordinator(ctx.deps, meshId, coordinatorDaemonId)) {
40
- return { success: true, events: [], heldForBusyLocalCoordinator: true };
61
+ return { success: true, events: [], heldForBusyLocalCoordinator: true, hasLiveCliCoordinator };
41
62
  }
42
63
  const events = drainPendingMeshCoordinatorEvents(meshId || undefined, coordinatorDaemonId);
43
- return { success: true, events };
64
+ return { success: true, events, hasLiveCliCoordinator };
44
65
  },
45
66
 
46
67
  interactive_prompt_response: async (ctx: HighFamilyContext, args: any) => {
@@ -426,6 +426,25 @@ export class CliProviderInstance implements ProviderInstance {
426
426
  */
427
427
  private static readonly AUTO_APPROVE_GATE_HYSTERESIS_MS = 1500;
428
428
 
429
+ /**
430
+ * STATUS-MISMATCH: upper bound on how long the auto-approve→`generating` SURFACE
431
+ * mask may hide a worker's `waiting_approval` (status + activeModal) before we give
432
+ * up and surface the real prompt. The mask exists because auto-approve is expected
433
+ * to resolve the modal momentarily; but if it STALLS without ever calling
434
+ * resolveModal — the modal signature never settles for AUTO_APPROVE_SETTLE_MS (a
435
+ * perpetually-flapping/streaming prompt), no concrete modal is ever captured, or the
436
+ * modal is a picker/non-affirmative we never auto-pick — the mask would persist
437
+ * forever and read_chat / mesh_status / the dashboard would NEVER see the pending
438
+ * approval (the coordinator cannot mesh_approve what it cannot see). Once an episode
439
+ * exceeds this bound we stop masking. Generously larger than
440
+ * AUTO_APPROVE_SETTLE_MS (600) + AUTO_APPROVE_GATE_HYSTERESIS_MS (1500) so a
441
+ * legitimately slow-settling / blip-flapping prompt is never unmasked early; a
442
+ * genuine never-resolving stall surfaces within this window. The settle gate keeps
443
+ * running underneath, so a prompt that finally stabilises still auto-approves, and
444
+ * mesh_approve (raw FSM, unmasked) works throughout.
445
+ */
446
+ private static readonly AUTO_APPROVE_MASK_STALL_MS = 4500;
447
+
429
448
  private adapter: ProviderCliAdapter;
430
449
  private context: InstanceContext | null = null;
431
450
  private events: ProviderEvent[] = [];
@@ -459,6 +478,14 @@ export class CliProviderInstance implements ProviderInstance {
459
478
  // a settle gate was in progress. Drives AUTO_APPROVE_GATE_HYSTERESIS_MS so a
460
479
  // brief generating flip does not immediately wipe the settle clock.
461
480
  private autoApproveInactiveSince = 0;
481
+ // STATUS-MISMATCH: wall-clock when the CURRENT auto-approve episode (waiting_approval
482
+ // + shouldAutoApprove) first began wanting to mask. Unlike pendingAutoApprovalSince it
483
+ // is NOT reset when the modal signature changes (a still-streaming/flapping prompt) and
484
+ // survives the same hysteresis blips the settle gate does, so it measures the TRUE age
485
+ // of an unresolved auto-approve. Once it exceeds AUTO_APPROVE_MASK_STALL_MS the surface
486
+ // mask is dropped so the real waiting_approval surfaces. Cleared when the episode ends
487
+ // (modal genuinely gone, manual attendance takes over, or auto-approve fires).
488
+ private autoApproveMaskSince = 0;
462
489
  // Provider-common manual-attendance signal: while a human is actively driving
463
490
  // this session from the dashboard, auto-approve holds so they can take manual
464
491
  // control. Background mesh workers are never attended → delegated auto-approve
@@ -696,7 +723,13 @@ export class CliProviderInstance implements ProviderInstance {
696
723
  this.provider,
697
724
  typeof adapterStatus?.providerSessionId === 'string' ? adapterStatus.providerSessionId : '',
698
725
  );
699
- const autoApproveActive = this.maybeAutoApproveStatus(adapterStatus, Date.now());
726
+ const nowMs = Date.now();
727
+ // STATUS-MISMATCH: maybeAutoApproveStatus still runs for its side effects (settle gate,
728
+ // resolveModal fire), but the SURFACE mask is dropped once the episode has stalled past
729
+ // AUTO_APPROVE_MASK_STALL_MS — otherwise a never-settling auto-approve hides the worker's
730
+ // waiting_approval + modal from read_chat/mesh_status/dashboard forever.
731
+ const autoApproveActive = this.maybeAutoApproveStatus(adapterStatus, nowMs)
732
+ && !this.autoApproveMaskStalled(nowMs);
700
733
  const autoApproveHoldIdle = this.autoApproveBusy && adapterStatus.status === 'idle';
701
734
  let visibleStatus = parseErrorMessage || parsedStatus?.status === 'error'
702
735
  ? 'error'
@@ -884,7 +917,10 @@ export class CliProviderInstance implements ProviderInstance {
884
917
 
885
918
  getHotChatSessionState(): HotChatSessionState {
886
919
  const adapterStatus = this.adapter.getStatus({ allowParse: false });
887
- const autoApproveActive = this.autoApproveEffectivelyActive(adapterStatus.status);
920
+ const nowMs = Date.now();
921
+ // STATUS-MISMATCH: drop the mask once the auto-approve episode has stalled (see getState).
922
+ const autoApproveActive = this.autoApproveEffectivelyActive(adapterStatus.status, nowMs)
923
+ && !this.autoApproveMaskStalled(nowMs);
888
924
  const autoApproveHoldIdle = this.autoApproveBusy && adapterStatus.status === 'idle';
889
925
  const visibleStatus = autoApproveActive || autoApproveHoldIdle ? 'generating' : adapterStatus.status;
890
926
  const runtime = this.adapter.getRuntimeMetadata();
@@ -900,7 +936,10 @@ export class CliProviderInstance implements ProviderInstance {
900
936
 
901
937
  getSessionModalState(sessionId?: string): SessionModalState {
902
938
  const adapterStatus = this.adapter.getStatus({ allowParse: true });
903
- const autoApproveActive = this.autoApproveEffectivelyActive(adapterStatus.status);
939
+ const nowMs = Date.now();
940
+ // STATUS-MISMATCH: drop the mask once the auto-approve episode has stalled (see getState).
941
+ const autoApproveActive = this.autoApproveEffectivelyActive(adapterStatus.status, nowMs)
942
+ && !this.autoApproveMaskStalled(nowMs);
904
943
  const autoApproveHoldIdle = this.autoApproveBusy && adapterStatus.status === 'idle';
905
944
  const visibleStatus = autoApproveActive || autoApproveHoldIdle ? 'generating' : adapterStatus.status;
906
945
  const dirName = workingDirBasename(this.workingDir);
@@ -1045,8 +1084,11 @@ export class CliProviderInstance implements ProviderInstance {
1045
1084
  }
1046
1085
  // A session whose auto-approve is held by manual attendance IS parked on a
1047
1086
  // modal awaiting the human — autoApproveEffectivelyActive folds that in, so
1048
- // the mesh force-inject guard correctly treats it as modal-parked.
1049
- if (adapterStatus.status === 'waiting_approval' && !this.autoApproveEffectivelyActive(adapterStatus.status)) {
1087
+ // the mesh force-inject guard correctly treats it as modal-parked. STATUS-MISMATCH:
1088
+ // a STALLED auto-approve (never resolving) is likewise effectively parked — treat it
1089
+ // as modal-parked so its events are held/surfaced rather than masked behind generating.
1090
+ if (adapterStatus.status === 'waiting_approval'
1091
+ && (!this.autoApproveEffectivelyActive(adapterStatus.status) || this.autoApproveMaskStalled())) {
1050
1092
  return 'waiting_approval';
1051
1093
  }
1052
1094
  return null;
@@ -1750,6 +1792,9 @@ export class CliProviderInstance implements ProviderInstance {
1750
1792
  this.pendingAutoApprovalSignature = '';
1751
1793
  this.pendingAutoApprovalSince = 0;
1752
1794
  this.autoApproveInactiveSince = 0;
1795
+ // Manual attendance takes over — the modal stays surfaced (maybeAutoApproveStatus
1796
+ // returns false), so end the mask episode.
1797
+ this.autoApproveMaskSince = 0;
1753
1798
  if (this.autoApproveSettleTimer) clearTimeout(this.autoApproveSettleTimer);
1754
1799
  this.autoApproveSettleTimer = setTimeout(() => {
1755
1800
  this.autoApproveSettleTimer = null;
@@ -1791,12 +1836,19 @@ export class CliProviderInstance implements ProviderInstance {
1791
1836
  this.pendingAutoApprovalSignature = '';
1792
1837
  this.pendingAutoApprovalSince = 0;
1793
1838
  this.autoApproveInactiveSince = 0;
1839
+ // Modal has genuinely been gone past the hysteresis window → the episode ended;
1840
+ // end the mask episode too (a later approval starts a fresh stall clock).
1841
+ this.autoApproveMaskSince = 0;
1794
1842
  if (this.autoApproveSettleTimer) { clearTimeout(this.autoApproveSettleTimer); this.autoApproveSettleTimer = null; }
1795
1843
  return autoApproveActive;
1796
1844
  }
1797
1845
  // Active approval observed — reset the inactivity tracker so a later
1798
1846
  // blip starts its hysteresis window fresh.
1799
1847
  this.autoApproveInactiveSince = 0;
1848
+ // STATUS-MISMATCH: start (or keep) the mask-stall clock for this episode. Set ONLY
1849
+ // when zero so it survives modal-signature changes and hysteresis blips — it measures
1850
+ // the true age of the unresolved auto-approve, not the per-signature settle window.
1851
+ if (!this.autoApproveMaskSince) this.autoApproveMaskSince = now;
1800
1852
  const modal = adapterStatus.activeModal;
1801
1853
  // (fix) Do not auto-approve when no concrete modal/buttons are present.
1802
1854
  // Claude TUI flaps between paints; without this guard adapterStatus
@@ -1900,6 +1952,8 @@ export class CliProviderInstance implements ProviderInstance {
1900
1952
  this.pendingAutoApprovalSignature = '';
1901
1953
  this.pendingAutoApprovalSince = 0;
1902
1954
  this.autoApproveInactiveSince = 0;
1955
+ // Fired (resolveModal in flight) — the episode resolved; end the mask-stall clock.
1956
+ this.autoApproveMaskSince = 0;
1903
1957
  if (this.autoApproveBusyTimer) clearTimeout(this.autoApproveBusyTimer);
1904
1958
  this.autoApproveBusyTimer = setTimeout(() => {
1905
1959
  this.autoApproveBusy = false;
@@ -2484,6 +2538,18 @@ export class CliProviderInstance implements ProviderInstance {
2484
2538
  && !this.manualAttendance.isAttended(now);
2485
2539
  }
2486
2540
 
2541
+ // STATUS-MISMATCH: true once the current auto-approve episode has been masking
2542
+ // waiting_approval behind `generating` for longer than AUTO_APPROVE_MASK_STALL_MS without
2543
+ // resolving (the settle gate never fired). When stalled, the surface mask must be dropped
2544
+ // so read_chat / mesh_status / the dashboard see the real waiting_approval + modal (and a
2545
+ // coordinator can mesh_approve it). autoApproveMaskSince is maintained by
2546
+ // maybeAutoApproveStatus (driven by getState + the recheck timer during a waiting episode);
2547
+ // this read is side-effect-free so getStatusMetadata can consult it too.
2548
+ private autoApproveMaskStalled(now = Date.now()): boolean {
2549
+ return this.autoApproveMaskSince > 0
2550
+ && now - this.autoApproveMaskSince > CliProviderInstance.AUTO_APPROVE_MASK_STALL_MS;
2551
+ }
2552
+
2487
2553
  private recordAutoApproval(modalMessage?: string, buttonLabel?: string, now = Date.now()): void {
2488
2554
  this.appendRuntimeSystemMessage(
2489
2555
  formatAutoApprovalMessage(modalMessage, buttonLabel),