@adhdev/daemon-core 0.9.82-rc.260 → 0.9.82-rc.262

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.
@@ -26,6 +26,23 @@ export declare class SpecCliAdapter implements CliAdapter {
26
26
  private activeInteractivePrompt;
27
27
  private interactivePromptTransport;
28
28
  private claudeTuiPromptCaptureInFlight;
29
+ /**
30
+ * Wall clock of the first frame on which a held interactive prompt was
31
+ * observed to have left the screen. Mirrors the approval FSM's
32
+ * `modalLostAt` hysteresis (see cli-state-engine.ts): claude-cli's TUI
33
+ * repaints the choice picker as several PTY chunks, so a single frame
34
+ * with no "Enter to select" footer is not proof the prompt is gone — it
35
+ * may just be mid-repaint. We only clear the held prompt once it has
36
+ * been absent across a short grace window. Reset to null the moment the
37
+ * prompt footer reappears.
38
+ *
39
+ * Without this, a choice prompt resolved *directly in the terminal* (the
40
+ * user picked an option without going through ADHDev's
41
+ * setInteractivePromptResponse) was never cleared from
42
+ * `activeInteractivePrompt`, so getStatus() re-emitted the same prompt
43
+ * forever — the choice-resolve-stuck bug.
44
+ */
45
+ private interactivePromptLostAt;
29
46
  private jsonLineTail;
30
47
  private exited;
31
48
  private spawned;
@@ -93,6 +110,27 @@ export declare class SpecCliAdapter implements CliAdapter {
93
110
  private readCurrentScreenSections;
94
111
  private extractProviderSessionIdFromScreen;
95
112
  private readClaudeScreenAssistantMessages;
113
+ /**
114
+ * Grace window a held interactive prompt must be absent from the screen
115
+ * before we treat it as resolved-in-terminal and clear it. claude-cli
116
+ * repaints the picker across multiple PTY chunks, so a single
117
+ * footer-less frame is not proof the prompt is gone. Sized in the same
118
+ * spirit as the approval FSM's `approvalCooldown` modal-lost hysteresis.
119
+ */
120
+ private static readonly INTERACTIVE_PROMPT_LOST_GRACE_MS;
121
+ /**
122
+ * Clear a held interactive prompt once the user has resolved it directly
123
+ * in the terminal (the choice picker leaves the screen without going
124
+ * through setInteractivePromptResponse). The approval path already does
125
+ * this via the FSM's modal-lost hysteresis; the interactive-prompt path
126
+ * had no equivalent, so a terminal-side answer left activeInteractivePrompt
127
+ * set and getStatus() re-emitted the same choice modal forever.
128
+ *
129
+ * Detection mirrors capture: the claude TUI picker is on-screen exactly
130
+ * while its "Enter to select" footer is rendered. When the footer is gone
131
+ * for INTERACTIVE_PROMPT_LOST_GRACE_MS the prompt is genuinely resolved.
132
+ */
133
+ private maybeClearResolvedClaudeTuiPrompt;
96
134
  private maybeCaptureClaudeTuiPrompt;
97
135
  private readClaudeTuiHeaders;
98
136
  private captureClaudeTuiPrompt;
@@ -82,6 +82,36 @@ export interface DriverHistoryEntry {
82
82
  busyHoldMs?: number;
83
83
  via?: string;
84
84
  }
85
+ /**
86
+ * A frozen snapshot of the FULL FSM evaluation captured at the instant a
87
+ * transition fired — the rich `transitions[]` table (per-transition eligible /
88
+ * hold countdown / per-condition CondResult + remainingMs) that `getFsmDebug()`
89
+ * otherwise only computes live for the current instant. Kept in a separate ring
90
+ * buffer from `stateHistory` (which stays intentionally lightweight) so the
91
+ * "why did this rule fire just before the transition" question is answerable
92
+ * after the fact. before-only: this is the evaluation that PRODUCED the
93
+ * transition, not the post-transition state.
94
+ */
95
+ export interface FsmSnapshotEntry {
96
+ /** State we transitioned out of. */
97
+ stateFrom: string;
98
+ /** State we transitioned into (the fired transition's destination). */
99
+ stateTo: string;
100
+ /** Wall-clock time the transition committed (ms). */
101
+ at: number;
102
+ /** The fired transition's destination state id (== stateTo; kept explicit
103
+ * to mirror the rule that fired). */
104
+ firedTo: string;
105
+ /** Human label of the fired transition (e.g. "approval→busy"). */
106
+ firedLabel: string;
107
+ /** Why-it-fired summary, same shape produced for stateHistory.matchedRules. */
108
+ reason: string[];
109
+ /** Every outgoing transition from `stateFrom` as evaluated at `at`, each
110
+ * with its eligible / hold / per-condition CondResult + remainingMs. This
111
+ * is the full pre-transition evaluation table — the whole point of the
112
+ * snapshot. */
113
+ transitions: TransitionEval[];
114
+ }
85
115
  export interface ISpecDriver {
86
116
  subscribe(listener: (ev: DashboardEvent) => void): () => void;
87
117
  start(): void;
@@ -108,6 +138,7 @@ export interface ISpecDriver {
108
138
  forceAfterMs: number;
109
139
  } | null;
110
140
  getFsmDebug?(): unknown;
141
+ getFsmSnapshotHistory?(): ReadonlyArray<FsmSnapshotEntry>;
111
142
  }
112
143
  export interface SpecDriverOpts {
113
144
  specPath: string;
@@ -148,6 +179,10 @@ export declare class FsmDriver implements ISpecDriver {
148
179
  private specWatcher;
149
180
  /** Last full FSM evaluation, kept for the debugger. */
150
181
  private lastFsmEval;
182
+ /** Ring buffer (max 20) of the full FSM evaluation captured at each
183
+ * transition — the rich pre-transition table that lastFsmEval only keeps
184
+ * for the single most recent evaluation. Separate from stateHistory. */
185
+ private fsmSnapshotHistory;
151
186
  constructor(opts: SpecDriverOpts);
152
187
  subscribe(listener: (ev: DashboardEvent) => void): () => void;
153
188
  start(): void;
@@ -172,6 +207,7 @@ export declare class FsmDriver implements ISpecDriver {
172
207
  transitions: TransitionEval[];
173
208
  };
174
209
  getStateHistory(): ReadonlyArray<HistoryEntry>;
210
+ getFsmSnapshotHistory(): ReadonlyArray<FsmSnapshotEntry>;
175
211
  getSections(): Array<{
176
212
  id: string;
177
213
  text: string;
@@ -194,6 +230,11 @@ export declare class FsmDriver implements ISpecDriver {
194
230
  private evalFsmNow;
195
231
  private reevaluate;
196
232
  private commitTransition;
233
+ /** Snapshot the full FSM evaluation that produced a transition into the
234
+ * separate fsmSnapshotHistory ring buffer (max 20). The transitions[]
235
+ * table is captured by reference — it is freshly built per evaluation in
236
+ * evaluateFsm and never mutated after, so no clone is needed. */
237
+ private pushFsmSnapshot;
197
238
  /** Re-derive the visible modal + controls for the current state and emit a
198
239
  * state_changed if anything differs from the last emit. */
199
240
  private emitStateChanged;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.260",
3
+ "version": "0.9.82-rc.262",
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",