@adhdev/daemon-core 0.8.89 → 0.8.91

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/session-host-core",
3
- "version": "0.8.89",
3
+ "version": "0.8.91",
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.89",
3
+ "version": "0.8.91",
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",
@@ -677,6 +677,7 @@ export class ProviderCliAdapter implements CliAdapter {
677
677
  private looksLikeClaudeGeneratingLine(line: string): boolean {
678
678
  const trimmed = String(line || '').trim();
679
679
  if (!trimmed) return false;
680
+ if (/^⏵⏵\s+accept edits on/i.test(trimmed)) return false;
680
681
  if (/esc to (cancel|interrupt|stop)/i.test(trimmed)) return true;
681
682
  if (/^[✻✶✳✢✽⠂⠐⠒⠓⠦⠴⠶⠷⠿]+\s+\S+.*\b(?:thinking|thought for \d+s?)\b/i.test(trimmed)) return true;
682
683
  if (/^[✻✶✳✢✽⠂⠐⠒⠓⠦⠴⠶⠷⠿]+\s+[A-Z][A-Za-z-]{3,}ing\b.*(?:…|\.{3})/u.test(trimmed)) return true;
@@ -922,15 +923,22 @@ export class ProviderCliAdapter implements CliAdapter {
922
923
  return;
923
924
  }
924
925
  const startupModal = this.getStartupConfirmationModal(screenText);
925
- const modal = this.runParseApproval(tail) || startupModal;
926
- const rawScriptStatus = this.runDetectStatus(tail);
927
- // detectStatus is the sole authority for status. parseApproval only enriches modal info.
928
- const scriptStatus = startupModal ? 'waiting_approval' : rawScriptStatus;
929
926
  const parsedTranscript = this.parseCurrentTranscript(
930
927
  this.committedMessages,
931
928
  this.responseBuffer,
932
929
  this.currentTurnScope,
933
930
  );
931
+ const parsedModal = parsedTranscript?.activeModal && Array.isArray(parsedTranscript.activeModal.buttons) && parsedTranscript.activeModal.buttons.some((button: any) => typeof button === 'string' && button.trim())
932
+ ? parsedTranscript.activeModal
933
+ : null;
934
+ const modal = this.runParseApproval(tail) || parsedModal || startupModal;
935
+ const rawScriptStatus = this.runDetectStatus(tail);
936
+ // detectStatus is the primary authority for status, but if the parsed transcript
937
+ // already surfaced actionable approval buttons, promote that state so runtime
938
+ // status and resolve_action stay aligned with the visible prompt.
939
+ const scriptStatus = startupModal
940
+ ? 'waiting_approval'
941
+ : (parsedModal && parsedTranscript?.status === 'waiting_approval' ? 'waiting_approval' : rawScriptStatus);
934
942
  const parsedMessages = Array.isArray(parsedTranscript?.messages)
935
943
  ? normalizeCliParsedMessages(parsedTranscript.messages, {
936
944
  committedMessages: this.committedMessages,
@@ -942,6 +950,9 @@ export class ProviderCliAdapter implements CliAdapter {
942
950
  return;
943
951
  }
944
952
  const lastParsedAssistant = [...parsedMessages].reverse().find((message) => message.role === 'assistant');
953
+ const parsedShowsLiveAssistantProgress = parsedTranscript?.status === 'generating'
954
+ && !!lastParsedAssistant
955
+ && parsedMessages.length > this.committedMessages.length;
945
956
  const normalizedPromptSnippet = normalizePromptText(this.submitRetryPromptSnippet || this.currentTurnScope?.prompt || '');
946
957
  this.recordTrace('settled', {
947
958
  tail: summarizeCliTraceText(tail, 500),
@@ -1049,7 +1060,8 @@ export class ProviderCliAdapter implements CliAdapter {
1049
1060
  && this.isWaitingForResponse
1050
1061
  && !modal
1051
1062
  && recentInteractiveActivity
1052
- && !(visibleIdlePrompt && visibleAssistantCandidate);
1063
+ && !(visibleIdlePrompt && visibleAssistantCandidate)
1064
+ && !(parsedTranscript?.status === 'idle' && !!lastParsedAssistant);
1053
1065
 
1054
1066
  if (shouldHoldGenerating) {
1055
1067
  this.clearIdleFinishCandidate('hold_generating_recent_activity');
@@ -1124,7 +1136,7 @@ export class ProviderCliAdapter implements CliAdapter {
1124
1136
  && (/Update available!/i.test(screenText)
1125
1137
  || /\/effort/i.test(screenText)
1126
1138
  || /^.*➜\s+\S+/m.test(effectiveScreenText)));
1127
- if (prevStatus === 'idle' && !this.isWaitingForResponse && noActiveTurn && !modal && looksIdleChrome) {
1139
+ if (prevStatus === 'idle' && !this.isWaitingForResponse && noActiveTurn && !modal && looksIdleChrome && !parsedShowsLiveAssistantProgress) {
1128
1140
  return;
1129
1141
  }
1130
1142
  if (prevStatus === 'waiting_approval') {
@@ -1461,14 +1473,19 @@ export class ProviderCliAdapter implements CliAdapter {
1461
1473
  }
1462
1474
  }
1463
1475
 
1476
+ private projectEffectiveStatus(startupModal: { message: string; buttons: string[] } | null = null): CliSessionStatus['status'] {
1477
+ if (this.parseErrorMessage) return 'error';
1478
+ if (startupModal) return 'waiting_approval';
1479
+ if (this.isWaitingForResponse && this.currentTurnScope && this.currentStatus === 'idle') return 'generating';
1480
+ return this.currentStatus;
1481
+ }
1482
+
1464
1483
  // ─── Public API (CliAdapter) ───────────────────
1465
1484
 
1466
1485
  getStatus(): CliSessionStatus {
1467
1486
  const screenText = this.terminalScreen.getText() || '';
1468
1487
  const startupModal = this.startupParseGate ? this.getStartupConfirmationModal(screenText) : null;
1469
- const effectiveStatus = this.parseErrorMessage
1470
- ? 'error'
1471
- : (startupModal ? 'waiting_approval' : this.currentStatus);
1488
+ const effectiveStatus = this.projectEffectiveStatus(startupModal);
1472
1489
  return {
1473
1490
  status: effectiveStatus,
1474
1491
  messages: [...this.committedMessages],
@@ -2201,7 +2218,7 @@ export class ProviderCliAdapter implements CliAdapter {
2201
2218
  }
2202
2219
  this.setStatus('generating', 'approval_resolved');
2203
2220
  this.onStatusChange?.();
2204
- const startupTrustModal = /Quick safety check|project trust|trust (?:this project|the contents of this directory|the files in this folder)/i.test(String(modal?.message || ''));
2221
+ const startupTrustModal = /Quick safety check|project trust|Confirm Claude Code project trust|trust (?:this project|the contents of this directory|the files in this folder)/i.test(String(modal?.message || ''));
2205
2222
  if (startupTrustModal && buttonIndex in this.approvalKeys) {
2206
2223
  this.ptyProcess.write(`${this.approvalKeys[buttonIndex]}\r`);
2207
2224
  } else if (this.shouldResolveModalWithEnter(modal, buttonIndex)) {
@@ -2228,7 +2245,7 @@ export class ProviderCliAdapter implements CliAdapter {
2228
2245
  getDebugState(): Record<string, any> {
2229
2246
  const screenText = sanitizeTerminalText(this.terminalScreen.getText());
2230
2247
  const startupModal = this.startupParseGate ? this.getStartupConfirmationModal(screenText) : null;
2231
- const effectiveStatus = startupModal ? 'waiting_approval' : this.currentStatus;
2248
+ const effectiveStatus = this.projectEffectiveStatus(startupModal);
2232
2249
  const effectiveReady = this.ready || !!startupModal;
2233
2250
  return {
2234
2251
  type: this.cliType,
@@ -139,10 +139,10 @@ export function buildCliParseInput(options: {
139
139
  runtimeSettings,
140
140
  } = options;
141
141
  const buffer = scope
142
- ? (sliceFromOffset(accumulatedBuffer, scope.bufferStart) || accumulatedBuffer)
142
+ ? sliceFromOffset(accumulatedBuffer, scope.bufferStart)
143
143
  : accumulatedBuffer;
144
144
  const rawBuffer = scope
145
- ? (sliceFromOffset(accumulatedRawBuffer, scope.rawBufferStart) || accumulatedRawBuffer)
145
+ ? sliceFromOffset(accumulatedRawBuffer, scope.rawBufferStart)
146
146
  : accumulatedRawBuffer;
147
147
  const screenText = terminalScreenText;
148
148
  const recentBuffer = buffer.slice(-1000) || recentOutputBuffer;
@@ -189,10 +189,10 @@ export function buildCliTraceParseSnapshot(options: {
189
189
  }): Record<string, any> {
190
190
  const { accumulatedBuffer, accumulatedRawBuffer, responseBuffer, partialResponse, scope } = options;
191
191
  const scopedBuffer = scope
192
- ? (sliceFromOffset(accumulatedBuffer, scope.bufferStart) || accumulatedBuffer)
192
+ ? sliceFromOffset(accumulatedBuffer, scope.bufferStart)
193
193
  : accumulatedBuffer;
194
194
  const scopedRawBuffer = scope
195
- ? (sliceFromOffset(accumulatedRawBuffer, scope.rawBufferStart) || accumulatedRawBuffer)
195
+ ? sliceFromOffset(accumulatedRawBuffer, scope.rawBufferStart)
196
196
  : accumulatedRawBuffer;
197
197
  return {
198
198
  currentTurnScope: scope || null,
@@ -491,13 +491,17 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
491
491
  && adapterStatus.messages.length > 0
492
492
  && Array.isArray(parsedRecord?.messages)
493
493
  && adapterStatus.messages.length > parsedRecord.messages.length;
494
+ const parsedShowsApproval = hasNonEmptyModalButtons(parsedRecord?.activeModal)
495
+ && parsedRecord?.status === 'waiting_approval';
494
496
  const status = parsedRecord
495
497
  ? {
496
498
  ...parsedRecord,
497
499
  messages: shouldPreferAdapterMessages ? adapterStatus.messages : parsedRecord.messages,
498
- status: adapterStatus.status !== 'idle'
499
- ? adapterStatus.status
500
- : (parsedRecord.status || adapterStatus.status),
500
+ status: parsedShowsApproval
501
+ ? parsedRecord.status
502
+ : (adapterStatus.status !== 'idle'
503
+ ? adapterStatus.status
504
+ : (parsedRecord.status || adapterStatus.status)),
501
505
  activeModal: parsedRecord.activeModal || adapterStatus.activeModal,
502
506
  }
503
507
  : adapterStatus;
@@ -91,6 +91,11 @@ export class DaemonStatusReporter {
91
91
  }
92
92
 
93
93
  onStatusChange(): void {
94
+ if (this.deps.p2p?.isConnected) {
95
+ this.resetP2PHash();
96
+ this.sendUnifiedStatusReport({ p2pOnly: true, reason: 'status-change' })
97
+ .catch(e => LOG.warn('Status', `Immediate P2P status send failed: ${e?.message}`));
98
+ }
94
99
  this.throttledReport();
95
100
  }
96
101
 
@@ -215,10 +220,12 @@ export class DaemonStatusReporter {
215
220
 
216
221
  async sendUnifiedStatusReport(opts?: { p2pOnly?: boolean; forceServer?: boolean; reason?: string }): Promise<void> {
217
222
  const { serverConn, p2p } = this.deps;
218
- if (!serverConn?.isConnected()) return;
223
+ const serverConnected = !!serverConn?.isConnected();
224
+ const p2pConnected = !!p2p?.isConnected;
225
+ if (!serverConnected && !p2pConnected) return;
219
226
  this.lastStatusSentAt = Date.now();
220
227
  const now = this.lastStatusSentAt;
221
- const target = opts?.p2pOnly ? 'P2P' : 'P2P+Server';
228
+ const target = opts?.p2pOnly ? 'P2P' : (serverConnected ? 'P2P+Server' : 'P2P');
222
229
 
223
230
  const allStates = this.deps.instanceManager.collectAllStates();
224
231
  const ideStates = allStates.filter((s): s is IdeProviderState => s.category === 'ide');
@@ -316,6 +323,7 @@ export class DaemonStatusReporter {
316
323
  ...wsPayload,
317
324
  timestamp: undefined,
318
325
  }));
326
+ if (!serverConnected || !serverConn) return;
319
327
  if (!opts?.forceServer && wsHash === this.lastServerStatusHash) {
320
328
  LOG.debug('Server', `skip duplicate status_report${opts?.reason ? ` (${opts.reason})` : ''}`);
321
329
  return;