@adhdev/daemon-standalone 0.9.82-rc.260 → 0.9.82-rc.261

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
@@ -58358,8 +58358,12 @@ ${formatManifestValidationIssues2(validation.issues)}`,
58358
58358
  this.rows = opts.rows ?? import_session_host_core6.DEFAULT_SESSION_HOST_ROWS;
58359
58359
  this.screenDebounceMs = opts.screenChangeDebounceMs ?? 80;
58360
58360
  this.tickIntervalMs = opts.tickIntervalMs ?? 0;
58361
- const { NodePtyTransportFactory: NodePtyTransportFactory2 } = (init_pty_transport(), __toCommonJS2(pty_transport_exports));
58362
- this.factory = opts.transportFactory ?? new NodePtyTransportFactory2();
58361
+ if (opts.transportFactory) {
58362
+ this.factory = opts.transportFactory;
58363
+ } else {
58364
+ const { NodePtyTransportFactory: NodePtyTransportFactory2 } = (init_pty_transport(), __toCommonJS2(pty_transport_exports));
58365
+ this.factory = new NodePtyTransportFactory2();
58366
+ }
58363
58367
  this.screen = new TerminalScreen(this.rows, this.cols);
58364
58368
  }
58365
58369
  rows;
@@ -58514,6 +58518,10 @@ ${formatManifestValidationIssues2(validation.issues)}`,
58514
58518
  specWatcher = null;
58515
58519
  /** Last full FSM evaluation, kept for the debugger. */
58516
58520
  lastFsmEval = null;
58521
+ /** Ring buffer (max 20) of the full FSM evaluation captured at each
58522
+ * transition — the rich pre-transition table that lastFsmEval only keeps
58523
+ * for the single most recent evaluation. Separate from stateHistory. */
58524
+ fsmSnapshotHistory = [];
58517
58525
  subscribe(listener) {
58518
58526
  this.listeners.add(listener);
58519
58527
  return () => {
@@ -58604,6 +58612,9 @@ ${formatManifestValidationIssues2(validation.issues)}`,
58604
58612
  getStateHistory() {
58605
58613
  return this.stateHistory;
58606
58614
  }
58615
+ getFsmSnapshotHistory() {
58616
+ return this.fsmSnapshotHistory;
58617
+ }
58607
58618
  getSections() {
58608
58619
  try {
58609
58620
  const screen = this.adapter.snapshot();
@@ -58699,7 +58710,7 @@ ${formatManifestValidationIssues2(validation.issues)}`,
58699
58710
  this.lastFsmEval = ev;
58700
58711
  this.prevScreenLines = currentLines;
58701
58712
  if (ev.fired) {
58702
- this.commitTransition(ev.fired, now);
58713
+ this.commitTransition(ev.fired, now, ev);
58703
58714
  this.emitStateChanged(forceEmit);
58704
58715
  this.scheduleWakeForState();
58705
58716
  return;
@@ -58708,8 +58719,9 @@ ${formatManifestValidationIssues2(validation.issues)}`,
58708
58719
  this.scheduleWakeForState();
58709
58720
  this.maybeMarkReady();
58710
58721
  }
58711
- commitTransition(fired, now) {
58722
+ commitTransition(fired, now, ev) {
58712
58723
  const from = this.currentStateId;
58724
+ this.pushFsmSnapshot(from, fired, now, ev);
58713
58725
  this.currentStateId = fired.to;
58714
58726
  this.stateEnteredAt = now;
58715
58727
  this.regionLastChangedAt.clear();
@@ -58720,6 +58732,22 @@ ${formatManifestValidationIssues2(validation.issues)}`,
58720
58732
  });
58721
58733
  LOG2.info("FsmDriver", `[${this.specTag()}] ${from} \u2192 ${fired.to} (${fired.label})`);
58722
58734
  }
58735
+ /** Snapshot the full FSM evaluation that produced a transition into the
58736
+ * separate fsmSnapshotHistory ring buffer (max 20). The transitions[]
58737
+ * table is captured by reference — it is freshly built per evaluation in
58738
+ * evaluateFsm and never mutated after, so no clone is needed. */
58739
+ pushFsmSnapshot(from, fired, now, ev) {
58740
+ this.fsmSnapshotHistory.push({
58741
+ stateFrom: from,
58742
+ stateTo: fired.to,
58743
+ at: now,
58744
+ firedTo: fired.to,
58745
+ firedLabel: fired.label,
58746
+ reason: summarizeTransition(fired),
58747
+ transitions: ev.transitions
58748
+ });
58749
+ if (this.fsmSnapshotHistory.length > 20) this.fsmSnapshotHistory.shift();
58750
+ }
58723
58751
  /** Re-derive the visible modal + controls for the current state and emit a
58724
58752
  * state_changed if anything differs from the last emit. */
