@adhdev/daemon-core 0.9.36 → 0.9.38
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.
- package/dist/cli-adapters/provider-cli-adapter.d.ts +3 -1
- package/dist/index.js +94 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +94 -25
- package/dist/index.mjs.map +1 -1
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +4 -3
- package/src/cli-adapters/provider-cli-parse.ts +89 -11
- package/src/providers/cli-provider-instance.ts +37 -16
package/package.json
CHANGED
|
@@ -1675,11 +1675,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1675
1675
|
|
|
1676
1676
|
// ─── Public API (CliAdapter) ───────────────────
|
|
1677
1677
|
|
|
1678
|
-
getStatus(): CliSessionStatus {
|
|
1679
|
-
const
|
|
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
|
|
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
|
-
&&
|
|
48
|
-
&&
|
|
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 =
|
|
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 (!
|
|
64
|
-
if (
|
|
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);
|
|
@@ -48,6 +48,13 @@ function buildPersistableCliHistorySignature(message: PersistableCliHistoryMessa
|
|
|
48
48
|
].join('|');
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
function hasSamePersistableCliHistoryIdentity(a: PersistableCliHistoryMessage, b: PersistableCliHistoryMessage): boolean {
|
|
52
|
+
return String(a?.role || '') === String(b?.role || '')
|
|
53
|
+
&& String(a?.kind || '') === String(b?.kind || '')
|
|
54
|
+
&& String(a?.senderName || '') === String(b?.senderName || '')
|
|
55
|
+
&& String(a?.content || '') === String(b?.content || '');
|
|
56
|
+
}
|
|
57
|
+
|
|
51
58
|
export function buildIncrementalHistoryAppendMessages(
|
|
52
59
|
previousMessages: PersistableCliHistoryMessage[],
|
|
53
60
|
currentMessages: PersistableCliHistoryMessage[],
|
|
@@ -55,20 +62,32 @@ export function buildIncrementalHistoryAppendMessages(
|
|
|
55
62
|
if (!Array.isArray(currentMessages) || currentMessages.length === 0) return [];
|
|
56
63
|
if (!Array.isArray(previousMessages) || previousMessages.length === 0) return currentMessages;
|
|
57
64
|
|
|
58
|
-
const
|
|
59
|
-
const currentSignatures = currentMessages.map(buildPersistableCliHistorySignature);
|
|
60
|
-
|
|
65
|
+
const comparableLength = Math.min(previousMessages.length, currentMessages.length);
|
|
61
66
|
let sharedPrefixLength = 0;
|
|
62
67
|
while (
|
|
63
|
-
sharedPrefixLength <
|
|
64
|
-
&& sharedPrefixLength
|
|
65
|
-
|
|
68
|
+
sharedPrefixLength < comparableLength
|
|
69
|
+
&& hasSamePersistableCliHistoryIdentity(previousMessages[sharedPrefixLength], currentMessages[sharedPrefixLength])
|
|
70
|
+
) {
|
|
71
|
+
sharedPrefixLength += 1;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (sharedPrefixLength === currentMessages.length) return [];
|
|
75
|
+
if (sharedPrefixLength === previousMessages.length) return currentMessages.slice(sharedPrefixLength);
|
|
76
|
+
|
|
77
|
+
// Rare fallback: preserve the older whitespace-normalized behavior only when
|
|
78
|
+
// the cheap identity check detects a changed prefix. Recomputing normalized
|
|
79
|
+
// signatures for the full transcript on every idle status poll was a CPU
|
|
80
|
+
// hot path for long CLI sessions.
|
|
81
|
+
while (
|
|
82
|
+
sharedPrefixLength < comparableLength
|
|
83
|
+
&& buildPersistableCliHistorySignature(previousMessages[sharedPrefixLength])
|
|
84
|
+
=== buildPersistableCliHistorySignature(currentMessages[sharedPrefixLength])
|
|
66
85
|
) {
|
|
67
86
|
sharedPrefixLength += 1;
|
|
68
87
|
}
|
|
69
88
|
|
|
70
|
-
if (sharedPrefixLength ===
|
|
71
|
-
if (sharedPrefixLength ===
|
|
89
|
+
if (sharedPrefixLength === currentMessages.length) return [];
|
|
90
|
+
if (sharedPrefixLength === previousMessages.length) return currentMessages.slice(sharedPrefixLength);
|
|
72
91
|
return currentMessages;
|
|
73
92
|
}
|
|
74
93
|
|
|
@@ -418,13 +437,15 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
418
437
|
}));
|
|
419
438
|
if (!canonicalBackedHistory && !shouldSkipReplayPersist && normalizedMessagesToSave.length > 0) {
|
|
420
439
|
const incrementalMessages = buildIncrementalHistoryAppendMessages(this.lastPersistedHistoryMessages, normalizedMessagesToSave);
|
|
421
|
-
|
|
422
|
-
this.
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
440
|
+
if (incrementalMessages.length > 0) {
|
|
441
|
+
this.historyWriter.appendNewMessages(
|
|
442
|
+
this.type,
|
|
443
|
+
incrementalMessages,
|
|
444
|
+
parsedStatus?.title || dirName,
|
|
445
|
+
this.instanceId,
|
|
446
|
+
this.providerSessionId,
|
|
447
|
+
);
|
|
448
|
+
}
|
|
428
449
|
}
|
|
429
450
|
if (!canonicalBackedHistory) {
|
|
430
451
|
this.lastPersistedHistoryMessages = normalizedMessagesToSave;
|
|
@@ -494,7 +515,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
494
515
|
}
|
|
495
516
|
|
|
496
517
|
getHotChatSessionState(): HotChatSessionState {
|
|
497
|
-
const adapterStatus = this.adapter.getStatus();
|
|
518
|
+
const adapterStatus = this.adapter.getStatus({ allowParse: false });
|
|
498
519
|
const autoApproveActive = adapterStatus.status === 'waiting_approval' && this.shouldAutoApprove();
|
|
499
520
|
const visibleStatus = autoApproveActive ? 'generating' : adapterStatus.status;
|
|
500
521
|
const runtime = this.adapter.getRuntimeMetadata();
|