@adhdev/daemon-core 0.9.38 → 0.9.40
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/commands/chat-commands.d.ts +2 -0
- package/dist/index.js +83 -67
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +83 -67
- 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 +11 -0
- package/src/cli-adapters/provider-cli-parse.ts +25 -15
- package/src/commands/chat-commands.ts +65 -56
- package/src/providers/cli-provider-instance.ts +6 -3
package/package.json
CHANGED
|
@@ -1053,6 +1053,17 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1053
1053
|
this.resolveStartupState('settled');
|
|
1054
1054
|
if (this.startupParseGate) return;
|
|
1055
1055
|
|
|
1056
|
+
if (!this.isWaitingForResponse && !this.currentTurnScope && !this.activeModal && !this.parseErrorMessage) {
|
|
1057
|
+
const tail = this.settledBuffer || this.recentOutputBuffer;
|
|
1058
|
+
const modal = this.runParseApproval(tail);
|
|
1059
|
+
const lightweightStatus = this.cliScripts?.detectStatus
|
|
1060
|
+
? this.runDetectStatus(tail)
|
|
1061
|
+
: null;
|
|
1062
|
+
if (!modal && lightweightStatus === 'idle' && this.currentStatus === 'idle') {
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1056
1067
|
const session = this.runParseSession();
|
|
1057
1068
|
if (!session) return;
|
|
1058
1069
|
|
|
@@ -173,10 +173,12 @@ export function hydrateCliParsedMessages(
|
|
|
173
173
|
});
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
function chooseMoreComparableCliMessage(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
176
|
+
function chooseMoreComparableCliMessage(
|
|
177
|
+
left: CliChatMessage,
|
|
178
|
+
right: CliChatMessage,
|
|
179
|
+
leftComparable = normalizeComparableMessageContent(left.content || ''),
|
|
180
|
+
rightComparable = normalizeComparableMessageContent(right.content || ''),
|
|
181
|
+
): CliChatMessage {
|
|
180
182
|
if (leftComparable && leftComparable === rightComparable) {
|
|
181
183
|
const leftNewlines = String(left.content || '').split(/\r\n|\n|\r/g).length - 1;
|
|
182
184
|
const rightNewlines = String(right.content || '').split(/\r\n|\n|\r/g).length - 1;
|
|
@@ -187,35 +189,43 @@ function chooseMoreComparableCliMessage(left: CliChatMessage, right: CliChatMess
|
|
|
187
189
|
}
|
|
188
190
|
|
|
189
191
|
function dedupeConsecutiveComparableCliMessages(messages: CliChatMessage[]): CliChatMessage[] {
|
|
190
|
-
const deduped: CliChatMessage
|
|
192
|
+
const deduped: Array<{ message: CliChatMessage; comparable: string }> = [];
|
|
191
193
|
|
|
192
194
|
for (const message of messages) {
|
|
193
195
|
const current = {
|
|
194
196
|
...message,
|
|
195
197
|
content: typeof message.content === 'string' ? message.content : String(message.content || ''),
|
|
196
198
|
} as CliChatMessage;
|
|
199
|
+
const currentComparable = normalizeComparableMessageContent(current.content || '');
|
|
197
200
|
const previous = deduped[deduped.length - 1];
|
|
198
201
|
if (!previous) {
|
|
199
|
-
deduped.push(current);
|
|
202
|
+
deduped.push({ message: current, comparable: currentComparable });
|
|
200
203
|
continue;
|
|
201
204
|
}
|
|
202
205
|
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
const
|
|
207
|
-
const sameSender = (previous.senderName || '') === (current.senderName || '');
|
|
208
|
-
const comparableMatch = previousComparable && previousComparable === currentComparable;
|
|
206
|
+
const sameRole = previous.message.role === current.role;
|
|
207
|
+
const sameKind = (previous.message.kind || 'standard') === (current.kind || 'standard');
|
|
208
|
+
const sameSender = (previous.message.senderName || '') === (current.senderName || '');
|
|
209
|
+
const comparableMatch = previous.comparable && previous.comparable === currentComparable;
|
|
209
210
|
|
|
210
211
|
if (sameRole && sameKind && sameSender && comparableMatch) {
|
|
211
|
-
|
|
212
|
+
const selected = chooseMoreComparableCliMessage(
|
|
213
|
+
previous.message,
|
|
214
|
+
current,
|
|
215
|
+
previous.comparable,
|
|
216
|
+
currentComparable,
|
|
217
|
+
);
|
|
218
|
+
deduped[deduped.length - 1] = {
|
|
219
|
+
message: selected,
|
|
220
|
+
comparable: selected === current ? currentComparable : previous.comparable,
|
|
221
|
+
};
|
|
212
222
|
continue;
|
|
213
223
|
}
|
|
214
224
|
|
|
215
|
-
deduped.push(current);
|
|
225
|
+
deduped.push({ message: current, comparable: currentComparable });
|
|
216
226
|
}
|
|
217
227
|
|
|
218
|
-
return deduped;
|
|
228
|
+
return deduped.map((entry) => entry.message);
|
|
219
229
|
}
|
|
220
230
|
|
|
221
231
|
export function normalizeCliParsedMessages(
|
|
@@ -178,85 +178,90 @@ function normalizeReadChatMessages(payload: Record<string, any>): ChatMessage[]
|
|
|
178
178
|
return normalizeChatMessages(messages);
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function shouldCollapseReadChatReplayDuplicate(message: ChatMessage | null | undefined): boolean {
|
|
191
|
-
if (!message) return false;
|
|
192
|
-
const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
|
|
193
|
-
return role === 'assistant' || role === 'system';
|
|
181
|
+
interface ReadChatReplayCollapseInfo {
|
|
182
|
+
role: string;
|
|
183
|
+
kind: string;
|
|
184
|
+
senderName: string;
|
|
185
|
+
content: string;
|
|
186
|
+
signature: string;
|
|
187
|
+
collapsible: boolean;
|
|
194
188
|
}
|
|
195
189
|
|
|
196
|
-
function
|
|
197
|
-
return flattenContent(
|
|
190
|
+
function normalizeReadChatReplayTextContent(content: ChatMessage['content'] | undefined): string {
|
|
191
|
+
return flattenContent(content || '').replace(/\s+/g, ' ').trim();
|
|
198
192
|
}
|
|
199
193
|
|
|
200
|
-
function
|
|
201
|
-
if (!message) return
|
|
194
|
+
function getReadChatReplayCollapseInfo(message: ChatMessage | null | undefined): ReadChatReplayCollapseInfo | null {
|
|
195
|
+
if (!message) return null;
|
|
202
196
|
const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
|
|
203
197
|
const kind = typeof message.kind === 'string' ? message.kind.trim().toLowerCase() : 'standard';
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
198
|
+
const senderName = typeof message.senderName === 'string' ? message.senderName.trim().toLowerCase() : '';
|
|
199
|
+
const collapsible = role === 'assistant' || role === 'system';
|
|
200
|
+
if (!collapsible) return { role, kind, senderName, content: '', signature: '', collapsible };
|
|
201
|
+
const content = normalizeReadChatReplayTextContent(message.content);
|
|
202
|
+
return {
|
|
203
|
+
role,
|
|
204
|
+
kind,
|
|
205
|
+
senderName,
|
|
206
|
+
content,
|
|
207
|
+
signature: `${role}:${kind}:${senderName}:${content}`,
|
|
208
|
+
collapsible,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function isStableReadChatAssistantAnswerInfo(info: ReadChatReplayCollapseInfo | null): boolean {
|
|
213
|
+
if (!info) return false;
|
|
214
|
+
if (info.role !== 'assistant') return false;
|
|
215
|
+
if (info.kind && info.kind !== 'standard') return false;
|
|
216
|
+
if (info.content.length < 160) return false;
|
|
208
217
|
|
|
209
218
|
// A provider may surface expanded command output as a standard assistant bubble
|
|
210
219
|
// (for example Claude Code's "Bash command ..." block). That is live work output,
|
|
211
220
|
// not a stable final answer. Treating it as a terminal answer would hide the
|
|
212
221
|
// real final response and violate read_chat fidelity.
|
|
213
|
-
if (/^(bash|shell|terminal) command\b/i.test(content)) return false;
|
|
222
|
+
if (/^(bash|shell|terminal) command\b/i.test(info.content)) return false;
|
|
214
223
|
return true;
|
|
215
224
|
}
|
|
216
225
|
|
|
217
|
-
function
|
|
218
|
-
|
|
219
|
-
|
|
226
|
+
function isReplayedAssistantAnswerAfterStableAnswerInfo(
|
|
227
|
+
info: ReadChatReplayCollapseInfo | null,
|
|
228
|
+
stableContent: string,
|
|
220
229
|
): boolean {
|
|
221
|
-
if (!
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (kind && kind !== 'standard') return false;
|
|
226
|
-
const content = normalizeReadChatReplayText(message);
|
|
227
|
-
const stableContent = normalizeReadChatReplayText(stableAnswer);
|
|
230
|
+
if (!info || !stableContent) return false;
|
|
231
|
+
if (info.role !== 'assistant') return false;
|
|
232
|
+
if (info.kind && info.kind !== 'standard') return false;
|
|
233
|
+
const content = info.content;
|
|
228
234
|
if (content.length < 80 || stableContent.length < 80) return false;
|
|
229
235
|
return content === stableContent || content.startsWith(stableContent) || stableContent.startsWith(content);
|
|
230
236
|
}
|
|
231
237
|
|
|
232
|
-
function collapseReplayDuplicatesFromReadChat(messages: ChatMessage[]): ChatMessage[] {
|
|
238
|
+
export function collapseReplayDuplicatesFromReadChat(messages: ChatMessage[]): ChatMessage[] {
|
|
233
239
|
const collapsed: ChatMessage[] = [];
|
|
234
240
|
const replaySignaturesInCurrentTurn = new Set<string>();
|
|
235
|
-
let
|
|
241
|
+
let stableAssistantAnswerContentInCurrentTurn = '';
|
|
242
|
+
let previousReplaySignature = '';
|
|
236
243
|
|
|
237
244
|
for (const message of messages) {
|
|
238
|
-
const
|
|
239
|
-
if (role === 'user') {
|
|
245
|
+
const info = getReadChatReplayCollapseInfo(message);
|
|
246
|
+
if (info?.role === 'user') {
|
|
240
247
|
replaySignaturesInCurrentTurn.clear();
|
|
241
|
-
|
|
248
|
+
stableAssistantAnswerContentInCurrentTurn = '';
|
|
249
|
+
previousReplaySignature = '';
|
|
242
250
|
}
|
|
243
251
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (shouldCollapseReadChatReplayDuplicate(message) && signature) {
|
|
249
|
-
if (previousSignature === signature) continue;
|
|
250
|
-
if (replaySignaturesInCurrentTurn.has(signature)) continue;
|
|
251
|
-
if (isReplayedAssistantAnswerAfterStableAnswer(message, stableAssistantAnswerInCurrentTurn)) continue;
|
|
252
|
+
if (info?.collapsible && info.signature) {
|
|
253
|
+
if (previousReplaySignature === info.signature) continue;
|
|
254
|
+
if (replaySignaturesInCurrentTurn.has(info.signature)) continue;
|
|
255
|
+
if (isReplayedAssistantAnswerAfterStableAnswerInfo(info, stableAssistantAnswerContentInCurrentTurn)) continue;
|
|
252
256
|
}
|
|
253
257
|
|
|
254
258
|
collapsed.push(message);
|
|
255
|
-
|
|
256
|
-
|
|
259
|
+
previousReplaySignature = info?.collapsible ? info.signature : '';
|
|
260
|
+
if (info?.collapsible && info.signature) {
|
|
261
|
+
replaySignaturesInCurrentTurn.add(info.signature);
|
|
257
262
|
}
|
|
258
|
-
if (
|
|
259
|
-
|
|
263
|
+
if (isStableReadChatAssistantAnswerInfo(info)) {
|
|
264
|
+
stableAssistantAnswerContentInCurrentTurn = info?.content || '';
|
|
260
265
|
}
|
|
261
266
|
}
|
|
262
267
|
|
|
@@ -369,13 +374,17 @@ function computeReadChatSync(messages: ChatMessage[], cursor: Required<ReadChatC
|
|
|
369
374
|
}
|
|
370
375
|
|
|
371
376
|
if (cursor.tailLimit > 0 && knownSignature === lastMessageSignature) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
377
|
+
const requestedTailCount = Math.min(totalMessages, cursor.tailLimit);
|
|
378
|
+
if (knownMessageCount >= requestedTailCount) {
|
|
379
|
+
return {
|
|
380
|
+
syncMode: 'noop',
|
|
381
|
+
replaceFrom: totalMessages,
|
|
382
|
+
messages: [],
|
|
383
|
+
totalMessages,
|
|
384
|
+
lastMessageSignature,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
return buildBoundedTailSync(messages, cursor);
|
|
379
388
|
}
|
|
380
389
|
|
|
381
390
|
if (knownMessageCount < totalMessages) {
|
|
@@ -599,8 +599,11 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
599
599
|
|
|
600
600
|
private detectStatusTransition(): void {
|
|
601
601
|
const now = Date.now();
|
|
602
|
-
|
|
603
|
-
|
|
602
|
+
// Status-change handling is a hot path: PTY output can fire it many times
|
|
603
|
+
// during long-running CLI sessions. Keep this path on adapter-owned light
|
|
604
|
+
// state only; rich provider parsing is reserved for getState/read_chat.
|
|
605
|
+
const adapterStatus = this.adapter.getStatus({ allowParse: false });
|
|
606
|
+
const parsedStatus = null;
|
|
604
607
|
const rawStatus = adapterStatus.status;
|
|
605
608
|
const autoApproveActive = rawStatus === 'waiting_approval' && this.shouldAutoApprove();
|
|
606
609
|
// Guard re-entry: onStatusChange can fire multiple times while the modal
|
|
@@ -696,7 +699,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
696
699
|
this.completedDebouncePending = { chatTitle, duration, timestamp: now };
|
|
697
700
|
this.completedDebounceTimer = setTimeout(() => {
|
|
698
701
|
if (this.completedDebouncePending) {
|
|
699
|
-
const latestStatus = this.adapter.getStatus();
|
|
702
|
+
const latestStatus = this.adapter.getStatus({ allowParse: false });
|
|
700
703
|
const latestAutoApproveActive = latestStatus.status === 'waiting_approval' && this.shouldAutoApprove();
|
|
701
704
|
const latestVisibleStatus = latestAutoApproveActive ? 'generating' : latestStatus.status;
|
|
702
705
|
if (latestVisibleStatus !== 'idle') {
|