@adhdev/daemon-core 0.9.82-rc.127 → 0.9.82-rc.129

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.
@@ -97,6 +97,7 @@ export declare class CliProviderInstance implements ProviderInstance {
97
97
  getSessionModalState(): SessionModalState;
98
98
  updateSettings(newSettings: Record<string, any>): void;
99
99
  onEvent(event: string, data?: any): void;
100
+ recordAcknowledgedUserInput(input: InputEnvelope | string): void;
100
101
  dispose(): void;
101
102
  private completedDebounceTimer;
102
103
  private completedDebouncePending;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.127",
3
+ "version": "0.9.82-rc.129",
4
4
  "description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -14,7 +14,7 @@ import { assertProviderSupportsDeclaredInput, assertTextOnlyInput } from '../pro
14
14
  import { validateReadChatResultPayload } from '../providers/read-chat-contract.js';
15
15
  import { pickApprovalButton } from '../providers/approval-utils.js';
16
16
  import type { ProviderInstance } from '../providers/provider-instance.js';
17
- import { isNativeSourceCanonicalHistory, readProviderChatHistory } from '../config/chat-history.js';
17
+ import { isNativeSourceCanonicalHistory, readChatHistory, readProviderChatHistory } from '../config/chat-history.js';
18
18
  import { LOG, getRecentLogs } from '../logging/logger.js';
19
19
  import { getRecentDebugTrace, recordDebugTrace } from '../logging/debug-trace.js';
20
20
  import { buildChatMessageSignature, hashSignatureParts } from '../chat/chat-signatures.js';
@@ -40,6 +40,7 @@ interface ApprovalSelectableInstance extends ProviderInstance {
40
40
 
41
41
  interface RuntimeChatMessageMerger extends ProviderInstance {
42
42
  mergeRuntimeChatMessages?(messages: ChatMessage[]): ChatMessage[];
43
+ recordAcknowledgedUserInput?(input: InputEnvelope | string): void;
43
44
  }
44
45
 
45
46
  type LegacyStringScript = (params?: Record<string, unknown> | string) => string;
@@ -412,9 +413,9 @@ function buildCliMessageSourceProvenance(args: {
412
413
  ptyMessageCount: ptyMessages.length,
413
414
  returnedMessageCount: returnedMessages.length,
414
415
  safeMapping: args.safeMapping === true,
415
- // true when native-history was selected: PTY message bodies are suppressed and
416
- // must not be treated as chat content. PTY only contributes status/approval/screen evidence.
417
- ptyMessagesSuppressed: args.selected === 'native-history',
416
+ // true when PTY message bodies are suppressed and must not be treated as
417
+ // chat content. PTY may still contribute status/approval/screen evidence.
418
+ ptyMessagesSuppressed: args.selected === 'native-history' || args.ptyStatusApprovalOnly === true,
418
419
  },
419
420
  };
420
421
  }
@@ -441,6 +442,58 @@ function buildNativeHistoryFallbackReason(args: {
441
442
  return 'native_history_not_selected';
442
443
  }
443
444
 
445
+ function isUnsafeNativeTranscriptFallback(reason?: string): boolean {
446
+ const value = String(reason || '').trim();
447
+ return value.startsWith('native_history_unavailable')
448
+ || value === 'native_history_not_safely_mapped'
449
+ || value === 'native_history_stale'
450
+ || value === 'native_history_partial';
451
+ }
452
+
453
+ function coerceUnsafeNativeFallbackStatus(status: string, activeModal: unknown): string {
454
+ if (status === 'waiting_approval' && activeModal) return status;
455
+ return 'idle';
456
+ }
457
+
458
+ function isRuntimeInputAckMessage(message: ChatMessage | undefined): boolean {
459
+ if (!message || typeof message !== 'object') return false;
460
+ const role = String((message as any).role || '').trim().toLowerCase();
461
+ if (role !== 'user' && role !== 'human') return false;
462
+ const meta = (message as any).meta;
463
+ return !!meta && typeof meta === 'object' && !Array.isArray(meta) && meta.runtimeInputAck === true;
464
+ }
465
+
466
+ function selectRuntimeInputAckMessages(messages: ChatMessage[]): ChatMessage[] {
467
+ return messages.filter((message) => isRuntimeInputAckMessage(message));
468
+ }
469
+
470
+ function readExactRuntimeMirrorMessages(args: {
471
+ providerType: string;
472
+ targetSessionId?: string;
473
+ currentSessionId?: string;
474
+ tailLimit: number;
475
+ historyBehavior?: ProviderModule['historyBehavior'];
476
+ }): ChatMessage[] {
477
+ const targetSessionId = String(args.targetSessionId || '').trim();
478
+ const currentSessionId = String(args.currentSessionId || '').trim();
479
+ if (!targetSessionId || targetSessionId !== currentSessionId) return [];
480
+
481
+ const history = readChatHistory(
482
+ args.providerType,
483
+ 0,
484
+ Math.max(args.tailLimit || 0, 200),
485
+ targetSessionId,
486
+ 0,
487
+ args.historyBehavior,
488
+ );
489
+ return normalizeChatMessages((history.messages || []) as ChatMessage[])
490
+ .filter((message) => {
491
+ const historySessionId = String((message as any).historySessionId || '').trim();
492
+ const instanceId = String((message as any).instanceId || '').trim();
493
+ return historySessionId === targetSessionId || instanceId === targetSessionId;
494
+ });
495
+ }
496
+
444
497
  function supportsCliNativeTranscript(providerType: string, provider?: ProviderModule): boolean {
445
498
  if (CLI_NATIVE_TRANSCRIPT_PROVIDERS.has(providerType)) return true;
446
499
  return provider?.category === 'cli' && isNativeSourceCanonicalHistory(provider?.canonicalHistory);
@@ -1354,6 +1407,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
1354
1407
  let selectedProviderSessionId = providerSessionId;
1355
1408
  let selectedTranscriptAuthority = transcriptAuthority;
1356
1409
  let selectedCoverage = coverage;
1410
+ let selectedStatus = returnedStatus;
1357
1411
  const sessionWorkspace = typeof (h.currentSession as any)?.workspace === 'string'
1358
1412
  ? (h.currentSession as any).workspace
1359
1413
  : typeof adapter.workingDir === 'string'
@@ -1496,6 +1550,30 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
1496
1550
  safeMapping,
1497
1551
  freshEnough,
1498
1552
  });
1553
+ const unsafeNativeFallback = adapter.cliType === 'codex-cli'
1554
+ && isUnsafeNativeTranscriptFallback(fallbackReason);
1555
+ const safeRuntimeAckMessages = unsafeNativeFallback
1556
+ ? selectRuntimeInputAckMessages(returnedMessages)
1557
+ : [];
1558
+ const exactRuntimeMirrorMessages = unsafeNativeFallback
1559
+ && safeRuntimeAckMessages.length === 0
1560
+ ? readExactRuntimeMirrorMessages({
1561
+ providerType,
1562
+ targetSessionId: typeof args?.targetSessionId === 'string' ? args.targetSessionId : undefined,
1563
+ currentSessionId: typeof (h.currentSession as any)?.sessionId === 'string' ? (h.currentSession as any).sessionId : undefined,
1564
+ tailLimit: nativeHistoryLimit,
1565
+ historyBehavior: provider?.historyBehavior,
1566
+ })
1567
+ : [];
1568
+ const safeDaemonMessages = safeRuntimeAckMessages.length > 0
1569
+ ? safeRuntimeAckMessages
1570
+ : exactRuntimeMirrorMessages;
1571
+ if (unsafeNativeFallback) {
1572
+ selectedMessages = safeDaemonMessages;
1573
+ selectedTranscriptAuthority = safeDaemonMessages.length > 0 ? 'daemon' : undefined;
1574
+ selectedCoverage = safeDaemonMessages.length > 0 ? 'tail' : undefined;
1575
+ selectedStatus = coerceUnsafeNativeFallbackStatus(returnedStatus, activeModal);
1576
+ }
1499
1577
  messageSource = buildCliMessageSourceProvenance({
1500
1578
  selected: 'pty-parser',
1501
1579
  provider: adapter.cliType,
@@ -1512,18 +1590,22 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
1512
1590
  unavailableReason,
1513
1591
  nativeMessages,
1514
1592
  ptyMessages: returnedMessages,
1515
- returnedMessages,
1593
+ returnedMessages: unsafeNativeFallback ? safeDaemonMessages : returnedMessages,
1516
1594
  safeMapping,
1517
1595
  freshEnough,
1518
- ptyStatusApprovalOnly: false,
1596
+ ptyStatusApprovalOnly: unsafeNativeFallback,
1519
1597
  });
1598
+ if (unsafeNativeFallback && exactRuntimeMirrorMessages.length > 0) {
1599
+ (messageSource as any).selectedDaemonSource = 'exact-runtime-mirror';
1600
+ (messageSource as any).transcriptAuthority = 'daemon';
1601
+ }
1520
1602
  }
