@adhdev/daemon-core 0.9.30 → 0.9.32

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.
@@ -85,6 +85,7 @@ export declare class ProviderLoader {
85
85
  probeStarts?: string[];
86
86
  });
87
87
  private log;
88
+ private debugLog;
88
89
  /**
89
90
  * User override root (~/.adhdev/providers by default).
90
91
  */
@@ -15,6 +15,7 @@ export declare function classifyHotChatSessionsForSubscriptionFlush(sessions: Ho
15
15
  now?: number;
16
16
  recentMessageGraceMs?: number;
17
17
  activeStatuses?: ReadonlySet<string>;
18
+ activeSessionIds?: ReadonlySet<string>;
18
19
  }): {
19
20
  active: Set<string>;
20
21
  finalizing: Set<string>;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/session-host-core",
3
- "version": "0.9.30",
3
+ "version": "0.9.32",
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.9.30",
3
+ "version": "0.9.32",
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",
@@ -282,6 +282,7 @@ export class ProviderCliAdapter implements CliAdapter {
282
282
  private static readonly STATUS_HOT_PATH_PARSE_MIN_INTERVAL_MS = 1000;
283
283
  private static readonly SCREEN_SNAPSHOT_MIN_INTERVAL_MS = 250;
284
284
  private static readonly MAX_TRACE_ENTRIES = 250;
285
+ private static readonly PARSE_MESSAGE_TAIL_LIMIT = 100;
285
286
 
286
287
  private readonly providerResolutionMeta: ProviderResolutionMeta;
287
288
  private static readonly FINISH_RETRY_DELAY_MS = 300;
@@ -333,6 +334,42 @@ export class ProviderCliAdapter implements CliAdapter {
333
334
  return null;
334
335
  }
335
336
 
337
+ private selectParseBaseMessages(baseMessages: CliChatMessage[]): CliChatMessage[] {
338
+ if (baseMessages.length <= ProviderCliAdapter.PARSE_MESSAGE_TAIL_LIMIT) return baseMessages;
339
+ return baseMessages.slice(-ProviderCliAdapter.PARSE_MESSAGE_TAIL_LIMIT);
340
+ }
341
+
342
+ private messagesComparable(left: any, right: any): boolean {
343
+ if (!left || !right) return false;
344
+ if ((left.role || '') !== (right.role || '')) return false;
345
+ const leftText = normalizeComparableTranscriptText(left.content);
346
+ const rightText = normalizeComparableTranscriptText(right.content);
347
+ return !!leftText && leftText === rightText;
348
+ }
349
+
350
+ private stitchParsedMessagesWithCommittedBase(
351
+ parsedMessages: any[],
352
+ fullBaseMessages: CliChatMessage[],
353
+ parseBaseMessages: CliChatMessage[],
354
+ ): any[] {
355
+ if (!Array.isArray(parsedMessages) || parsedMessages.length === 0) return parsedMessages;
356
+ if (fullBaseMessages.length <= parseBaseMessages.length) return parsedMessages;
357
+
358
+ const parsedFirst = parsedMessages[0];
359
+ const fullFirst = fullBaseMessages[0];
360
+ if (parsedMessages.length >= fullBaseMessages.length && this.messagesComparable(parsedFirst, fullFirst)) {
361
+ return parsedMessages;
362
+ }
363
+
364
+ const tailFirst = parseBaseMessages[0];
365
+ if (tailFirst && this.messagesComparable(parsedFirst, tailFirst)) {
366
+ const prefixLength = fullBaseMessages.length - parseBaseMessages.length;
367
+ return [...fullBaseMessages.slice(0, prefixLength), ...parsedMessages];
368
+ }
369
+
370
+ return [...fullBaseMessages, ...parsedMessages];
371
+ }
372
+
336
373
  private getIdleFinishConfirmMs(): number {
337
374
  return this.timeouts.idleFinishConfirm;
338
375
  }
@@ -1044,7 +1081,7 @@ export class ProviderCliAdapter implements CliAdapter {
1044
1081
  if (!this.applyPendingScriptStatusDebounce(ctx)) return;
1045
1082
 
1046
1083
  const recentInteractiveActivity = this.hasRecentInteractiveActivity(now);
1047
- LOG.info(
1084
+ LOG.debug(
1048
1085
  'CLI',
1049
1086
  `[${this.cliType}] settled diagnostics prompt=${JSON.stringify(this.currentTurnScope?.prompt || '').slice(0, 140)} status=${String(status || '')} parsedStatus=${String(parsedStatus || '')} parsedMsgCount=${parsedMessages.length} lastParsedAssistant=${JSON.stringify(summarizeCliTraceText(lastParsedAssistant?.content || '', 120)).slice(0, 160)} responseBuffer=${JSON.stringify(summarizeCliTraceText(this.responseBuffer, 160)).slice(0, 220)} screen=${JSON.stringify(summarizeCliTraceText(screenText, 160)).slice(0, 220)}`
1050
1087
  );
@@ -1439,18 +1476,26 @@ export class ProviderCliAdapter implements CliAdapter {
1439
1476
  try {
1440
1477
  const screenText = this.terminalScreen.getText();
1441
1478
  const tail = this.recentOutputBuffer.slice(-500);
1479
+ const parseBaseMessages = this.selectParseBaseMessages(this.committedMessages);
1442
1480
  const input = buildCliParseInput({
1443
1481
  accumulatedBuffer: this.accumulatedBuffer,
1444
1482
  accumulatedRawBuffer: this.accumulatedRawBuffer,
1445
1483
  recentOutputBuffer: this.recentOutputBuffer,
1446
1484
  terminalScreenText: screenText,
1447
- baseMessages: this.committedMessages,
1485
+ baseMessages: parseBaseMessages,
1448
1486
  partialResponse: this.responseBuffer,
1449
1487
  isWaitingForResponse: this.isWaitingForResponse,
1450
1488
  scope: this.currentTurnScope,
1451
1489
  runtimeSettings: this.runtimeSettings,
1452
1490
  });
1453
1491
  const session = this.cliScripts.parseSession({ ...input, tail, tailScreen: buildCliScreenSnapshot(tail) });
1492
+ if (session && typeof session === 'object' && Array.isArray(session.messages)) {
1493
+ session.messages = this.stitchParsedMessagesWithCommittedBase(
1494
+ session.messages,
1495
+ this.committedMessages,
1496
+ parseBaseMessages,
1497
+ );
1498
+ }
1454
1499
  this.parseErrorMessage = null;
1455
1500
  return session && typeof session === 'object' ? session : null;
1456
1501
  } catch (e: any) {
@@ -1871,12 +1916,13 @@ export class ProviderCliAdapter implements CliAdapter {
1871
1916
  }
1872
1917
  try {
1873
1918
  const screenText = typeof screenTextOverride === 'string' ? screenTextOverride : this.terminalScreen.getText();
1919
+ const parseBaseMessages = this.selectParseBaseMessages(baseMessages);
1874
1920
  const input = buildCliParseInput({
1875
1921
  accumulatedBuffer: this.accumulatedBuffer,
1876
1922
  accumulatedRawBuffer: this.accumulatedRawBuffer,
1877
1923
  recentOutputBuffer: this.recentOutputBuffer,
1878
1924
  terminalScreenText: screenText,
1879
- baseMessages,
1925
+ baseMessages: parseBaseMessages,
1880
1926
  partialResponse,
1881
1927
  isWaitingForResponse: this.isWaitingForResponse,
1882
1928
  scope,
@@ -1888,6 +1934,11 @@ export class ProviderCliAdapter implements CliAdapter {
1888
1934
  }
1889
1935
  const normalizedParsed = this.suppressStaleParsedApproval(parsed, input.recentBuffer, input.screenText);
1890
1936
  if (normalizedParsed && Array.isArray(normalizedParsed.messages)) {
1937
+ normalizedParsed.messages = this.stitchParsedMessagesWithCommittedBase(
1938
+ normalizedParsed.messages,
1939
+ baseMessages,
1940
+ parseBaseMessages,
1941
+ );
1891
1942
  this.trimLastAssistantEcho(normalizedParsed.messages, scope?.prompt || getLastUserPromptText(baseMessages));
1892
1943
  }
1893
1944
  this.parseErrorMessage = null;
@@ -583,11 +583,18 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
583
583
 
584
584
  // Extension transport: evaluateInSession
585
585
  if (isExtensionTransport(transport)) {
586
+ let extensionReadChatError = '';
586
587
  try {
587
588
  const evalResult = await h.evaluateProviderScript('readChat', undefined, READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS);
588
589
  if (evalResult?.result) {
589
590
  let parsed = evalResult.result;
590
- if (typeof parsed === 'string') { try { parsed = JSON.parse(parsed); } catch { } }
591
+ if (typeof parsed === 'string') {
592
+ try {
593
+ parsed = JSON.parse(parsed);
594
+ } catch (e: any) {
595
+ extensionReadChatError = `extension read_chat parse failed: ${e?.message || String(e)}`;
596
+ }
597
+ }
591
598
  if (parsed && typeof parsed === 'object') {
592
599
  const validated = validateReadChatResultPayload(parsed, 'extension read_chat');
593
600
  _log(`Extension OK: ${validated.messages?.length || 0} msgs`);
@@ -610,8 +617,14 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
610
617
  );
611
618
  return buildReadChatCommandResult(validated as Record<string, any>, args);
612
619
  }
620
+ if (!extensionReadChatError) {
621
+ extensionReadChatError = 'extension read_chat returned a non-object payload';
622
+ }
623
+ } else {
624
+ extensionReadChatError = 'extension read_chat returned no payload';
613
625
  }
614
626
  } catch (e: any) {
627
+ extensionReadChatError = `extension read_chat failed: ${e?.message || String(e)}`;
615
628
  _log(`Extension error: ${e.message}`);
616
629
  traceProviderEvent(args, 'provider', 'extension.read_chat.error', {
617
630
  h,
@@ -626,8 +639,8 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
626
639
  const parentSessionId = h.currentSession?.parentSessionId;
627
640
  if (cdp && parentSessionId) {
628
641
  const stream = await h.agentStream.collectActiveSession(cdp, parentSessionId);
629
- if (stream?.agentType !== provider?.type) {
630
- return buildReadChatCommandResult({ messages: [], status: 'idle' }, args);
642
+ if (stream && stream.agentType !== provider?.type) {
643
+ return { success: false, error: `extension read_chat stream agent mismatch for ${provider?.type || 'unknown_extension'}` };
631
644
  }
632
645
  if (stream) {
633
646
  h.historyWriter.appendNewMessages(
@@ -645,7 +658,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
645
658
  }
646
659
  }
647
660
  }
648
- return buildReadChatCommandResult({ messages: [], status: 'idle' }, args);
661
+ return { success: false, error: extensionReadChatError || 'extension read_chat unavailable' };
649
662
  }
650
663
 
651
664
  // IDE category (default): cdp.evaluate
@@ -655,6 +668,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
655
668
  // webview IDE (Kiro, PearAI) → evaluateInWebviewFrame directly use
656
669
  const webviewScript = h.getProviderScript('webviewReadChat') || h.getProviderScript('webview_read_chat');
657
670
  if (webviewScript) {
671
+ let webviewReadChatError = '';
658
672
  try {
659
673
  const matchText = provider?.webviewMatchText;
660
674
  const matchFn = matchText
@@ -663,37 +677,56 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
663
677
  const raw = await cdp.evaluateInWebviewFrame(webviewScript, matchFn);
664
678
  if (raw) {
665
679
  let parsed: any = raw;
666
- if (typeof parsed === 'string') { try { parsed = JSON.parse(parsed); } catch { } }
680
+ if (typeof parsed === 'string') {
681
+ try {
682
+ parsed = JSON.parse(parsed);
683
+ } catch (e: any) {
684
+ webviewReadChatError = `webview read_chat parse failed: ${e?.message || String(e)}`;
685
+ }
686
+ }
667
687
  if (parsed && typeof parsed === 'object') {
668
688
  const validated = validateReadChatResultPayload(parsed, 'webview read_chat');
669
689
  _log(`Webview OK: ${validated.messages?.length || 0} msgs`);
670
- h.historyWriter.appendNewMessages(
671
- provider?.type || getCurrentProviderType(h, 'unknown_webview'),
672
- toHistoryPersistedMessages(normalizeReadChatMessages(validated)),
673
- validated.title,
674
- args?.targetSessionId,
675
- historySessionId,
676
- );
690
+ h.historyWriter.appendNewMessages(
691
+ provider?.type || getCurrentProviderType(h, 'unknown_webview'),
692
+ toHistoryPersistedMessages(normalizeReadChatMessages(validated)),
693
+ validated.title,
694
+ args?.targetSessionId,
695
+ historySessionId,
696
+ );
677
697
  return buildReadChatCommandResult(validated as Record<string, any>, args);
678
698
  }
699
+ if (!webviewReadChatError) {
700
+ webviewReadChatError = 'webview read_chat returned a non-object payload';
701
+ }
702
+ } else {
703
+ webviewReadChatError = 'webview read_chat returned no payload';
679
704
  }
680
705
  } catch (e: any) {
706
+ webviewReadChatError = `webview read_chat failed: ${e?.message || String(e)}`;
681
707
  _log(`Webview readChat error: ${e.message}`);
682
708
  }
683
- return buildReadChatCommandResult({ messages: [], status: 'idle' }, args);
709
+ return { success: false, error: webviewReadChatError || 'webview read_chat unavailable' };
684
710
  }
685
711
 
686
712
  // Regular IDE (Cursor, Windsurf, Trae etc) → main DOM evaluate
687
713
  const script = h.getProviderScript('readChat') || h.getProviderScript('read_chat');
688
714
  if (script) {
715
+ let ideReadChatError = '';
689
716
  try {
690
717
  const evalResult = await h.evaluateProviderScript('readChat', undefined, READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS);
691
718
  if (evalResult?.result) {
692
719
  let parsed: any = evalResult.result;
693
- if (typeof parsed === 'string') { try { parsed = JSON.parse(parsed); } catch { } }
694
- if (parsed && typeof parsed === 'object' && parsed.messages?.length > 0) {
720
+ if (typeof parsed === 'string') {
721
+ try {
722
+ parsed = JSON.parse(parsed);
723
+ } catch (e: any) {
724
+ ideReadChatError = `ide read_chat parse failed: ${e?.message || String(e)}`;
725
+ }
726
+ }
727
+ if (parsed && typeof parsed === 'object') {
695
728
  const validated = validateReadChatResultPayload(parsed, 'ide read_chat');
696
- _log(`OK: ${validated.messages?.length} msgs`);
729
+ _log(`OK: ${validated.messages?.length || 0} msgs`);
697
730
  traceProviderEvent(args, 'provider', 'ide.read_chat.success', {
698
731
  h,
699
732
  provider,
@@ -713,8 +746,14 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
713
746
  );
714
747
  return buildReadChatCommandResult(validated as Record<string, any>, args);
715
748
  }
749
+ if (!ideReadChatError) {
750
+ ideReadChatError = 'ide read_chat returned a non-object payload';
751
+ }
752
+ } else {
753
+ ideReadChatError = 'ide read_chat returned no payload';
716
754
  }
717
755
  } catch (e: any) {
756
+ ideReadChatError = `ide read_chat failed: ${e?.message || String(e)}`;
718
757
  LOG.info('Command', `[read_chat] Script error: ${e.message}`);
719
758
  traceProviderEvent(args, 'provider', 'ide.read_chat.error', {
720
759
  h,
@@ -723,9 +762,10 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
723
762
  payload: { method: 'evaluate', error: e.message },
724
763
  });
725
764
  }
765
+ return { success: false, error: ideReadChatError || 'ide read_chat unavailable' };
726
766
  }
727
767
 
728
- return buildReadChatCommandResult({ messages: [], status: 'idle' }, args);
768
+ return { success: false, error: 'read_chat unavailable' };
729
769
  }
730
770
 
731
771
  export async function handleSendChat(h: CommandHelpers, args: any): Promise<CommandResult> {
@@ -175,19 +175,18 @@ export function daemonLog(category: string, msg: string, level: LogLevel = 'info
175
175
  const label = LEVEL_LABEL[level];
176
176
  const line = `[${ts()}] [${label}] [${category}] ${msg}`;
177
177
 
178
- // Always record to file (including DEBUG)
178
+ // Apply the active log level consistently to console, file, and the remote ring buffer.
179
+ // Debug hot paths are useful when explicitly enabled, but should not inflate normal-mode logs.
180
+ if (!shouldOutput) return;
181
+
179
182
  writeToFile(line);
180
183
 
181
- // Always save to ring buffer (for remote transmission)
182
184
  ringBuffer.push({ ts: Date.now(), level, category, message: msg });
183
185
  if (ringBuffer.length > RING_BUFFER_SIZE) {
184
186
  ringBuffer.splice(0, ringBuffer.length - RING_BUFFER_SIZE);
185
187
  }
186
188
 
187
- // Apply filter to console output
188
- if (shouldOutput) {
189
- origConsoleLog(line);
190
- }
189
+ origConsoleLog(line);
191
190
  }
192
191
 
193
192
  // ─── Convenience API ────────────────────────────────
@@ -199,6 +199,10 @@ export class ProviderLoader {
199
199
  this.logFn(`[ProviderLoader] ${msg}`);
200
200
  }
201
201
 
202
+ private debugLog(msg: string): void {
203
+ LOG.debug('Provider', `[ProviderLoader] ${msg}`);
204
+ }
205
+
202
206
  // ─── Public API ────────────────────────────────
203
207
 
204
208
  /**
@@ -892,7 +896,7 @@ export class ProviderLoader {
892
896
  const loaded = this.loadScriptsFromDir(type, entry.scriptDir);
893
897
  if (loaded) {
894
898
  resolved.scripts = loaded;
895
- this.log(` [compatibility] ${type} v${currentVersion} → ${entry.scriptDir}`);
899
+ this.debugLog(` [compatibility] ${type} v${currentVersion} → ${entry.scriptDir}`);
896
900
  resolved._resolvedScriptDir = entry.scriptDir;
897
901
  resolved._resolvedScriptsSource = `compatibility:${entry.ideVersion}`;
898
902
  if (providerDir) {
@@ -912,7 +916,7 @@ export class ProviderLoader {
912
916
  const loaded = this.loadScriptsFromDir(type, base.defaultScriptDir);
913
917
  if (loaded) {
914
918
  resolved.scripts = loaded;
915
- this.log(` [compatibility] ${type} v${currentVersion} → default: ${base.defaultScriptDir}`);
919
+ this.debugLog(` [compatibility] ${type} v${currentVersion} → default: ${base.defaultScriptDir}`);
916
920
  resolved._resolvedScriptDir = base.defaultScriptDir;
917
921
  resolved._resolvedScriptsSource = 'defaultScriptDir:version_miss';
918
922
  if (providerDir) {
@@ -955,7 +959,7 @@ export class ProviderLoader {
955
959
  const loaded = this.loadScriptsFromDir(type, base.defaultScriptDir);
956
960
  if (loaded) {
957
961
  resolved.scripts = loaded;
958
- this.log(` [compatibility] ${type} no version detected → default: ${base.defaultScriptDir}`);
962
+ this.debugLog(` [compatibility] ${type} no version detected → default: ${base.defaultScriptDir}`);
959
963
  resolved._resolvedScriptDir = base.defaultScriptDir;
960
964
  resolved._resolvedScriptsSource = 'defaultScriptDir:no_version';
961
965
  if (providerDir) {
@@ -59,6 +59,7 @@ export function classifyHotChatSessionsForSubscriptionFlush(
59
59
  now?: number;
60
60
  recentMessageGraceMs?: number;
61
61
  activeStatuses?: ReadonlySet<string>;
62
+ activeSessionIds?: ReadonlySet<string>;
62
63
  } = {},
63
64
  ): { active: Set<string>; finalizing: Set<string> } {
64
65
  const now = options.now ?? Date.now();
@@ -69,6 +70,7 @@ export function classifyHotChatSessionsForSubscriptionFlush(
69
70
  : DEFAULT_CHAT_TAIL_RECENT_MESSAGE_GRACE_MS,
70
71
  );
71
72
  const activeStatuses = options.activeStatuses ?? DEFAULT_ACTIVE_CHAT_POLL_STATUSES;
73
+ const activeSessionIds = options.activeSessionIds ?? new Set<string>();
72
74
  const active = new Set<string>();
73
75
  const excluded = new Set<string>();
74
76
 
@@ -79,6 +81,10 @@ export function classifyHotChatSessionsForSubscriptionFlush(
79
81
  excluded.add(sessionId);
80
82
  continue;
81
83
  }
84
+ if (activeSessionIds.has(sessionId)) {
85
+ active.add(sessionId);
86
+ continue;
87
+ }
82
88
 
83
89
  const status = String(session?.status || '').toLowerCase();
84
90
  const unread = session?.unread === true;