@adhdev/daemon-core 0.9.31 → 0.9.33

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
  */
@@ -220,6 +221,7 @@ export declare class ProviderLoader {
220
221
  getMachineProviderConfig(type: string): MachineProviderConfig;
221
222
  setMachineProviderConfig(type: string, patch: Partial<MachineProviderConfig>): boolean;
222
223
  setMachineProviderEnabled(type: string, enabled: boolean): boolean;
224
+ private getEffectiveProviderAvailability;
223
225
  getMachineProviderStatus(type: string): ProviderMachineStatus;
224
226
  getSpawnArgs(type: string, fallback?: string[]): string[];
225
227
  private parseArgsSetting;
@@ -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.31",
3
+ "version": "0.9.33",
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.31",
3
+ "version": "0.9.33",
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;
@@ -297,6 +297,36 @@ function toHistoryPersistedMessages(messages: ChatMessage[]): Array<{
297
297
  }));
298
298
  }
299
299
 
300
+ function findLastMessageIndexBySignature(messages: ChatMessage[], signature: string): number {
301
+ if (!signature) return -1;
302
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
303
+ if (getChatMessageSignature(messages[index]) === signature) {
304
+ return index;
305
+ }
306
+ }
307
+ return -1;
308
+ }
309
+
310
+ function buildBoundedTailSync(messages: ChatMessage[], cursor: Required<ReadChatCursor>): {
311
+ syncMode: ReadChatSyncMode;
312
+ replaceFrom: number;
313
+ messages: ChatMessage[];
314
+ totalMessages: number;
315
+ lastMessageSignature: string;
316
+ } {
317
+ const totalMessages = messages.length;
318
+ const tailMessages = cursor.tailLimit > 0 && totalMessages > cursor.tailLimit
319
+ ? messages.slice(-cursor.tailLimit)
320
+ : messages;
321
+ return {
322
+ syncMode: 'full',
323
+ replaceFrom: 0,
324
+ messages: tailMessages,
325
+ totalMessages,
326
+ lastMessageSignature: getChatMessageSignature(messages[totalMessages - 1]),
327
+ };
328
+ }
329
+
300
330
  function computeReadChatSync(messages: ChatMessage[], cursor: Required<ReadChatCursor>): {
301
331
  syncMode: ReadChatSyncMode;
302
332
  replaceFrom: number;
@@ -338,6 +368,16 @@ function computeReadChatSync(messages: ChatMessage[], cursor: Required<ReadChatC
338
368
  };
339
369
  }
340
370
 
371
+ if (cursor.tailLimit > 0 && knownSignature === lastMessageSignature) {
372
+ return {
373
+ syncMode: 'noop',
374
+ replaceFrom: totalMessages,
375
+ messages: [],
376
+ totalMessages,
377
+ lastMessageSignature,
378
+ };
379
+ }
380
+
341
381
  if (knownMessageCount < totalMessages) {
342
382
  const anchorSignature = getChatMessageSignature(messages[knownMessageCount - 1]);
343
383
  if (anchorSignature === knownSignature) {
@@ -349,6 +389,20 @@ function computeReadChatSync(messages: ChatMessage[], cursor: Required<ReadChatC
349
389
  lastMessageSignature,
350
390
  };
351
391
  }
392
+
393
+ if (cursor.tailLimit > 0) {
394
+ const signatureIndex = findLastMessageIndexBySignature(messages, knownSignature);
395
+ if (signatureIndex >= 0) {
396
+ return {
397
+ syncMode: 'append',
398
+ replaceFrom: knownMessageCount,
399
+ messages: messages.slice(signatureIndex + 1),
400
+ totalMessages,
401
+ lastMessageSignature,
402
+ };
403
+ }
404
+ return buildBoundedTailSync(messages, cursor);
405
+ }
352
406
  }
353
407
 
354
408
  const replaceFrom = Math.max(0, Math.min(knownMessageCount - 1, totalMessages));