1521
1603
  }
1522
1604
  }
1523
1605
  LOG.debug('Command', `[read_chat] cli-like parsed provider=${adapter.cliType} target=${String(args?.targetSessionId || '')} adapterStatus=${String(adapterStatus.status || '')} parsedStatus=${String(parsedRecord.status || '')} parsedMsgCount=${parsedRecord.messages.length} returnedMsgCount=${returnedMessages.length}`);
1524
1606
  return buildReadChatCommandResult({
1525
1607
  messages: selectedMessages,
1526
- status: returnedStatus,
1608
+ status: selectedStatus,
1527
1609
  activeModal,
1528
1610
  messageSource,
1529
1611
  transcriptProvenance: messageSource,
@@ -1532,7 +1614,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
1532
1614
  targetSessionId: String(args?.targetSessionId || ''),
1533
1615
  adapterStatus: String(adapterStatus.status || ''),
1534
1616
  parsedStatus: String(parsedRecord.status || ''),
1535
- returnedStatus: String(returnedStatus || ''),
1617
+ returnedStatus: String(selectedStatus || ''),
1536
1618
  selectedMessageSource: (messageSource as any).selected,
1537
1619
  messageSource,
1538
1620
  shouldPreferAdapterMessages: supportsCliNativeTranscript(providerType, provider)
@@ -1541,6 +1623,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
1541
1623
  && typeof (messageSource as any).fallbackReason === 'string'
1542
1624
  && (messageSource as any).fallbackReason.startsWith('native_history_')
1543
1625
  && (messageSource as any).fallbackReason !== 'native_history_not_checked'
1626
+ && !isUnsafeNativeTranscriptFallback((messageSource as any).fallbackReason)
1544
1627
  && !(selectedTranscriptAuthority === 'provider' && selectedCoverage === 'full'),
1545
1628
  parsedMsgCount: parsedRecord.messages.length,
1546
1629
  returnedMsgCount: selectedMessages.length,
@@ -1931,6 +2014,12 @@ export async function handleSendChat(h: CommandHelpers, args: any): Promise<Comm
1931
2014
  } else {
1932
2015
  await adapter.sendMessage(text);
1933
2016
  }
2017
+ const target = getTargetInstance(h, args) as RuntimeChatMessageMerger | null;
2018
+ if (target?.category === 'cli'
2019
+ && target.type === adapter.cliType
2020
+ && typeof target.recordAcknowledgedUserInput === 'function') {
2021
+ target.recordAcknowledgedUserInput(input);
2022
+ }
1934
2023
  return {
1935
2024
  ..._logSendSuccess(`${transport}-adapter`, adapter.cliType),
1936
2025
  ...(forceSend ? { forceSent: true } : {}),
@@ -752,6 +752,34 @@ export class CliProviderInstance implements ProviderInstance {
752
752
  }
753
753
  }
754
754
 
755
+ recordAcknowledgedUserInput(input: InputEnvelope | string): void {
756
+ const content = typeof input === 'string'
757
+ ? input.trim()
758
+ : buildCliStructuredInputPrompt(input).trim();
759
+ if (!content) return;
760
+
761
+ const receivedAt = Date.now();
762
+ const dedupKey = `user_input_ack:${crypto
763
+ .createHash('sha256')
764
+ .update(`${this.instanceId}:${content}:${receivedAt}`)
765
+ .digest('hex')
766
+ .slice(0, 24)}`;
767
+ this.appendRuntimeMessage(buildChatMessage({
768
+ role: 'user',
769
+ senderName: 'User',
770
+ kind: 'standard',
771
+ content,
772
+ receivedAt,
773
+ timestamp: receivedAt,
774
+ source: 'runtime_input_ack',
775
+ meta: {
776
+ runtimeInputAck: true,
777
+ provider: this.type,
778
+ workspace: this.workingDir,
779
+ },
780
+ } as ChatMessage), dedupKey);
781
+ }
782
+
755
783
  dispose(): void {
756
784
  this.adapter.shutdown();
757
785
  this.monitor.reset();
@@ -1450,12 +1478,28 @@ export class CliProviderInstance implements ProviderInstance {
1450
1478
  index,
1451
1479
  source: 'parsed',
1452
1480
  }));
