@adhdev/daemon-core 0.9.36 → 0.9.37

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.9.36",
3
+ "version": "0.9.37",
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.36",
3
+ "version": "0.9.37",
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",
@@ -1675,11 +1675,12 @@ export class ProviderCliAdapter implements CliAdapter {
1675
1675
 
1676
1676
  // ─── Public API (CliAdapter) ───────────────────
1677
1677
 
1678
- getStatus(): CliSessionStatus {
1679
- const startupModal = this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
1678
+ getStatus(options: { allowParse?: boolean } = {}): CliSessionStatus {
1679
+ const allowParse = options.allowParse !== false;
1680
+ const startupModal = allowParse && this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
1680
1681
  let effectiveStatus = this.projectEffectiveStatus(startupModal);
1681
1682
  let effectiveModal = startupModal || this.activeModal;
1682
- if (!startupModal && !effectiveModal && typeof this.cliScripts?.parseOutput === 'function') {
1683
+ if (allowParse && !startupModal && !effectiveModal && typeof this.cliScripts?.parseOutput === 'function') {
1683
1684
  let parsed = this.getFreshParsedStatusCache();
1684
1685
  if (!parsed && effectiveStatus !== 'idle') {
1685
1686
  const now = Date.now();
@@ -31,37 +31,115 @@ export function hydrateCliParsedMessages(
31
31
  ): any[] {
32
32
  const { committedMessages, scope, lastOutputAt } = options;
33
33
  const referenceMessages = [...committedMessages];
34
- const referenceComparables = referenceMessages.map((message) => normalizeComparableMessageContent(message?.content || ''));
34
+ const referenceComparables: Array<string | undefined> = new Array(referenceMessages.length);
35
35
  const usedReferenceIndexes = new Set<number>();
36
36
  const now = options.now ?? Date.now();
37
+ let exactReferenceIndexesByKey: Map<string, number[]> | null = null;
38
+ const exactReferenceCursorByKey = new Map<string, number>();
39
+
40
+ const hasFiniteTimestamp = (message: any): message is { timestamp: number } => (
41
+ typeof message?.timestamp === 'number' && Number.isFinite(message.timestamp)
42
+ );
43
+
44
+ const getReferenceComparable = (index: number): string => {
45
+ if (typeof referenceComparables[index] === 'string') return referenceComparables[index] || '';
46
+ const comparable = normalizeComparableMessageContent(referenceMessages[index]?.content || '');
47
+ referenceComparables[index] = comparable;
48
+ return comparable;
49
+ };
50
+
51
+ const messagesShareStableIdentity = (parsed: any, reference: any): boolean => {
52
+ if (!parsed || !reference) return false;
53
+ const parsedId = typeof parsed.id === 'string' ? parsed.id.trim() : '';
54
+ const referenceId = typeof reference.id === 'string' ? reference.id.trim() : '';
55
+ if (parsedId && referenceId && parsedId === referenceId) return true;
56
+ return typeof parsed.index === 'number'
57
+ && Number.isFinite(parsed.index)
58
+ && typeof reference.index === 'number'
59
+ && Number.isFinite(reference.index)
60
+ && parsed.index === reference.index;
61
+ };
62
+
63
+ const exactReferenceKey = (role: 'user' | 'assistant', comparable: string): string => `${role}\u0000${comparable}`;
64
+
65
+ const ensureExactReferenceIndex = (): Map<string, number[]> => {
66
+ if (exactReferenceIndexesByKey) return exactReferenceIndexesByKey;
67
+ const byKey = new Map<string, number[]>();
68
+ for (let i = 0; i < referenceMessages.length; i++) {
69
+ const candidate = referenceMessages[i];
70
+ if (!candidate || (candidate.role !== 'user' && candidate.role !== 'assistant') || !hasFiniteTimestamp(candidate)) continue;
71
+ const comparable = getReferenceComparable(i);
72
+ if (!comparable) continue;
73
+ const key = exactReferenceKey(candidate.role, comparable);
74
+ const indexes = byKey.get(key);
75
+ if (indexes) {
76
+ indexes.push(i);
77
+ } else {
78
+ byKey.set(key, [i]);
79
+ }
80
+ }
81
+ exactReferenceIndexesByKey = byKey;
82
+ return byKey;
83
+ };
84
+
85
+ const takeExactReferenceTimestamp = (role: 'user' | 'assistant', normalizedContent: string): number | undefined => {
86
+ const key = exactReferenceKey(role, normalizedContent);
87
+ const indexes = ensureExactReferenceIndex().get(key);
88
+ if (!indexes) return undefined;
89
+ let cursor = exactReferenceCursorByKey.get(key) || 0;
90
+ while (cursor < indexes.length) {
91
+ const candidateIndex = indexes[cursor];
92
+ cursor += 1;
93
+ if (usedReferenceIndexes.has(candidateIndex)) continue;
94
+ const candidate = referenceMessages[candidateIndex];
95
+ if (!candidate || candidate.role !== role || !hasFiniteTimestamp(candidate)) continue;
96
+ usedReferenceIndexes.add(candidateIndex);
97
+ exactReferenceCursorByKey.set(key, cursor);
98
+ return candidate.timestamp;
99
+ }
100
+ exactReferenceCursorByKey.set(key, cursor);
101
+ return undefined;
102
+ };
103
+
104
+ const findReferenceTimestamp = (message: any, role: 'user' | 'assistant', content: string, parsedIndex: number): number | undefined => {
105
+ const sameIndex = referenceMessages[parsedIndex];
106
+ if (
107
+ sameIndex
108
+ && !usedReferenceIndexes.has(parsedIndex)
109
+ && sameIndex.role === role
110
+ && hasFiniteTimestamp(sameIndex)
111
+ && messagesShareStableIdentity(message, sameIndex)
112
+ ) {
113
+ usedReferenceIndexes.add(parsedIndex);
114
+ return sameIndex.timestamp;
115
+ }
37
116
 
38
- const findReferenceTimestamp = (role: 'user' | 'assistant', content: string, parsedIndex: number): number | undefined => {
39
117
  const normalizedContent = normalizeComparableMessageContent(content);
40
118
  if (!normalizedContent) return undefined;
41
119
 
42
- const sameIndex = referenceMessages[parsedIndex];
43
120
  if (
44
121
  sameIndex
45
122
  && !usedReferenceIndexes.has(parsedIndex)
46
123
  && sameIndex.role === role
47
- && referenceComparables[parsedIndex] === normalizedContent
48
- && typeof sameIndex.timestamp === 'number'
49
- && Number.isFinite(sameIndex.timestamp)
124
+ && getReferenceComparable(parsedIndex) === normalizedContent
125
+ && hasFiniteTimestamp(sameIndex)
50
126
  ) {
51
127
  usedReferenceIndexes.add(parsedIndex);
52
128
  return sameIndex.timestamp;
53
129
  }
54
130
 
131
+ const exactTimestamp = takeExactReferenceTimestamp(role, normalizedContent);
132
+ if (typeof exactTimestamp === 'number') return exactTimestamp;
133
+
55
134
  for (let i = 0; i < referenceMessages.length; i++) {
56
135
  if (usedReferenceIndexes.has(i)) continue;
57
136
  const candidate = referenceMessages[i];
58
137
  if (!candidate || candidate.role !== role) continue;
59
- const candidateContent = referenceComparables[i];
138
+ const candidateContent = getReferenceComparable(i);
60
139
  if (!candidateContent) continue;
61
- const exactMatch = candidateContent === normalizedContent;
62
140
  const fuzzyMatch = candidateContent.includes(normalizedContent) || normalizedContent.includes(candidateContent);
63
- if (!exactMatch && !fuzzyMatch) continue;
64
- if (typeof candidate.timestamp === 'number' && Number.isFinite(candidate.timestamp)) {
141
+ if (!fuzzyMatch) continue;
142
+ if (hasFiniteTimestamp(candidate)) {
65
143
  usedReferenceIndexes.add(i);
66
144
  return candidate.timestamp;
67
145
  }
@@ -78,7 +156,7 @@ export function hydrateCliParsedMessages(
78
156
  const parsedTimestamp = typeof message.timestamp === 'number' && Number.isFinite(message.timestamp)
79
157
  ? message.timestamp
80
158
  : undefined;
81
- const referenceTimestamp = parsedTimestamp ?? findReferenceTimestamp(role, content, index);
159
+ const referenceTimestamp = parsedTimestamp ?? findReferenceTimestamp(message, role, content, index);
82
160
  const fallbackTimestamp = role === 'user'
83
161
  ? (scope?.startedAt || now)
84
162
  : (lastOutputAt || scope?.startedAt || now);
@@ -494,7 +494,7 @@ export class CliProviderInstance implements ProviderInstance {
494
494
  }
495
495
 
496
496
  getHotChatSessionState(): HotChatSessionState {
497
- const adapterStatus = this.adapter.getStatus();
497
+ const adapterStatus = this.adapter.getStatus({ allowParse: false });
498
498
  const autoApproveActive = adapterStatus.status === 'waiting_approval' && this.shouldAutoApprove();
499
499
  const visibleStatus = autoApproveActive ? 'generating' : adapterStatus.status;
500
500
  const runtime = this.adapter.getRuntimeMetadata();