58725
58753
  emitStateChanged(forceEmit) {
@@ -59748,7 +59776,7 @@ ${formatManifestValidationIssues2(validation.issues)}`,
59748
59776
  function stripAnsi3(text) {
59749
59777
  return String(text || "").replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, "").replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, "").replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
59750
59778
  }
59751
- var SpecCliAdapter = class {
59779
+ var SpecCliAdapter = class _SpecCliAdapter {
59752
59780
  cliType;
59753
59781
  cliName;
59754
59782
  workingDir;
@@ -59773,6 +59801,23 @@ ${formatManifestValidationIssues2(validation.issues)}`,
59773
59801
  activeInteractivePrompt = null;
59774
59802
  interactivePromptTransport = null;
59775
59803
  claudeTuiPromptCaptureInFlight = false;
59804
+ /**
59805
+ * Wall clock of the first frame on which a held interactive prompt was
59806
+ * observed to have left the screen. Mirrors the approval FSM's
59807
+ * `modalLostAt` hysteresis (see cli-state-engine.ts): claude-cli's TUI
59808
+ * repaints the choice picker as several PTY chunks, so a single frame
59809
+ * with no "Enter to select" footer is not proof the prompt is gone — it
59810
+ * may just be mid-repaint. We only clear the held prompt once it has
59811
+ * been absent across a short grace window. Reset to null the moment the
59812
+ * prompt footer reappears.
59813
+ *
59814
+ * Without this, a choice prompt resolved *directly in the terminal* (the
59815
+ * user picked an option without going through ADHDev's
59816
+ * setInteractivePromptResponse) was never cleared from
59817
+ * `activeInteractivePrompt`, so getStatus() re-emitted the same prompt
59818
+ * forever — the choice-resolve-stuck bug.
59819
+ */
59820
+ interactivePromptLostAt = null;
59776
59821
  jsonLineTail = "";
59777
59822
  exited = false;
59778
59823
  spawned = false;
@@ -60033,6 +60078,11 @@ ${formatManifestValidationIssues2(validation.issues)}`,
60033
60078
  // transition from the current state with its per-condition match
60034
60079
  // result + countdown — the canonical "why isn't it moving" answer.
60035
60080
  fsm: this.driver.getFsmDebug?.() ?? null,
60081
+ // v4 FSM transition snapshot history (null for v3 specs). The full
60082
+ // pre-transition evaluation table captured at each transition —
60083
+ // answers "why did this rule fire" after the fact, unlike the live
60084
+ // `fsm` field which only reflects the current instant.
60085
+ fsmHistory: this.driver.getFsmSnapshotHistory?.() ?? null,
60036
60086
  // Extended fields
60037
60087
  name: this.cliName,
60038
60088
  status: this.getStatus().status,
@@ -60070,11 +60120,13 @@ ${formatManifestValidationIssues2(validation.issues)}`,
60070
60120
  if (ev.state.title) {
60071
60121
  LOG2.debug("SpecAdapter", `[${this.cliType}] state.title=${JSON.stringify(ev.state.title)}`);
60072
60122
  }
60123
+ this.maybeClearResolvedClaudeTuiPrompt();
60073
60124
  this.maybeCaptureClaudeTuiPrompt();
60074
60125
  this.statusCallback?.();
60075
60126
  return;
60076
60127
  case "pty_data":
60077
60128
  this.detectInteractivePromptFromPtyChunk(ev.chunk);
60129
+ this.maybeClearResolvedClaudeTuiPrompt();
60078
60130
  this.maybeCaptureClaudeTuiPrompt();
60079
60131
  try {
60080
60132
  this.ptyDataCallback?.(ev.chunk);
@@ -60107,6 +60159,7 @@ ${formatManifestValidationIssues2(validation.issues)}`,
60107
60159
  if (!prompt) continue;
60108
60160
  this.activeInteractivePrompt = prompt;
60109
60161
  this.interactivePromptTransport = "stream-json";
60162
+ this.interactivePromptLostAt = null;
60110
60163
  this.statusCallback?.();
60111
60164
  } catch {
60112
60165
  }
@@ -60160,6 +60213,47 @@ ${formatManifestValidationIssues2(validation.issues)}`,
60160
60213
  }
60161
60214
  return messages;
60162
60215
  }