1481
+ const getRole = (message: ChatMessage): string => typeof message.role === 'string'
1482
+ ? message.role.trim().toLowerCase()
1483
+ : '';
1453
1484
  const runtimeEntries: MergeEntry[] = this.runtimeMessages.map((entry, index) => ({
1454
1485
  message: entry.message,
1455
1486
  index: parsedMessages.length + index,
1456
- source: 'runtime',
1487
+ source: 'runtime' as const,
1457
1488
  runtimeKey: entry.key,
1458
- }));
1489
+ })).filter((entry) => {
1490
+ const meta = entry.message.meta && typeof entry.message.meta === 'object' && !Array.isArray(entry.message.meta)
1491
+ ? entry.message.meta as Record<string, unknown>
1492
+ : {};
1493
+ if (meta.runtimeInputAck !== true) return true;
1494
+ const runtimeText = flattenContent(entry.message.content).replace(/\s+/g, ' ').trim();
1495
+ if (!runtimeText) return false;
1496
+ return !parsedEntries.some((parsedEntry) => {
1497
+ const parsedRole = getRole(parsedEntry.message);
1498
+ if (parsedRole !== 'user' && parsedRole !== 'human') return false;
1499
+ const parsedText = flattenContent(parsedEntry.message.content).replace(/\s+/g, ' ').trim();
1500
+ return parsedText === runtimeText;
1501
+ });
1502
+ });
1459
1503
  const getTime = (message: ChatMessage): number => {
1460
1504
  const value = typeof message.receivedAt === 'number'
1461
1505
  ? message.receivedAt
@@ -1465,9 +1509,6 @@ export class CliProviderInstance implements ProviderInstance {
1465
1509
  return Number.isFinite(value) && value > 0 ? value : 0;
1466
1510
  };
1467
1511
 
1468
- const getRole = (message: ChatMessage): string => typeof message.role === 'string'
1469
- ? message.role.trim().toLowerCase()
1470
- : '';
1471
1512
  const isRuntimeOverlay = (entry: MergeEntry): boolean => {
1472
1513
  if (entry.source !== 'runtime') return false;
1473
1514
  const key = typeof entry.runtimeKey === 'string' ? entry.runtimeKey.trim().toLowerCase() : '';