@adhdev/daemon-core 0.8.96 → 0.8.97

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.
@@ -42,6 +42,8 @@ export declare class CliProviderInstance implements ProviderInstance {
42
42
  private generatingDebounceTimer;
43
43
  private generatingDebouncePending;
44
44
  private lastApprovalEventAt;
45
+ private autoApproveBusy;
46
+ private autoApproveBusyTimer;
45
47
  private controlValues;
46
48
  private summaryMetadata;
47
49
  private appliedEffectKeys;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/session-host-core",
3
- "version": "0.8.96",
3
+ "version": "0.8.97",
4
4
  "description": "ADHDev local session host core \u2014 session registry, protocol, buffers",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.8.96",
3
+ "version": "0.8.97",
4
4
  "description": "ADHDev daemon core \u2014 CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -94,6 +94,38 @@ interface IdleFinishCandidate {
94
94
  assistantLength: number;
95
95
  }
96
96
 
97
+ function normalizeComparableTranscriptText(value: unknown): string {
98
+ return sanitizeTerminalText(String(value || ''))
99
+ .replace(/\s+/g, ' ')
100
+ .trim();
101
+ }
102
+
103
+ function parsedTranscriptIsRicherThanCommitted(
104
+ parsedMessages: Array<{ role?: string; content?: unknown; id?: string; index?: number }> | null | undefined,
105
+ committedMessages: Array<{ role?: string; content?: unknown; id?: string; index?: number }> | null | undefined,
106
+ ): boolean {
107
+ if (!Array.isArray(parsedMessages) || !Array.isArray(committedMessages)) return false;
108
+ if (parsedMessages.length > committedMessages.length) return true;
109
+ if (parsedMessages.length !== committedMessages.length) return false;
110
+
111
+ for (let index = 0; index < parsedMessages.length; index += 1) {
112
+ const parsed = parsedMessages[index];
113
+ const committed = committedMessages[index];
114
+ if (!parsed || !committed) return false;
115
+ if ((parsed.role || '') !== (committed.role || '')) return false;
116
+ if (parsed.id && committed.id && String(parsed.id) !== String(committed.id)) return false;
117
+ if (typeof parsed.index === 'number' && typeof committed.index === 'number' && parsed.index !== committed.index) return false;
118
+
119
+ const parsedText = normalizeComparableTranscriptText(parsed.content);
120
+ const committedText = normalizeComparableTranscriptText(committed.content);
121
+ if (!parsedText || !committedText || parsedText === committedText) continue;
122
+ if (parsedText.length > committedText.length && parsedText.startsWith(committedText)) return true;
123
+ return false;
124
+ }
125
+
126
+ return false;
127
+ }
128
+
97
129
  // ─── Adapter ────────────────────────────────────────
98
130
 
99
131
  export class ProviderCliAdapter implements CliAdapter {
@@ -1621,7 +1653,7 @@ export class ProviderCliAdapter implements CliAdapter {
1621
1653
  !this.currentTurnScope
1622
1654
  && !this.activeModal
1623
1655
  && !!parsedLastAssistant
1624
- && parsedHydratedMessages.length > committedHydratedMessages.length
1656
+ && parsedTranscriptIsRicherThanCommitted(parsedHydratedMessages, committedHydratedMessages)
1625
1657
  && (
1626
1658
  this.currentStatus === 'idle'
1627
1659
  || (
@@ -152,6 +152,8 @@ export class CliProviderInstance implements ProviderInstance {
152
152
  private generatingDebounceTimer: NodeJS.Timeout | null = null;
153
153
  private generatingDebouncePending: { chatTitle: string; timestamp: number } | null = null;
154
154
  private lastApprovalEventAt = 0;
155
+ private autoApproveBusy = false;
156
+ private autoApproveBusyTimer: NodeJS.Timeout | null = null;
155
157
  private controlValues: Record<string, string | number | boolean> = {};
156
158
  private summaryMetadata: unknown = undefined;
157
159
  private appliedEffectKeys = new Set<string>();
@@ -542,7 +544,17 @@ export class CliProviderInstance implements ProviderInstance {
542
544
  const parsedStatus = this.adapter.getScriptParsedStatus?.() || null;
543
545
  const rawStatus = adapterStatus.status;
544
546
  const autoApproveActive = rawStatus === 'waiting_approval' && this.shouldAutoApprove();
545
- if (autoApproveActive) {
547
+ // Guard re-entry: onStatusChange can fire multiple times while the modal
548
+ // is still on screen (before the PTY absorbs the approval key). Without this
549
+ // flag, we'd write the approval key repeatedly — stray keys then leak into
550
+ // the text input once Claude Code dismisses the modal.
551
+ if (autoApproveActive && !this.autoApproveBusy) {
552
+ this.autoApproveBusy = true;
553
+ if (this.autoApproveBusyTimer) clearTimeout(this.autoApproveBusyTimer);
554
+ this.autoApproveBusyTimer = setTimeout(() => {
555
+ this.autoApproveBusy = false;
556
+ this.autoApproveBusyTimer = null;
557
+ }, 2000);
546
558
  const { index: buttonIndex, label: buttonLabel } = pickApprovalButton(adapterStatus.activeModal?.buttons, this.provider);
547
559
  this.recordAutoApproval(adapterStatus.activeModal?.message, buttonLabel, now);
548
560
  setTimeout(() => {