@adhdev/daemon-core 0.9.82-rc.92 → 0.9.82-rc.94
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 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +8 -0
- package/dist/index.js +150 -22
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +150 -22
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +160 -10
- package/src/cli-adapters/provider-cli-shared.ts +8 -0
- package/src/commands/chat-commands.ts +1 -1
- package/src/commands/cli-manager.ts +5 -13
package/package.json
CHANGED
|
@@ -110,6 +110,14 @@ interface SendMessageCompletion {
|
|
|
110
110
|
rejectOnce: (error: unknown) => void;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
interface PendingOutboundMessage {
|
|
114
|
+
id: string;
|
|
115
|
+
role: 'user';
|
|
116
|
+
content: string;
|
|
117
|
+
queuedAt: number;
|
|
118
|
+
source: 'sendMessage';
|
|
119
|
+
}
|
|
120
|
+
|
|
113
121
|
export function appendBoundedText(current: string, chunk: string, maxChars: number): string {
|
|
114
122
|
if (!chunk) return current.length <= maxChars ? current : current.slice(-maxChars);
|
|
115
123
|
if (maxChars <= 0) return '';
|
|
@@ -186,6 +194,9 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
186
194
|
private idleFinishCandidate: IdleFinishCandidate | null = null;
|
|
187
195
|
private finishRetryTimer: NodeJS.Timeout | null = null;
|
|
188
196
|
private finishRetryCount = 0;
|
|
197
|
+
private pendingOutboundQueue: PendingOutboundMessage[] = [];
|
|
198
|
+
private pendingOutboundFlushTimer: NodeJS.Timeout | null = null;
|
|
199
|
+
private pendingOutboundFlushInFlight = false;
|
|
189
200
|
|
|
190
201
|
// Resize redraw suppression
|
|
191
202
|
private resizeSuppressUntil: number = 0;
|
|
@@ -1415,6 +1426,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1415
1426
|
this.activeModal = null;
|
|
1416
1427
|
this.setStatus('idle', 'response_finished');
|
|
1417
1428
|
this.onStatusChange?.();
|
|
1429
|
+
this.schedulePendingOutboundFlush();
|
|
1418
1430
|
}
|
|
1419
1431
|
|
|
1420
1432
|
private maybeCommitVisibleIdleTranscript(session: ParsedSession, parsedMessages: CliChatMessage[]): boolean {
|
|
@@ -1445,6 +1457,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1445
1457
|
this.activeModal = null;
|
|
1446
1458
|
this.setStatus('idle', 'script_idle_commit');
|
|
1447
1459
|
this.onStatusChange?.();
|
|
1460
|
+
this.schedulePendingOutboundFlush();
|
|
1448
1461
|
this.recordTrace('script_idle_commit', {
|
|
1449
1462
|
messageCount: parsedMessages.length,
|
|
1450
1463
|
lastAssistant: summarizeCliTraceText(visibleAssistant.content, 320),
|
|
@@ -1654,6 +1667,14 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1654
1667
|
messages: [],
|
|
1655
1668
|
workingDir: this.workingDir,
|
|
1656
1669
|
activeModal: effectiveModal,
|
|
1670
|
+
pendingOutboundCount: this.pendingOutboundQueue.length,
|
|
1671
|
+
pendingOutboundMessages: this.pendingOutboundQueue.map((message) => ({
|
|
1672
|
+
id: message.id,
|
|
1673
|
+
role: message.role,
|
|
1674
|
+
content: message.content,
|
|
1675
|
+
queuedAt: message.queuedAt,
|
|
1676
|
+
source: message.source,
|
|
1677
|
+
})),
|
|
1657
1678
|
errorMessage: this.parseErrorMessage || undefined,
|
|
1658
1679
|
errorReason: this.parseErrorMessage ? 'parse_error' : undefined,
|
|
1659
1680
|
...(bufferState ? { bufferState } : {}),
|
|
@@ -1671,7 +1692,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1671
1692
|
const cached = this.parsedStatusCache;
|
|
1672
1693
|
const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
|
|
1673
1694
|
if (
|
|
1674
|
-
|
|
1695
|
+
!this.providerOwnsTranscript()
|
|
1696
|
+
&& cached
|
|
1675
1697
|
&& cached.responseBuffer === this.responseBuffer
|
|
1676
1698
|
&& cached.currentTurnScope === this.currentTurnScope
|
|
1677
1699
|
&& cached.recentOutputBuffer === this.recentOutputBuffer
|
|
@@ -1997,6 +2019,104 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1997
2019
|
}
|
|
1998
2020
|
|
|
1999
2021
|
async sendMessage(text: string): Promise<void> {
|
|
2022
|
+
await this.sendMessageNow(text, true);
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
private enqueuePendingOutboundMessage(text: string, reason: string): void {
|
|
2026
|
+
const content = String(text || '');
|
|
2027
|
+
const duplicate = this.pendingOutboundQueue.some((message) => message.content === content);
|
|
2028
|
+
if (duplicate) {
|
|
2029
|
+
this.recordTrace('send_message_queued_duplicate_suppressed', {
|
|
2030
|
+
reason,
|
|
2031
|
+
queueLength: this.pendingOutboundQueue.length,
|
|
2032
|
+
text: summarizeCliTraceText(content, 500),
|
|
2033
|
+
});
|
|
2034
|
+
return;
|
|
2035
|
+
}
|
|
2036
|
+
const queuedAt = Date.now();
|
|
2037
|
+
const message: PendingOutboundMessage = {
|
|
2038
|
+
id: `${queuedAt}:${this.pendingOutboundQueue.length}:${Math.random().toString(36).slice(2, 10)}`,
|
|
2039
|
+
role: 'user',
|
|
2040
|
+
content,
|
|
2041
|
+
queuedAt,
|
|
2042
|
+
source: 'sendMessage',
|
|
2043
|
+
};
|
|
2044
|
+
this.pendingOutboundQueue.push(message);
|
|
2045
|
+
this.recordTrace('send_message_queued', {
|
|
2046
|
+
reason,
|
|
2047
|
+
queueLength: this.pendingOutboundQueue.length,
|
|
2048
|
+
queuedAt,
|
|
2049
|
+
text: summarizeCliTraceText(content, 500),
|
|
2050
|
+
});
|
|
2051
|
+
LOG.info('CLI', `[${this.cliType}] queued outbound message while busy (${reason}); queue=${this.pendingOutboundQueue.length}`);
|
|
2052
|
+
this.onStatusChange?.();
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
private shouldQueuePendingOutboundMessage(parsedStatusBeforeSend: any | null = null): string | null {
|
|
2056
|
+
if (this.provider.allowInputDuringGeneration === true) return null;
|
|
2057
|
+
if (this.hasActionableApproval()) return null;
|
|
2058
|
+
const parsedSessionStatus = typeof parsedStatusBeforeSend?.status === 'string'
|
|
2059
|
+
? String(parsedStatusBeforeSend.status)
|
|
2060
|
+
: '';
|
|
2061
|
+
if (parsedSessionStatus === 'idle' && this.parsedStatusHasFinalAssistantMessage(parsedStatusBeforeSend)) return null;
|
|
2062
|
+
if (this.currentStatus === 'generating') return 'current_status_generating';
|
|
2063
|
+
if (parsedSessionStatus === 'generating' || parsedSessionStatus === 'long_generating') {
|
|
2064
|
+
const parsedModal = parsedStatusBeforeSend?.activeModal ?? parsedStatusBeforeSend?.modal ?? null;
|
|
2065
|
+
const parsedHasActionableModal = Boolean(
|
|
2066
|
+
parsedModal
|
|
2067
|
+
&& Array.isArray(parsedModal.buttons)
|
|
2068
|
+
&& parsedModal.buttons.some((candidate: unknown) => typeof candidate === 'string' && candidate.trim()),
|
|
2069
|
+
);
|
|
2070
|
+
const terminalLooksIdle = this.currentStatus === 'idle'
|
|
2071
|
+
&& this.runDetectStatus(this.recentOutputBuffer) === 'idle'
|
|
2072
|
+
&& !this.isWaitingForResponse
|
|
2073
|
+
&& !this.currentTurnScope
|
|
2074
|
+
&& !this.hasActionableApproval()
|
|
2075
|
+
&& !parsedHasActionableModal;
|
|
2076
|
+
return terminalLooksIdle ? null : `parsed_status_${parsedSessionStatus}`;
|
|
2077
|
+
}
|
|
2078
|
+
if (this.isWaitingForResponse && this.currentTurnScope) return 'active_turn_in_progress';
|
|
2079
|
+
return null;
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
private schedulePendingOutboundFlush(delayMs = 0): void {
|
|
2083
|
+
if (this.pendingOutboundFlushTimer) clearTimeout(this.pendingOutboundFlushTimer);
|
|
2084
|
+
this.pendingOutboundFlushTimer = setTimeout(() => {
|
|
2085
|
+
this.pendingOutboundFlushTimer = null;
|
|
2086
|
+
void this.flushPendingOutboundQueue();
|
|
2087
|
+
}, Math.max(0, delayMs));
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
private async flushPendingOutboundQueue(): Promise<void> {
|
|
2091
|
+
if (this.pendingOutboundFlushInFlight || this.pendingOutboundQueue.length === 0) return;
|
|
2092
|
+
if (this.currentStatus !== 'idle' || this.isWaitingForResponse || this.hasActionableApproval()) return;
|
|
2093
|
+
this.pendingOutboundFlushInFlight = true;
|
|
2094
|
+
try {
|
|
2095
|
+
while (this.pendingOutboundQueue.length > 0) {
|
|
2096
|
+
if (this.currentStatus !== 'idle' || this.isWaitingForResponse || this.hasActionableApproval()) break;
|
|
2097
|
+
const next = this.pendingOutboundQueue[0];
|
|
2098
|
+
this.recordTrace('send_message_queue_flush', {
|
|
2099
|
+
id: next.id,
|
|
2100
|
+
queuedAt: next.queuedAt,
|
|
2101
|
+
queueLength: this.pendingOutboundQueue.length,
|
|
2102
|
+
text: summarizeCliTraceText(next.content, 500),
|
|
2103
|
+
});
|
|
2104
|
+
try {
|
|
2105
|
+
await this.sendMessageNow(next.content, false);
|
|
2106
|
+
this.pendingOutboundQueue.shift();
|
|
2107
|
+
this.onStatusChange?.();
|
|
2108
|
+
} catch (error: any) {
|
|
2109
|
+
LOG.warn('CLI', `[${this.cliType}] queued outbound flush failed: ${error?.message || error}`);
|
|
2110
|
+
this.schedulePendingOutboundFlush(1000);
|
|
2111
|
+
break;
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
} finally {
|
|
2115
|
+
this.pendingOutboundFlushInFlight = false;
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
private async sendMessageNow(text: string, allowQueue: boolean): Promise<void> {
|
|
2000
2120
|
if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
|
|
2001
2121
|
const allowInputDuringGeneration = this.provider.allowInputDuringGeneration === true;
|
|
2002
2122
|
const allowInterventionPrompt = allowInputDuringGeneration
|
|
@@ -2009,6 +2129,20 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2009
2129
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
2010
2130
|
}
|
|
2011
2131
|
}
|
|
2132
|
+
const parsedStatusBeforeSend = !allowInputDuringGeneration
|
|
2133
|
+
? (() => {
|
|
2134
|
+
try {
|
|
2135
|
+
return this.getScriptParsedStatus?.() || null;
|
|
2136
|
+
} catch {
|
|
2137
|
+
return null;
|
|
2138
|
+
}
|
|
2139
|
+
})()
|
|
2140
|
+
: null;
|
|
2141
|
+
const queueReason = this.shouldQueuePendingOutboundMessage(parsedStatusBeforeSend);
|
|
2142
|
+
if (allowQueue && queueReason) {
|
|
2143
|
+
this.enqueuePendingOutboundMessage(text, queueReason);
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
2012
2146
|
if (!allowInterventionPrompt) {
|
|
2013
2147
|
await this.waitForInteractivePrompt();
|
|
2014
2148
|
}
|
|
@@ -2021,15 +2155,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2021
2155
|
}
|
|
2022
2156
|
}
|
|
2023
2157
|
if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
|
|
2024
|
-
const parsedStatusBeforeSend = !allowInputDuringGeneration
|
|
2025
|
-
? (() => {
|
|
2026
|
-
try {
|
|
2027
|
-
return this.getScriptParsedStatus?.() || null;
|
|
2028
|
-
} catch {
|
|
2029
|
-
return null;
|
|
2030
|
-
}
|
|
2031
|
-
})()
|
|
2032
|
-
: null;
|
|
2033
2158
|
const parsedSessionStatus = typeof parsedStatusBeforeSend?.status === 'string'
|
|
2034
2159
|
? String(parsedStatusBeforeSend.status)
|
|
2035
2160
|
: '';
|
|
@@ -2047,6 +2172,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2047
2172
|
&& !this.hasActionableApproval()
|
|
2048
2173
|
&& !parsedHasActionableModal;
|
|
2049
2174
|
if (!terminalLooksIdle) {
|
|
2175
|
+
if (allowQueue) {
|
|
2176
|
+
this.enqueuePendingOutboundMessage(text, `parsed_status_${parsedSessionStatus}`);
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2050
2179
|
throw new Error(`${this.cliName} is still processing the previous prompt`);
|
|
2051
2180
|
}
|
|
2052
2181
|
}
|
|
@@ -2055,6 +2184,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2055
2184
|
!this.clearStaleIdleResponseGuard('send_message_guard')
|
|
2056
2185
|
&& !this.clearParsedIdleResponseGuard('send_message_parsed_idle_guard', parsedStatusBeforeSend)
|
|
2057
2186
|
) {
|
|
2187
|
+
if (allowQueue) {
|
|
2188
|
+
this.enqueuePendingOutboundMessage(text, 'waiting_for_response');
|
|
2189
|
+
return;
|
|
2190
|
+
}
|
|
2058
2191
|
throw new Error(`${this.cliName} is still processing the previous prompt`);
|
|
2059
2192
|
}
|
|
2060
2193
|
}
|
|
@@ -2305,6 +2438,9 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2305
2438
|
this.pendingTerminalQueryTail = '';
|
|
2306
2439
|
this.ptyOutputChunks = [];
|
|
2307
2440
|
this.finishRetryCount = 0;
|
|
2441
|
+
if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
|
|
2442
|
+
this.pendingOutboundQueue = [];
|
|
2443
|
+
this.pendingOutboundFlushInFlight = false;
|
|
2308
2444
|
if (this.ptyProcess) {
|
|
2309
2445
|
this.ptyProcess.write('\x03');
|
|
2310
2446
|
setTimeout(() => {
|
|
@@ -2326,6 +2462,9 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2326
2462
|
this.pendingTerminalQueryTail = '';
|
|
2327
2463
|
this.ptyOutputChunks = [];
|
|
2328
2464
|
this.finishRetryCount = 0;
|
|
2465
|
+
if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
|
|
2466
|
+
this.pendingOutboundQueue = [];
|
|
2467
|
+
this.pendingOutboundFlushInFlight = false;
|
|
2329
2468
|
if (this.ptyProcess) {
|
|
2330
2469
|
try {
|
|
2331
2470
|
if (typeof this.ptyProcess.detach === 'function') {
|
|
@@ -2356,6 +2495,9 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2356
2495
|
this.ptyOutputChunks = [];
|
|
2357
2496
|
if (this.finishRetryTimer) { clearTimeout(this.finishRetryTimer); this.finishRetryTimer = null; }
|
|
2358
2497
|
this.finishRetryCount = 0;
|
|
2498
|
+
if (this.pendingOutboundFlushTimer) { clearTimeout(this.pendingOutboundFlushTimer); this.pendingOutboundFlushTimer = null; }
|
|
2499
|
+
this.pendingOutboundQueue = [];
|
|
2500
|
+
this.pendingOutboundFlushInFlight = false;
|
|
2359
2501
|
this.resetTerminalScreen();
|
|
2360
2502
|
this.ptyProcess?.clearBuffer?.();
|
|
2361
2503
|
this.onStatusChange?.();
|
|
@@ -2495,6 +2637,14 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
2495
2637
|
rawBufferPreview: this.accumulatedRawBuffer.slice(-1000),
|
|
2496
2638
|
sanitizedRawPreview: sanitizeTerminalText(this.accumulatedRawBuffer).slice(-1000),
|
|
2497
2639
|
responseBuffer: this.responseBuffer.slice(-1000),
|
|
2640
|
+
pendingOutboundQueue: this.pendingOutboundQueue.map((message) => ({
|
|
2641
|
+
id: message.id,
|
|
2642
|
+
role: message.role,
|
|
2643
|
+
content: message.content,
|
|
2644
|
+
queuedAt: message.queuedAt,
|
|
2645
|
+
source: message.source,
|
|
2646
|
+
})),
|
|
2647
|
+
pendingOutboundCount: this.pendingOutboundQueue.length,
|
|
2498
2648
|
lastOutputAt: this.lastOutputAt,
|
|
2499
2649
|
lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
|
|
2500
2650
|
lastScreenChangeAt: this.lastScreenChangeAt,
|
|
@@ -28,6 +28,14 @@ export interface CliSessionStatus {
|
|
|
28
28
|
messages: CliChatMessage[];
|
|
29
29
|
workingDir: string;
|
|
30
30
|
activeModal: { message: string; buttons: string[] } | null;
|
|
31
|
+
pendingOutboundCount?: number;
|
|
32
|
+
pendingOutboundMessages?: Array<{
|
|
33
|
+
id: string;
|
|
34
|
+
role: 'user';
|
|
35
|
+
content: string;
|
|
36
|
+
queuedAt: number;
|
|
37
|
+
source: string;
|
|
38
|
+
}>;
|
|
31
39
|
errorMessage?: string;
|
|
32
40
|
errorReason?: string;
|
|
33
41
|
bufferState?: {
|
|
@@ -25,7 +25,7 @@ const RECENT_SEND_WINDOW_MS = 1200;
|
|
|
25
25
|
export const READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS = 25_000;
|
|
26
26
|
const HERMES_CLI_STARTING_SEND_SETTLE_MS = 2_000;
|
|
27
27
|
const CLI_NATIVE_HISTORY_FRESH_MS = 5 * 60_000;
|
|
28
|
-
const CLI_NATIVE_TRANSCRIPT_PROVIDERS = new Set(['codex-cli', 'claude-cli']);
|
|
28
|
+
const CLI_NATIVE_TRANSCRIPT_PROVIDERS = new Set(['codex-cli', 'claude-cli', 'hermes-cli']);
|
|
29
29
|
const recentSendByTarget = new Map<string, number>();
|
|
30
30
|
|
|
31
31
|
interface ApprovalSelectableInstance extends ProviderInstance {
|
|
@@ -1187,18 +1187,6 @@ export class DaemonCliManager {
|
|
|
1187
1187
|
} else if (currentStatus === 'starting') {
|
|
1188
1188
|
currentStatus = getEffectiveAgentSendStatus(adapter);
|
|
1189
1189
|
}
|
|
1190
|
-
if (BUSY_AGENT_STATUSES.has(currentStatus)) {
|
|
1191
|
-
return {
|
|
1192
|
-
success: false,
|
|
1193
|
-
code: 'agent_runtime_busy',
|
|
1194
|
-
reason: 'agent_runtime_busy',
|
|
1195
|
-
retryable: true,
|
|
1196
|
-
retryRecommended: true,
|
|
1197
|
-
status: currentStatus,
|
|
1198
|
-
targetSessionId: args?.targetSessionId,
|
|
1199
|
-
error: `CLI agent '${agentType}' is currently ${currentStatus}; retry after the current turn finishes.`,
|
|
1200
|
-
};
|
|
1201
|
-
}
|
|
1202
1190
|
const input = normalizeInputEnvelope(args?.input ? { input: args.input } : args);
|
|
1203
1191
|
const provider = this.providerLoader.resolve(agentType) || this.providerLoader.getMeta(agentType);
|
|
1204
1192
|
if (provider?.category === 'acp') {
|
|
@@ -1209,7 +1197,11 @@ export class DaemonCliManager {
|
|
|
1209
1197
|
const message = input.textFallback;
|
|
1210
1198
|
if (!message) throw new Error('message required for send_chat');
|
|
1211
1199
|
await adapter.sendMessage(message);
|
|
1212
|
-
return {
|
|
1200
|
+
return {
|
|
1201
|
+
success: true,
|
|
1202
|
+
status: BUSY_AGENT_STATUSES.has(currentStatus) ? currentStatus : 'generating',
|
|
1203
|
+
...(BUSY_AGENT_STATUSES.has(currentStatus) ? { queued: true, queuedReason: 'agent_runtime_busy' } : {}),
|
|
1204
|
+
};
|
|
1213
1205
|
} else if (action === 'clear_history') {
|
|
1214
1206
|
if (typeof adapter.clearHistory === 'function') adapter.clearHistory();
|
|
1215
1207
|
return { success: true, cleared: true };
|