60216
+ /**
60217
+ * Grace window a held interactive prompt must be absent from the screen
60218
+ * before we treat it as resolved-in-terminal and clear it. claude-cli
60219
+ * repaints the picker across multiple PTY chunks, so a single
60220
+ * footer-less frame is not proof the prompt is gone. Sized in the same
60221
+ * spirit as the approval FSM's `approvalCooldown` modal-lost hysteresis.
60222
+ */
60223
+ static INTERACTIVE_PROMPT_LOST_GRACE_MS = 1500;
60224
+ /**
60225
+ * Clear a held interactive prompt once the user has resolved it directly
60226
+ * in the terminal (the choice picker leaves the screen without going
60227
+ * through setInteractivePromptResponse). The approval path already does
60228
+ * this via the FSM's modal-lost hysteresis; the interactive-prompt path
60229
+ * had no equivalent, so a terminal-side answer left activeInteractivePrompt
60230
+ * set and getStatus() re-emitted the same choice modal forever.
60231
+ *
60232
+ * Detection mirrors capture: the claude TUI picker is on-screen exactly
60233
+ * while its "Enter to select" footer is rendered. When the footer is gone
60234
+ * for INTERACTIVE_PROMPT_LOST_GRACE_MS the prompt is genuinely resolved.
60235
+ */
60236
+ maybeClearResolvedClaudeTuiPrompt() {
60237
+ if (this.cliType !== "claude-cli" || !this.activeInteractivePrompt) return;
60238
+ let screenText = "";
60239
+ try {
60240
+ screenText = this.driver.snapshot();
60241
+ } catch {
60242
+ return;
60243
+ }
60244
+ const stillOnScreen = screenText.includes("Enter to select");
60245
+ if (stillOnScreen) {
60246
+ this.interactivePromptLostAt = null;
60247
+ return;
60248
+ }
60249
+ const lostAt = this.interactivePromptLostAt ?? Date.now();
60250
+ if (this.interactivePromptLostAt === null) this.interactivePromptLostAt = lostAt;
60251
+ if (Date.now() - lostAt < _SpecCliAdapter.INTERACTIVE_PROMPT_LOST_GRACE_MS) return;
60252
+ this.activeInteractivePrompt = null;
60253
+ this.interactivePromptTransport = null;
60254
+ this.interactivePromptLostAt = null;
60255
+ this.statusCallback?.();
60256
+ }
60163
60257
  maybeCaptureClaudeTuiPrompt() {
60164
60258
  if (this.cliType !== "claude-cli" || this.activeInteractivePrompt || this.claudeTuiPromptCaptureInFlight) return;
60165
60259
  const screenText = this.driver.snapshot();
@@ -60173,6 +60267,7 @@ ${formatManifestValidationIssues2(validation.issues)}`,
60173
60267
  if (!prompt) return;
60174
60268
  this.activeInteractivePrompt = prompt;
60175
60269
  this.interactivePromptTransport = "tui";
60270
+ this.interactivePromptLostAt = null;
60176
60271
  this.statusCallback?.();
60177
60272
  return;
60178
60273
  }
@@ -60209,6 +60304,7 @@ ${formatManifestValidationIssues2(validation.issues)}`,
60209
60304
  if (!prompt) return;
60210
60305
  this.activeInteractivePrompt = prompt;
60211
60306
  this.interactivePromptTransport = "tui";
60307
+ this.interactivePromptLostAt = null;
60212
60308
  this.statusCallback?.();
60213
60309
  }
60214
60310
  getDebugState() {
@@ -60263,6 +60359,9 @@ ${formatManifestValidationIssues2(validation.issues)}`,
60263
60359
  // and countdown. This is the canonical "why isn't it transitioning"
60264
60360
  // answer — no screenshots needed.
60265
60361
  fsm: this.driver.getFsmDebug?.() ?? null,
60362
+ // v4 FSM transition snapshot history — the captured pre-transition
60363
+ // evaluation table at each transition (null for v3 specs).
60364
+ fsmHistory: this.driver.getFsmSnapshotHistory?.() ?? null,
60266
60365
  messages,
60267
60366
  committedMessages: messages
60268
60367
  };