@adhdev/daemon-core 0.9.35 → 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.
- package/dist/cli-adapters/provider-cli-adapter.d.ts +8 -1
- package/dist/index.js +161 -35
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +161 -35
- 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 +112 -35
- package/src/cli-adapters/provider-cli-parse.ts +89 -11
- package/src/providers/cli-provider-instance.ts +1 -1
package/package.json
CHANGED
|
@@ -369,7 +369,17 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
369
369
|
return baseMessages.slice(-ProviderCliAdapter.PARSE_MESSAGE_TAIL_LIMIT);
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
+
private messagesShareStableIdentity(left: any, right: any): boolean {
|
|
373
|
+
if (left === right) return true;
|
|
374
|
+
if (!left || !right) return false;
|
|
375
|
+
if ((left.role || '') !== (right.role || '')) return false;
|
|
376
|
+
if (left.id && right.id && String(left.id) === String(right.id)) return true;
|
|
377
|
+
if (typeof left.index === 'number' && typeof right.index === 'number' && left.index === right.index) return true;
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
|
|
372
381
|
private messagesComparable(left: any, right: any): boolean {
|
|
382
|
+
if (this.messagesShareStableIdentity(left, right)) return true;
|
|
373
383
|
if (!left || !right) return false;
|
|
374
384
|
if ((left.role || '') !== (right.role || '')) return false;
|
|
375
385
|
const leftText = normalizeComparableTranscriptText(left.content);
|
|
@@ -1665,11 +1675,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1665
1675
|
|
|
1666
1676
|
// ─── Public API (CliAdapter) ───────────────────
|
|
1667
1677
|
|
|
1668
|
-
getStatus(): CliSessionStatus {
|
|
1669
|
-
const
|
|
1678
|
+
getStatus(options: { allowParse?: boolean } = {}): CliSessionStatus {
|
|
1679
|
+
const allowParse = options.allowParse !== false;
|
|
1680
|
+
const startupModal = allowParse && this.startupParseGate ? this.runParseApproval(this.recentOutputBuffer) : null;
|
|
1670
1681
|
let effectiveStatus = this.projectEffectiveStatus(startupModal);
|
|
1671
1682
|
let effectiveModal = startupModal || this.activeModal;
|
|
1672
|
-
if (!startupModal && !effectiveModal && typeof this.cliScripts?.parseOutput === 'function') {
|
|
1683
|
+
if (allowParse && !startupModal && !effectiveModal && typeof this.cliScripts?.parseOutput === 'function') {
|
|
1673
1684
|
let parsed = this.getFreshParsedStatusCache();
|
|
1674
1685
|
if (!parsed && effectiveStatus !== 'idle') {
|
|
1675
1686
|
const now = Date.now();
|
|
@@ -1723,6 +1734,82 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1723
1734
|
this.syncMessageViews();
|
|
1724
1735
|
}
|
|
1725
1736
|
|
|
1737
|
+
private getSharedCommittedPrefixLength(parsedMessages: any[]): number {
|
|
1738
|
+
const committedMessages = this.committedMessages;
|
|
1739
|
+
const max = Math.min(parsedMessages.length, committedMessages.length);
|
|
1740
|
+
let index = 0;
|
|
1741
|
+
while (index < max && this.messagesShareStableIdentity(parsedMessages[index], committedMessages[index])) {
|
|
1742
|
+
index += 1;
|
|
1743
|
+
}
|
|
1744
|
+
return index;
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
private hydrateCommittedPrefixForParsedStatus(parsedMessages: any[]): any[] | null {
|
|
1748
|
+
const sharedPrefixLength = this.getSharedCommittedPrefixLength(parsedMessages);
|
|
1749
|
+
if (sharedPrefixLength !== this.committedMessages.length) return null;
|
|
1750
|
+
|
|
1751
|
+
const committedHydratedMessages = this.committedMessages.map((message, index) => {
|
|
1752
|
+
const timestamp = typeof message.timestamp === 'number' && Number.isFinite(message.timestamp)
|
|
1753
|
+
? message.timestamp
|
|
1754
|
+
: (this.lastOutputAt || this.currentTurnScope?.startedAt || Date.now());
|
|
1755
|
+
const contentValue = message.content;
|
|
1756
|
+
return {
|
|
1757
|
+
role: message.role,
|
|
1758
|
+
content: typeof contentValue === 'string' ? contentValue : String(contentValue || ''),
|
|
1759
|
+
timestamp,
|
|
1760
|
+
receivedAt: typeof message.receivedAt === 'number' && Number.isFinite(message.receivedAt)
|
|
1761
|
+
? message.receivedAt
|
|
1762
|
+
: timestamp,
|
|
1763
|
+
kind: message.kind,
|
|
1764
|
+
id: message.id || `msg_${index}`,
|
|
1765
|
+
index: typeof message.index === 'number' ? message.index : index,
|
|
1766
|
+
meta: message.meta,
|
|
1767
|
+
senderName: message.senderName,
|
|
1768
|
+
};
|
|
1769
|
+
});
|
|
1770
|
+
const extraMessages = parsedMessages.slice(sharedPrefixLength);
|
|
1771
|
+
if (extraMessages.length === 0) return committedHydratedMessages;
|
|
1772
|
+
|
|
1773
|
+
const extraHydratedMessages = hydrateCliParsedMessages(extraMessages, {
|
|
1774
|
+
committedMessages: [],
|
|
1775
|
+
scope: this.currentTurnScope,
|
|
1776
|
+
lastOutputAt: this.lastOutputAt,
|
|
1777
|
+
}).map((message, offset) => ({
|
|
1778
|
+
...message,
|
|
1779
|
+
id: message.id || `msg_${sharedPrefixLength + offset}`,
|
|
1780
|
+
index: typeof message.index === 'number' ? message.index : sharedPrefixLength + offset,
|
|
1781
|
+
}));
|
|
1782
|
+
return [...committedHydratedMessages, ...extraHydratedMessages];
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
private hydrateParsedMessagesForStatus(parsedMessages: any[]): any[] {
|
|
1786
|
+
return this.hydrateCommittedPrefixForParsedStatus(parsedMessages)
|
|
1787
|
+
|| hydrateCliParsedMessages(parsedMessages, {
|
|
1788
|
+
committedMessages: this.committedMessages,
|
|
1789
|
+
scope: this.currentTurnScope,
|
|
1790
|
+
lastOutputAt: this.lastOutputAt,
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
private buildCommittedChatMessages(): any[] {
|
|
1795
|
+
return this.committedMessages.map((message, index) => {
|
|
1796
|
+
const contentValue = message.content;
|
|
1797
|
+
return buildChatMessage({
|
|
1798
|
+
role: message.role,
|
|
1799
|
+
content: typeof contentValue === 'string' ? contentValue : String(contentValue || ''),
|
|
1800
|
+
timestamp: message.timestamp,
|
|
1801
|
+
kind: message.kind,
|
|
1802
|
+
meta: message.meta,
|
|
1803
|
+
senderName: message.senderName,
|
|
1804
|
+
id: message.id || `msg_${index}`,
|
|
1805
|
+
index: typeof message.index === 'number' ? message.index : index,
|
|
1806
|
+
receivedAt: typeof message.receivedAt === 'number'
|
|
1807
|
+
? message.receivedAt
|
|
1808
|
+
: message.timestamp,
|
|
1809
|
+
});
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1726
1813
|
/**
|
|
1727
1814
|
* Script-based full parse — returns ReadChatResult.
|
|
1728
1815
|
* Called by command handler / dashboard for rich content rendering.
|
|
@@ -1765,7 +1852,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1765
1852
|
this.onStatusChange?.();
|
|
1766
1853
|
}
|
|
1767
1854
|
}
|
|
1768
|
-
if (parsed && Array.isArray(parsed.messages)) {
|
|
1855
|
+
if (parsed && Array.isArray(parsed.messages) && this.provider.allowInputDuringGeneration === true) {
|
|
1769
1856
|
const hydratedForCommit = normalizeCliParsedMessages(parsed.messages, {
|
|
1770
1857
|
committedMessages: this.committedMessages,
|
|
1771
1858
|
scope: this.currentTurnScope,
|
|
@@ -1787,25 +1874,13 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1787
1874
|
&& this.currentStatus === 'idle';
|
|
1788
1875
|
let result: any;
|
|
1789
1876
|
if (parsed && Array.isArray(parsed.messages)) {
|
|
1790
|
-
const parsedHydratedMessages =
|
|
1791
|
-
committedMessages: this.committedMessages,
|
|
1792
|
-
scope: this.currentTurnScope,
|
|
1793
|
-
lastOutputAt: this.lastOutputAt,
|
|
1794
|
-
});
|
|
1795
|
-
const committedHydratedMessages = this.committedMessages.map((message, index) => buildChatMessage({
|
|
1796
|
-
...message,
|
|
1797
|
-
id: message.id || `msg_${index}`,
|
|
1798
|
-
index: typeof message.index === 'number' ? message.index : index,
|
|
1799
|
-
receivedAt: typeof message.receivedAt === 'number'
|
|
1800
|
-
? message.receivedAt
|
|
1801
|
-
: message.timestamp,
|
|
1802
|
-
}));
|
|
1877
|
+
const parsedHydratedMessages = this.hydrateParsedMessagesForStatus(parsed.messages);
|
|
1803
1878
|
const parsedLastAssistant = [...parsedHydratedMessages].reverse().find((message) => message.role === 'assistant' && typeof message.content === 'string' && message.content.trim());
|
|
1804
1879
|
const shouldAdoptParsedIdleReplay =
|
|
1805
1880
|
!this.currentTurnScope
|
|
1806
1881
|
&& !this.activeModal
|
|
1807
1882
|
&& !!parsedLastAssistant
|
|
1808
|
-
&& parsedTranscriptIsRicherThanCommitted(parsedHydratedMessages,
|
|
1883
|
+
&& parsedTranscriptIsRicherThanCommitted(parsedHydratedMessages, this.committedMessages)
|
|
1809
1884
|
&& (
|
|
1810
1885
|
this.currentStatus === 'idle'
|
|
1811
1886
|
|| (
|
|
@@ -1816,11 +1891,23 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1816
1891
|
)
|
|
1817
1892
|
);
|
|
1818
1893
|
if (shouldAdoptParsedIdleReplay) {
|
|
1819
|
-
this.committedMessages =
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1894
|
+
this.committedMessages = this.getSharedCommittedPrefixLength(parsed.messages) === this.committedMessages.length
|
|
1895
|
+
? parsedHydratedMessages.map((message) => ({
|
|
1896
|
+
role: message.role,
|
|
1897
|
+
content: typeof message.content === 'string' ? message.content : String(message.content || ''),
|
|
1898
|
+
timestamp: message.timestamp,
|
|
1899
|
+
receivedAt: message.receivedAt,
|
|
1900
|
+
kind: message.kind,
|
|
1901
|
+
id: message.id,
|
|
1902
|
+
index: message.index,
|
|
1903
|
+
meta: message.meta,
|
|
1904
|
+
senderName: message.senderName,
|
|
1905
|
+
}))
|
|
1906
|
+
: normalizeCliParsedMessages(parsed.messages, {
|
|
1907
|
+
committedMessages: this.committedMessages,
|
|
1908
|
+
scope: this.currentTurnScope,
|
|
1909
|
+
lastOutputAt: this.lastOutputAt,
|
|
1910
|
+
});
|
|
1824
1911
|
this.syncMessageViews();
|
|
1825
1912
|
if (this.currentStatus !== 'idle' || this.isWaitingForResponse) {
|
|
1826
1913
|
this.responseBuffer = '';
|
|
@@ -1835,25 +1922,15 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1835
1922
|
this.onStatusChange?.();
|
|
1836
1923
|
}
|
|
1837
1924
|
}
|
|
1838
|
-
const effectiveCommittedHydratedMessages = shouldAdoptParsedIdleReplay
|
|
1839
|
-
? this.committedMessages.map((message, index) => buildChatMessage({
|
|
1840
|
-
...message,
|
|
1841
|
-
id: message.id || `msg_${index}`,
|
|
1842
|
-
index: typeof message.index === 'number' ? message.index : index,
|
|
1843
|
-
receivedAt: typeof message.receivedAt === 'number'
|
|
1844
|
-
? message.receivedAt
|
|
1845
|
-
: message.timestamp,
|
|
1846
|
-
}))
|
|
1847
|
-
: committedHydratedMessages;
|
|
1848
1925
|
const shouldPreferCommittedHistoryReplay =
|
|
1849
1926
|
!this.currentTurnScope
|
|
1850
1927
|
&& !this.activeModal
|
|
1851
|
-
&&
|
|
1928
|
+
&& this.committedMessages.length > parsedHydratedMessages.length;
|
|
1852
1929
|
const shouldPreferCommittedIdleReplay =
|
|
1853
1930
|
shouldPreferCommittedMessages
|
|
1854
1931
|
&& !shouldAdoptParsedIdleReplay;
|
|
1855
1932
|
const hydratedMessages = (shouldPreferCommittedIdleReplay || shouldPreferCommittedHistoryReplay)
|
|
1856
|
-
?
|
|
1933
|
+
? this.buildCommittedChatMessages()
|
|
1857
1934
|
: parsedHydratedMessages;
|
|
1858
1935
|
result = {
|
|
1859
1936
|
id: parsed.id || 'cli_session',
|
|
@@ -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);
|
|
@@ -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();
|