@@ -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
  /**
@@ -649,10 +653,26 @@ export class ProviderLoader {
649
653
  return this.setMachineProviderConfig(type, { enabled });
650
654
  }
651
655
 
656
+ private getEffectiveProviderAvailability(type: string): ProviderAvailabilityState | undefined {
657
+ const providerType = this.resolveAlias(type);
658
+ const availability = this.providerAvailability.get(providerType);
659
+ if (availability) return availability;
660
+
661
+ const machineConfig = this.getMachineProviderConfig(providerType);
662
+ const lastDetection = machineConfig.lastDetection;
663
+ if (!lastDetection) return undefined;
664
+ return {
665
+ installed: lastDetection.ok === true,
666
+ detectedPath: typeof lastDetection.path === 'string' && lastDetection.path.trim()
667
+ ? lastDetection.path.trim()
668
+ : null,
669
+ };
670
+ }
671
+
652
672
  getMachineProviderStatus(type: string): ProviderMachineStatus {
653
673
  const providerType = this.resolveAlias(type);
654
674
  if (!this.isMachineProviderEnabled(providerType)) return 'disabled';
655
- const availability = this.providerAvailability.get(providerType);
675
+ const availability = this.getEffectiveProviderAvailability(providerType);
656
676
  if (!availability) return 'enabled_unchecked';
657
677
  return availability.installed ? 'detected' : 'not_detected';
658
678
  }
@@ -788,7 +808,7 @@ export class ProviderLoader {
788
808
 
789
809
  getAvailableProviderInfos(): Array<ProviderModule & { installed?: boolean; detectedPath?: string | null; enabled: boolean; machineStatus: ProviderMachineStatus; lastDetection?: MachineProviderCheckResult; lastVerification?: MachineProviderCheckResult }> {
790
810
  return this.getAll().map((provider) => {
791
- const availability = this.providerAvailability.get(provider.type);
811
+ const availability = this.getEffectiveProviderAvailability(provider.type);
792
812
  const enabled = this.isMachineProviderEnabled(provider.type);
793
813
  const machineConfig = this.getMachineProviderConfig(provider.type);
794
814
  return {
@@ -892,7 +912,7 @@ export class ProviderLoader {
892
912
  const loaded = this.loadScriptsFromDir(type, entry.scriptDir);
893
913
  if (loaded) {
894
914
  resolved.scripts = loaded;
895
- this.log(` [compatibility] ${type} v${currentVersion} → ${entry.scriptDir}`);
915
+ this.debugLog(` [compatibility] ${type} v${currentVersion} → ${entry.scriptDir}`);
896
916
  resolved._resolvedScriptDir = entry.scriptDir;
897
917
  resolved._resolvedScriptsSource = `compatibility:${entry.ideVersion}`;
898
918
  if (providerDir) {
@@ -912,7 +932,7 @@ export class ProviderLoader {
912
932
  const loaded = this.loadScriptsFromDir(type, base.defaultScriptDir);
913
933
  if (loaded) {
914
934
  resolved.scripts = loaded;
915
- this.log(` [compatibility] ${type} v${currentVersion} → default: ${base.defaultScriptDir}`);
935
+ this.debugLog(` [compatibility] ${type} v${currentVersion} → default: ${base.defaultScriptDir}`);
916
936
  resolved._resolvedScriptDir = base.defaultScriptDir;
917
937
  resolved._resolvedScriptsSource = 'defaultScriptDir:version_miss';
918
938
  if (providerDir) {
@@ -955,7 +975,7 @@ export class ProviderLoader {
955
975
  const loaded = this.loadScriptsFromDir(type, base.defaultScriptDir);
956
976
  if (loaded) {
957
977
  resolved.scripts = loaded;
958
- this.log(` [compatibility] ${type} no version detected → default: ${base.defaultScriptDir}`);
978
+ this.debugLog(` [compatibility] ${type} no version detected → default: ${base.defaultScriptDir}`);
959
979
  resolved._resolvedScriptDir = base.defaultScriptDir;
960
980
  resolved._resolvedScriptsSource = 'defaultScriptDir:no_version';
961
981
  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;