@adhdev/daemon-core 0.8.21 → 0.8.23
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/agent-stream/types.d.ts +3 -0
- package/dist/cli-adapter-types.d.ts +3 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +71 -11
- package/dist/commands/stream-commands.d.ts +1 -0
- package/dist/config/config.d.ts +6 -0
- package/dist/index.js +1163 -314
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1163 -314
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +6 -0
- package/dist/providers/contracts.d.ts +59 -1
- package/dist/providers/control-effects.d.ts +4 -0
- package/dist/providers/extension-provider-instance.d.ts +9 -0
- package/dist/providers/ide-provider-instance.d.ts +8 -0
- package/dist/shared-types.d.ts +2 -1
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +3 -2
- package/src/agent-stream/forward.ts +2 -0
- package/src/agent-stream/provider-adapter.ts +5 -15
- package/src/agent-stream/types.ts +4 -0
- package/src/cli-adapter-types.ts +3 -0
- package/src/cli-adapters/provider-cli-adapter.ts +399 -49
- package/src/commands/chat-commands.ts +33 -12
- package/src/commands/handler.ts +1 -0
- package/src/commands/stream-commands.ts +99 -8
- package/src/config/config.d.ts +1 -0
- package/src/config/config.ts +9 -0
- package/src/launch.ts +57 -11
- package/src/providers/cli-provider-instance.ts +148 -2
- package/src/providers/contracts.ts +65 -2
- package/src/providers/control-effects.ts +114 -0
- package/src/providers/extension-provider-instance.ts +163 -3
- package/src/providers/ide-provider-instance.ts +181 -2
- package/src/shared-types.d.ts +1 -0
- package/src/shared-types.ts +2 -1
- package/src/status/snapshot.ts +1 -0
|
@@ -35,8 +35,19 @@ export interface CliChatMessage {
|
|
|
35
35
|
role: 'user' | 'assistant';
|
|
36
36
|
content: string;
|
|
37
37
|
timestamp?: number;
|
|
38
|
+
receivedAt?: number;
|
|
39
|
+
kind?: string;
|
|
40
|
+
id?: string;
|
|
41
|
+
index?: number;
|
|
42
|
+
meta?: Record<string, any>;
|
|
43
|
+
senderName?: string;
|
|
38
44
|
}
|
|
39
45
|
|
|
46
|
+
type SeedCliChatMessage = Omit<Partial<CliChatMessage>, 'role'> & {
|
|
47
|
+
role?: string;
|
|
48
|
+
content?: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
40
51
|
export interface CliSessionStatus {
|
|
41
52
|
status: 'idle' | 'generating' | 'waiting_approval' | 'error' | 'stopped' | 'starting';
|
|
42
53
|
messages: CliChatMessage[];
|
|
@@ -53,23 +64,70 @@ export interface CliScripts {
|
|
|
53
64
|
/** Full PTY buffer → ReadChatResult (messages, status, activeModal) */
|
|
54
65
|
parseOutput?: (input: CliScriptInput) => any;
|
|
55
66
|
/** Lightweight status detection (high-frequency polling) → AgentStatus string */
|
|
56
|
-
detectStatus?: (input:
|
|
67
|
+
detectStatus?: (input: CliStatusInput) => string | null;
|
|
57
68
|
/** Parse approval modal from PTY output → ModalInfo | null */
|
|
58
|
-
parseApproval?: (input:
|
|
69
|
+
parseApproval?: (input: CliApprovalInput) => { message: string; buttons: string[] } | null;
|
|
59
70
|
/** Produce a cli-specific prompt from a dashboard action payload */
|
|
60
71
|
resolveAction?: (data: any) => string;
|
|
61
72
|
/** Custom scripts */
|
|
62
73
|
[name: string]: ((input: any) => any) | undefined;
|
|
63
74
|
}
|
|
64
75
|
|
|
76
|
+
export interface CliScreenLine {
|
|
77
|
+
index: number;
|
|
78
|
+
fromTop: number;
|
|
79
|
+
fromBottom: number;
|
|
80
|
+
text: string;
|
|
81
|
+
trimmed: string;
|
|
82
|
+
isEmpty: boolean;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface CliScreenSnapshot {
|
|
86
|
+
text: string;
|
|
87
|
+
lineCount: number;
|
|
88
|
+
lines: CliScreenLine[];
|
|
89
|
+
nonEmptyLines: CliScreenLine[];
|
|
90
|
+
firstNonEmptyLineIndex: number;
|
|
91
|
+
lastNonEmptyLineIndex: number;
|
|
92
|
+
firstNonEmptyLine: CliScreenLine | null;
|
|
93
|
+
lastNonEmptyLine: CliScreenLine | null;
|
|
94
|
+
promptLineIndex: number;
|
|
95
|
+
promptLine: CliScreenLine | null;
|
|
96
|
+
linesAbovePrompt: CliScreenLine[];
|
|
97
|
+
linesBelowPrompt: CliScreenLine[];
|
|
98
|
+
}
|
|
99
|
+
|
|
65
100
|
export interface CliScriptInput {
|
|
66
101
|
buffer: string; // Full ANSI-stripped accumulated PTY output
|
|
67
102
|
rawBuffer: string; // Raw PTY output (with ANSI)
|
|
68
103
|
recentBuffer: string; // Recent 1000 chars (ANSI-stripped)
|
|
69
104
|
screenText: string; // Current visible screen snapshot
|
|
105
|
+
screen: CliScreenSnapshot;
|
|
106
|
+
bufferScreen: CliScreenSnapshot;
|
|
107
|
+
recentScreen: CliScreenSnapshot;
|
|
70
108
|
messages: CliChatMessage[]; // Previously parsed messages
|
|
71
109
|
partialResponse: string; // Current partial response being generated
|
|
72
110
|
promptText?: string; // Current turn prompt when available
|
|
111
|
+
settings?: Record<string, any>;
|
|
112
|
+
args?: Record<string, any>;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface CliStatusInput {
|
|
116
|
+
tail: string;
|
|
117
|
+
screenText?: string;
|
|
118
|
+
rawBuffer?: string;
|
|
119
|
+
screen: CliScreenSnapshot;
|
|
120
|
+
tailScreen: CliScreenSnapshot;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface CliApprovalInput {
|
|
124
|
+
buffer: string;
|
|
125
|
+
screenText?: string;
|
|
126
|
+
rawBuffer?: string;
|
|
127
|
+
tail: string;
|
|
128
|
+
screen: CliScreenSnapshot;
|
|
129
|
+
bufferScreen: CliScreenSnapshot;
|
|
130
|
+
tailScreen: CliScreenSnapshot;
|
|
73
131
|
}
|
|
74
132
|
|
|
75
133
|
interface TurnParseScope {
|
|
@@ -174,6 +232,61 @@ function sanitizeTerminalText(str: string): string {
|
|
|
174
232
|
return stripTerminalNoise(stripAnsi(str));
|
|
175
233
|
}
|
|
176
234
|
|
|
235
|
+
function splitCliScreenLines(text: string): string[] {
|
|
236
|
+
return String(text || '')
|
|
237
|
+
.replace(/\u0007/g, '')
|
|
238
|
+
.replace(/\r\n/g, '\n')
|
|
239
|
+
.replace(/\r/g, '\n')
|
|
240
|
+
.split('\n')
|
|
241
|
+
.map((line) => line.replace(/\s+$/, ''));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function isPromptLikeCliLine(line: string): boolean {
|
|
245
|
+
const trimmed = String(line || '').trim();
|
|
246
|
+
if (!trimmed) return false;
|
|
247
|
+
return /^[❯›>]\s*(?:$|\S.*)$/.test(trimmed);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function buildCliScreenSnapshot(text: string): CliScreenSnapshot {
|
|
251
|
+
const normalizedText = String(text || '').replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
252
|
+
const rawLines = splitCliScreenLines(normalizedText);
|
|
253
|
+
const lines = rawLines.map((line, index, arr) => {
|
|
254
|
+
const trimmed = String(line || '').trim();
|
|
255
|
+
return {
|
|
256
|
+
index,
|
|
257
|
+
fromTop: index,
|
|
258
|
+
fromBottom: arr.length - index - 1,
|
|
259
|
+
text: line,
|
|
260
|
+
trimmed,
|
|
261
|
+
isEmpty: trimmed.length === 0,
|
|
262
|
+
};
|
|
263
|
+
});
|
|
264
|
+
const nonEmptyLines = lines.filter((line) => !line.isEmpty);
|
|
265
|
+
const firstNonEmptyLine = nonEmptyLines[0] ?? null;
|
|
266
|
+
const lastNonEmptyLine = nonEmptyLines[nonEmptyLines.length - 1] ?? null;
|
|
267
|
+
let promptLineIndex = -1;
|
|
268
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
269
|
+
if (isPromptLikeCliLine(lines[i].text)) {
|
|
270
|
+
promptLineIndex = i;
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
text: normalizedText,
|
|
276
|
+
lineCount: lines.length,
|
|
277
|
+
lines,
|
|
278
|
+
nonEmptyLines,
|
|
279
|
+
firstNonEmptyLineIndex: firstNonEmptyLine?.index ?? -1,
|
|
280
|
+
lastNonEmptyLineIndex: lastNonEmptyLine?.index ?? -1,
|
|
281
|
+
firstNonEmptyLine,
|
|
282
|
+
lastNonEmptyLine,
|
|
283
|
+
promptLineIndex,
|
|
284
|
+
promptLine: promptLineIndex >= 0 ? lines[promptLineIndex] : null,
|
|
285
|
+
linesAbovePrompt: promptLineIndex >= 0 ? lines.slice(0, promptLineIndex) : [...lines],
|
|
286
|
+
linesBelowPrompt: promptLineIndex >= 0 ? lines.slice(promptLineIndex + 1) : [],
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
177
290
|
// Re-export sanitizeSpawnEnv under the local alias for backward compat within this file
|
|
178
291
|
const buildCliSpawnEnv = sanitizeSpawnEnv;
|
|
179
292
|
|
|
@@ -412,7 +525,9 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
412
525
|
private ready = false;
|
|
413
526
|
private startupBuffer = '';
|
|
414
527
|
private startupParseGate = false;
|
|
528
|
+
private startupSettleTimer: NodeJS.Timeout | null = null;
|
|
415
529
|
private spawnAt = 0;
|
|
530
|
+
private startupFirstOutputAt = 0;
|
|
416
531
|
|
|
417
532
|
// PTY I/O
|
|
418
533
|
private onPtyDataCallback: ((data: string) => void) | null = null;
|
|
@@ -459,8 +574,9 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
459
574
|
// Debug: status transition history
|
|
460
575
|
private statusHistory: { status: string; at: number; trigger?: string }[] = [];
|
|
461
576
|
|
|
462
|
-
|
|
577
|
+
// ─── CLI Scripts (script-based parsing) ───
|
|
463
578
|
private cliScripts: CliScripts;
|
|
579
|
+
private runtimeSettings: Record<string, any> = {};
|
|
464
580
|
/** Full accumulated ANSI-stripped PTY output */
|
|
465
581
|
private accumulatedBuffer: string = '';
|
|
466
582
|
/** Full accumulated raw PTY output (with ANSI) */
|
|
@@ -475,7 +591,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
475
591
|
private traceSessionId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
476
592
|
private static readonly MAX_TRACE_ENTRIES = 250;
|
|
477
593
|
private readonly providerResolutionMeta: Record<string, any>;
|
|
478
|
-
private static readonly IDLE_FINISH_CONFIRM_MS =
|
|
594
|
+
private static readonly IDLE_FINISH_CONFIRM_MS = 2000;
|
|
595
|
+
private static readonly STATUS_ACTIVITY_HOLD_MS = 2000;
|
|
479
596
|
private static readonly FINISH_RETRY_DELAY_MS = 300;
|
|
480
597
|
private static readonly MAX_FINISH_RETRIES = 2;
|
|
481
598
|
|
|
@@ -484,7 +601,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
484
601
|
this.structuredMessages = [...this.committedMessages];
|
|
485
602
|
}
|
|
486
603
|
|
|
487
|
-
private
|
|
604
|
+
private hydrateParsedMessages(parsedMessages: any[], scope?: TurnParseScope | null): any[] {
|
|
488
605
|
const referenceMessages = [...this.committedMessages];
|
|
489
606
|
const usedReferenceIndexes = new Set<number>();
|
|
490
607
|
const now = Date.now();
|
|
@@ -533,14 +650,36 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
533
650
|
? message.timestamp
|
|
534
651
|
: undefined;
|
|
535
652
|
const referenceTimestamp = parsedTimestamp ?? findReferenceTimestamp(role, content, index);
|
|
653
|
+
const fallbackTimestamp = role === 'user'
|
|
654
|
+
? (scope?.startedAt || now)
|
|
655
|
+
: (this.lastOutputAt || scope?.startedAt || now);
|
|
656
|
+
const timestamp = referenceTimestamp ?? fallbackTimestamp;
|
|
536
657
|
return {
|
|
658
|
+
...message,
|
|
537
659
|
role,
|
|
538
660
|
content,
|
|
539
|
-
timestamp
|
|
661
|
+
timestamp,
|
|
662
|
+
receivedAt: typeof message.receivedAt === 'number' && Number.isFinite(message.receivedAt)
|
|
663
|
+
? message.receivedAt
|
|
664
|
+
: timestamp,
|
|
540
665
|
};
|
|
541
666
|
});
|
|
542
667
|
}
|
|
543
668
|
|
|
669
|
+
private normalizeParsedMessages(parsedMessages: any[], scope?: TurnParseScope | null): CliChatMessage[] {
|
|
670
|
+
return this.hydrateParsedMessages(parsedMessages, scope).map((message) => ({
|
|
671
|
+
role: message.role,
|
|
672
|
+
content: message.content,
|
|
673
|
+
timestamp: message.timestamp,
|
|
674
|
+
receivedAt: message.receivedAt,
|
|
675
|
+
kind: message.kind,
|
|
676
|
+
id: message.id,
|
|
677
|
+
index: message.index,
|
|
678
|
+
meta: message.meta,
|
|
679
|
+
senderName: message.senderName,
|
|
680
|
+
}));
|
|
681
|
+
}
|
|
682
|
+
|
|
544
683
|
private sliceFromOffset(text: string, start: number): string {
|
|
545
684
|
if (!text) return '';
|
|
546
685
|
if (!Number.isFinite(start) || start <= 0) return text;
|
|
@@ -555,15 +694,21 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
555
694
|
const rawBuffer = scope
|
|
556
695
|
? (this.sliceFromOffset(this.accumulatedRawBuffer, scope.rawBufferStart) || this.accumulatedRawBuffer)
|
|
557
696
|
: this.accumulatedRawBuffer;
|
|
697
|
+
const screenText = this.terminalScreen.getText();
|
|
698
|
+
const recentBuffer = buffer.slice(-1000) || this.recentOutputBuffer;
|
|
558
699
|
|
|
559
700
|
return {
|
|
560
701
|
buffer,
|
|
561
702
|
rawBuffer,
|
|
562
|
-
recentBuffer
|
|
563
|
-
screenText
|
|
703
|
+
recentBuffer,
|
|
704
|
+
screenText,
|
|
705
|
+
screen: buildCliScreenSnapshot(screenText),
|
|
706
|
+
bufferScreen: buildCliScreenSnapshot(buffer),
|
|
707
|
+
recentScreen: buildCliScreenSnapshot(recentBuffer),
|
|
564
708
|
messages: [...baseMessages],
|
|
565
709
|
partialResponse,
|
|
566
710
|
promptText: scope?.prompt || '',
|
|
711
|
+
settings: { ...this.runtimeSettings },
|
|
567
712
|
};
|
|
568
713
|
}
|
|
569
714
|
|
|
@@ -746,6 +891,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
746
891
|
LOG.info('CLI', `[${this.cliType}] CLI scripts injected: [${scriptNames.join(', ')}]`);
|
|
747
892
|
}
|
|
748
893
|
|
|
894
|
+
updateRuntimeSettings(settings: Record<string, any>): void {
|
|
895
|
+
this.runtimeSettings = { ...settings };
|
|
896
|
+
}
|
|
897
|
+
|
|
749
898
|
// ─── Lifecycle ─────────────────────────────────
|
|
750
899
|
|
|
751
900
|
setServerConn(serverConn: any): void {
|
|
@@ -796,7 +945,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
796
945
|
);
|
|
797
946
|
// On Windows, .cmd/.bat shims cannot be spawned directly — must go through cmd.exe
|
|
798
947
|
const isCmdShim = isWin && /\.(cmd|bat)$/i.test(binaryPath);
|
|
799
|
-
const useShellWin =
|
|
948
|
+
const useShellWin = !!spawnConfig.shell
|
|
949
|
+
|| isCmdShim
|
|
800
950
|
|| !path.isAbsolute(binaryPath)
|
|
801
951
|
|| isScriptBinary(binaryPath);
|
|
802
952
|
const useShell = isWin ? useShellWin : useShellUnix;
|
|
@@ -916,6 +1066,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
916
1066
|
this.spawnAt = Date.now();
|
|
917
1067
|
this.startupParseGate = true;
|
|
918
1068
|
this.startupBuffer = '';
|
|
1069
|
+
this.startupFirstOutputAt = 0;
|
|
1070
|
+
if (this.startupSettleTimer) { clearTimeout(this.startupSettleTimer); this.startupSettleTimer = null; }
|
|
919
1071
|
this.terminalScreen.reset(24, 80);
|
|
920
1072
|
this.pendingTerminalQueryTail = '';
|
|
921
1073
|
this.currentTurnScope = null;
|
|
@@ -926,7 +1078,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
926
1078
|
this.recordTrace('ready', {
|
|
927
1079
|
runtimeMeta: this.getRuntimeMetadata(),
|
|
928
1080
|
});
|
|
929
|
-
this.setStatus('
|
|
1081
|
+
this.setStatus('starting', 'pty_ready');
|
|
1082
|
+
this.scheduleStartupSettleCheck();
|
|
930
1083
|
this.onStatusChange?.();
|
|
931
1084
|
}
|
|
932
1085
|
|
|
@@ -943,6 +1096,9 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
943
1096
|
this.lastScreenSnapshot = normalizedScreenSnapshot;
|
|
944
1097
|
this.lastScreenChangeAt = now;
|
|
945
1098
|
}
|
|
1099
|
+
if (this.startupParseGate && !this.startupFirstOutputAt && (cleanData.trim() || normalizedScreenSnapshot.trim())) {
|
|
1100
|
+
this.startupFirstOutputAt = now;
|
|
1101
|
+
}
|
|
946
1102
|
if (this.idleFinishCandidate && (rawData.length > 0 || cleanData.length > 0)) {
|
|
947
1103
|
this.clearIdleFinishCandidate('new_output');
|
|
948
1104
|
}
|
|
@@ -954,6 +1110,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
954
1110
|
screenText: this.summarizeTraceText(this.terminalScreen.getText(), 1200),
|
|
955
1111
|
});
|
|
956
1112
|
|
|
1113
|
+
if (this.startupParseGate) {
|
|
1114
|
+
this.scheduleStartupSettleCheck();
|
|
1115
|
+
}
|
|
1116
|
+
|
|
957
1117
|
if (this.isWaitingForResponse && cleanData) {
|
|
958
1118
|
this.responseBuffer = (this.responseBuffer + cleanData).slice(-8000);
|
|
959
1119
|
}
|
|
@@ -972,36 +1132,61 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
972
1132
|
this.accumulatedBuffer = (this.accumulatedBuffer + cleanData).slice(-ProviderCliAdapter.MAX_ACCUMULATED_BUFFER);
|
|
973
1133
|
this.accumulatedRawBuffer = (this.accumulatedRawBuffer + rawData).slice(-ProviderCliAdapter.MAX_ACCUMULATED_BUFFER);
|
|
974
1134
|
|
|
975
|
-
|
|
976
|
-
if (this.startupParseGate) {
|
|
977
|
-
this.startupBuffer += cleanData;
|
|
978
|
-
const elapsed = Date.now() - this.spawnAt;
|
|
979
|
-
const screenText = this.terminalScreen.getText() || '';
|
|
980
|
-
const startupModal = this.getStartupConfirmationModal(screenText);
|
|
981
|
-
const scriptStatus = startupModal ? 'waiting_approval' : this.runDetectStatus(this.startupBuffer);
|
|
982
|
-
const hasInteractivePrompt = this.looksLikeVisibleIdlePrompt(screenText);
|
|
983
|
-
const startupStableMs = this.lastScreenChangeAt ? (now - this.lastScreenChangeAt) : 0;
|
|
984
|
-
const isReady = ((scriptStatus === 'idle' || scriptStatus === 'waiting_approval') && hasInteractivePrompt && startupStableMs >= 700)
|
|
985
|
-
|| (!!startupModal && startupStableMs >= 700)
|
|
986
|
-
|| elapsed > 8000
|
|
987
|
-
|| this.startupBuffer.length > 12000;
|
|
988
|
-
|
|
989
|
-
if (isReady) {
|
|
990
|
-
this.startupParseGate = false;
|
|
991
|
-
this.ready = true;
|
|
992
|
-
LOG.info(
|
|
993
|
-
'CLI',
|
|
994
|
-
`[${this.cliType}] Startup ready (${elapsed}ms, scriptStatus=${scriptStatus}, prompt=${hasInteractivePrompt}, stableMs=${startupStableMs}) providerDir=${this.providerResolutionMeta.providerDir || '-'} scriptDir=${this.providerResolutionMeta.scriptDir || '-'} scriptsPath=${this.providerResolutionMeta.scriptsPath || '-'}`
|
|
995
|
-
);
|
|
996
|
-
this.onStatusChange?.();
|
|
997
|
-
}
|
|
998
|
-
// No early return — status detection runs from the start
|
|
999
|
-
}
|
|
1135
|
+
this.resolveStartupState('output');
|
|
1000
1136
|
|
|
1001
1137
|
// ─── Script-based status detection
|
|
1002
1138
|
this.scheduleSettle();
|
|
1003
1139
|
}
|
|
1004
1140
|
|
|
1141
|
+
private resolveStartupState(trigger: string): void {
|
|
1142
|
+
if (!this.startupParseGate) return;
|
|
1143
|
+
|
|
1144
|
+
const now = Date.now();
|
|
1145
|
+
const screenText = this.terminalScreen.getText() || '';
|
|
1146
|
+
const normalizedScreen = normalizeScreenSnapshot(screenText);
|
|
1147
|
+
const hasStartupOutput = !!this.startupFirstOutputAt || !!normalizedScreen.trim();
|
|
1148
|
+
if (!hasStartupOutput) return;
|
|
1149
|
+
|
|
1150
|
+
const stableMs = this.lastScreenChangeAt ? (now - this.lastScreenChangeAt) : 0;
|
|
1151
|
+
if (stableMs < 2000) return;
|
|
1152
|
+
|
|
1153
|
+
const startupModal = this.getStartupConfirmationModal(screenText);
|
|
1154
|
+
this.startupParseGate = false;
|
|
1155
|
+
if (this.startupSettleTimer) {
|
|
1156
|
+
clearTimeout(this.startupSettleTimer);
|
|
1157
|
+
this.startupSettleTimer = null;
|
|
1158
|
+
}
|
|
1159
|
+
this.ready = true;
|
|
1160
|
+
if (startupModal) {
|
|
1161
|
+
this.activeModal = startupModal;
|
|
1162
|
+
this.setStatus('waiting_approval', `startup_ready:${trigger}`);
|
|
1163
|
+
} else {
|
|
1164
|
+
this.setStatus('idle', `startup_ready:${trigger}`);
|
|
1165
|
+
}
|
|
1166
|
+
LOG.info(
|
|
1167
|
+
'CLI',
|
|
1168
|
+
`[${this.cliType}] Startup settled (${trigger}, stableMs=${stableMs}, modal=${!!startupModal}) providerDir=${this.providerResolutionMeta.providerDir || '-'} scriptDir=${this.providerResolutionMeta.scriptDir || '-'} scriptsPath=${this.providerResolutionMeta.scriptsPath || '-'}`
|
|
1169
|
+
);
|
|
1170
|
+
this.onStatusChange?.();
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
private scheduleStartupSettleCheck(): void {
|
|
1174
|
+
if (!this.startupParseGate) return;
|
|
1175
|
+
if (this.startupSettleTimer) clearTimeout(this.startupSettleTimer);
|
|
1176
|
+
|
|
1177
|
+
const now = Date.now();
|
|
1178
|
+
const stableMs = this.lastScreenChangeAt ? (now - this.lastScreenChangeAt) : 0;
|
|
1179
|
+
const delayMs = Math.max(250, 2050 - stableMs);
|
|
1180
|
+
|
|
1181
|
+
this.startupSettleTimer = setTimeout(() => {
|
|
1182
|
+
this.startupSettleTimer = null;
|
|
1183
|
+
this.resolveStartupState('startup_timer');
|
|
1184
|
+
if (this.startupParseGate && (Date.now() - this.spawnAt) < 10000) {
|
|
1185
|
+
this.scheduleStartupSettleCheck();
|
|
1186
|
+
}
|
|
1187
|
+
}, delayMs);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1005
1190
|
private scheduleSettle(): void {
|
|
1006
1191
|
if (this.settleTimer) clearTimeout(this.settleTimer);
|
|
1007
1192
|
const settleEpoch = this.responseEpoch;
|
|
@@ -1053,6 +1238,57 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1053
1238
|
|| /for\s*shortcuts/i.test(text);
|
|
1054
1239
|
}
|
|
1055
1240
|
|
|
1241
|
+
private findLastMatchingLineIndex(lines: string[], predicate: (line: string) => boolean): number {
|
|
1242
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
1243
|
+
if (predicate(lines[index])) return index;
|
|
1244
|
+
}
|
|
1245
|
+
return -1;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
private looksLikeClaudeGeneratingLine(line: string): boolean {
|
|
1249
|
+
const trimmed = String(line || '').trim();
|
|
1250
|
+
if (!trimmed) return false;
|
|
1251
|
+
if (/esc to (cancel|interrupt|stop)/i.test(trimmed)) return true;
|
|
1252
|
+
if (/^[✻✶✳✢✽⠂⠐⠒⠓⠦⠴⠶⠷⠿]+\s+\S+.*\b(?:thinking|thought for \d+s?)\b/i.test(trimmed)) return true;
|
|
1253
|
+
if (/^[✻✶✳✢✽⠂⠐⠒⠓⠦⠴⠶⠷⠿]+\s+[A-Z][A-Za-z-]{3,}ing\b.*(?:…|\.{3})/u.test(trimmed)) return true;
|
|
1254
|
+
if (/^[⏺•]\s+(?:Reading|Writing|Editing|Searching|Inspecting|Planning|Analyzing|Synthesizing|Drafting|Running|Listing|Scanning|Matching)\b.*(?:…|\.{3})/i.test(trimmed)) {
|
|
1255
|
+
return /ctrl\+o to expand/i.test(trimmed)
|
|
1256
|
+
|| /\b\d+\s+(?:file|files|pattern|patterns|director(?:y|ies)|match|matches|result|results)\b/i.test(trimmed);
|
|
1257
|
+
}
|
|
1258
|
+
return false;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
private detectClaudeGeneratingOverride(screenText: string, tail: string): boolean {
|
|
1262
|
+
if (this.cliType !== 'claude-cli') return false;
|
|
1263
|
+
|
|
1264
|
+
const source = sanitizeTerminalText(screenText || tail || '');
|
|
1265
|
+
if (!source.trim()) return false;
|
|
1266
|
+
|
|
1267
|
+
const allLines = source
|
|
1268
|
+
.split(/\r\n|\n|\r/g)
|
|
1269
|
+
.map(line => line.trim())
|
|
1270
|
+
.filter(Boolean);
|
|
1271
|
+
if (allLines.length === 0) return false;
|
|
1272
|
+
|
|
1273
|
+
const recentLines = allLines.slice(-12);
|
|
1274
|
+
const promptIndex = this.findLastMatchingLineIndex(recentLines, (line) => /^[❯›>]\s*$/.test(line));
|
|
1275
|
+
const activeRegion = promptIndex >= 0 ? recentLines.slice(Math.max(0, promptIndex - 2), promptIndex) : recentLines;
|
|
1276
|
+
if (activeRegion.length === 0) return false;
|
|
1277
|
+
|
|
1278
|
+
return activeRegion.some((line) => this.looksLikeClaudeGeneratingLine(line));
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
private refineDetectedStatus(status: string | null, tail: string, screenText?: string): string | null {
|
|
1282
|
+
if (this.startupParseGate) {
|
|
1283
|
+
return this.getStartupConfirmationModal(screenText || '')
|
|
1284
|
+
? 'waiting_approval'
|
|
1285
|
+
: 'starting';
|
|
1286
|
+
}
|
|
1287
|
+
if (status === 'waiting_approval') return status;
|
|
1288
|
+
if (this.detectClaudeGeneratingOverride(screenText || '', tail)) return 'generating';
|
|
1289
|
+
return status;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1056
1292
|
private looksLikeVisibleAssistantCandidate(screenText: string): boolean {
|
|
1057
1293
|
const lines = sanitizeTerminalText(String(screenText || '')).split(/\r\n|\n|\r/g);
|
|
1058
1294
|
for (const line of lines) {
|
|
@@ -1091,6 +1327,13 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1091
1327
|
return quietForMs < 1200 || screenStableMs < 1200 || !commitResult.hasAssistant;
|
|
1092
1328
|
}
|
|
1093
1329
|
|
|
1330
|
+
private hasRecentInteractiveActivity(now: number): boolean {
|
|
1331
|
+
const quietForMs = this.lastNonEmptyOutputAt ? (now - this.lastNonEmptyOutputAt) : Number.MAX_SAFE_INTEGER;
|
|
1332
|
+
const screenStableMs = this.lastScreenChangeAt ? (now - this.lastScreenChangeAt) : Number.MAX_SAFE_INTEGER;
|
|
1333
|
+
return quietForMs < ProviderCliAdapter.STATUS_ACTIVITY_HOLD_MS
|
|
1334
|
+
|| screenStableMs < ProviderCliAdapter.STATUS_ACTIVITY_HOLD_MS;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1094
1337
|
private getStartupConfirmationModal(screenText: string): { message: string; buttons: string[] } | null {
|
|
1095
1338
|
const text = sanitizeTerminalText(String(screenText || ''));
|
|
1096
1339
|
if (!text.trim()) return null;
|
|
@@ -1128,6 +1371,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1128
1371
|
let loggedWait = false;
|
|
1129
1372
|
|
|
1130
1373
|
while (Date.now() - startedAt < maxWaitMs) {
|
|
1374
|
+
this.resolveStartupState('interactive_wait');
|
|
1131
1375
|
const screenText = this.terminalScreen.getText() || '';
|
|
1132
1376
|
const hasPrompt = this.looksLikeVisibleIdlePrompt(screenText);
|
|
1133
1377
|
const stableMs = this.lastScreenChangeAt ? (Date.now() - this.lastScreenChangeAt) : 0;
|
|
@@ -1137,7 +1381,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1137
1381
|
const interactiveReady = hasPrompt
|
|
1138
1382
|
&& stableMs >= 700
|
|
1139
1383
|
&& recentlyOutput >= 350
|
|
1140
|
-
&& status !== 'starting'
|
|
1141
1384
|
&& status !== 'generating';
|
|
1142
1385
|
|
|
1143
1386
|
if (interactiveReady) {
|
|
@@ -1181,6 +1424,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1181
1424
|
}
|
|
1182
1425
|
const tail = this.settledBuffer;
|
|
1183
1426
|
const screenText = this.terminalScreen.getText() || '';
|
|
1427
|
+
this.resolveStartupState('settled');
|
|
1428
|
+
if (this.startupParseGate) {
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1184
1431
|
const startupModal = this.getStartupConfirmationModal(screenText);
|
|
1185
1432
|
const modal = this.runParseApproval(tail) || startupModal;
|
|
1186
1433
|
const rawScriptStatus = this.runDetectStatus(tail);
|
|
@@ -1254,6 +1501,34 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1254
1501
|
clearPendingScriptStatus();
|
|
1255
1502
|
}
|
|
1256
1503
|
|
|
1504
|
+
const recentInteractiveActivity = this.hasRecentInteractiveActivity(now);
|
|
1505
|
+
const shouldHoldGenerating =
|
|
1506
|
+
scriptStatus === 'idle'
|
|
1507
|
+
&& this.isWaitingForResponse
|
|
1508
|
+
&& !modal
|
|
1509
|
+
&& recentInteractiveActivity;
|
|
1510
|
+
|
|
1511
|
+
if (shouldHoldGenerating) {
|
|
1512
|
+
this.clearIdleFinishCandidate('hold_generating_recent_activity');
|
|
1513
|
+
this.setStatus('generating', 'recent_activity_hold');
|
|
1514
|
+
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
1515
|
+
this.idleTimeout = setTimeout(() => {
|
|
1516
|
+
if (this.isWaitingForResponse && this.currentStatus !== 'waiting_approval') {
|
|
1517
|
+
this.finishResponse();
|
|
1518
|
+
}
|
|
1519
|
+
}, this.timeouts.generatingIdle);
|
|
1520
|
+
this.recordTrace('hold_generating_recent_activity', {
|
|
1521
|
+
scriptStatus,
|
|
1522
|
+
recentInteractiveActivity,
|
|
1523
|
+
lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
|
|
1524
|
+
lastScreenChangeAt: this.lastScreenChangeAt,
|
|
1525
|
+
holdMs: ProviderCliAdapter.STATUS_ACTIVITY_HOLD_MS,
|
|
1526
|
+
...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer),
|
|
1527
|
+
});
|
|
1528
|
+
this.onStatusChange?.();
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1257
1532
|
if (scriptStatus === 'waiting_approval') {
|
|
1258
1533
|
this.clearIdleFinishCandidate('waiting_approval');
|
|
1259
1534
|
const inCooldown = this.lastApprovalResolvedAt && (Date.now() - this.lastApprovalResolvedAt) < this.timeouts.approvalCooldown;
|
|
@@ -1333,8 +1608,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1333
1608
|
const screenStableMs = this.lastScreenChangeAt ? (now - this.lastScreenChangeAt) : 0;
|
|
1334
1609
|
const hasAssistantTurn = !!lastParsedAssistant;
|
|
1335
1610
|
const assistantLength = lastParsedAssistant?.content?.length || 0;
|
|
1336
|
-
const idleQuietThresholdMs = Math.max(
|
|
1337
|
-
const idleStableThresholdMs =
|
|
1611
|
+
const idleQuietThresholdMs = Math.max(2000, this.timeouts.outputSettle);
|
|
1612
|
+
const idleStableThresholdMs = 2000;
|
|
1338
1613
|
const idleReady = visibleIdlePrompt
|
|
1339
1614
|
&& !modal
|
|
1340
1615
|
&& hasAssistantTurn
|
|
@@ -1449,7 +1724,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1449
1724
|
this.currentTurnScope,
|
|
1450
1725
|
);
|
|
1451
1726
|
if (parsed && Array.isArray(parsed.messages)) {
|
|
1452
|
-
this.committedMessages = this.normalizeParsedMessages(parsed.messages);
|
|
1727
|
+
this.committedMessages = this.normalizeParsedMessages(parsed.messages, this.currentTurnScope);
|
|
1453
1728
|
const promptForTrim = this.currentTurnScope?.prompt || getLastUserPromptText(this.committedMessages);
|
|
1454
1729
|
if (promptForTrim) {
|
|
1455
1730
|
const lastAssistantForTrim = [...this.committedMessages].reverse().find((message) => message.role === 'assistant');
|
|
@@ -1488,11 +1763,15 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1488
1763
|
private runDetectStatus(text: string): string | null {
|
|
1489
1764
|
if (!this.cliScripts?.detectStatus) return null;
|
|
1490
1765
|
try {
|
|
1491
|
-
|
|
1766
|
+
const screenText = this.terminalScreen.getText();
|
|
1767
|
+
const status = this.cliScripts.detectStatus({
|
|
1492
1768
|
tail: text.slice(-500),
|
|
1493
|
-
screenText
|
|
1769
|
+
screenText,
|
|
1494
1770
|
rawBuffer: this.accumulatedRawBuffer,
|
|
1771
|
+
screen: buildCliScreenSnapshot(screenText),
|
|
1772
|
+
tailScreen: buildCliScreenSnapshot(text.slice(-500)),
|
|
1495
1773
|
});
|
|
1774
|
+
return this.refineDetectedStatus(status, text, screenText || '');
|
|
1496
1775
|
} catch (e: any) {
|
|
1497
1776
|
LOG.warn('CLI', `[${this.cliType}] detectStatus error: ${e.message}`);
|
|
1498
1777
|
return null;
|
|
@@ -1502,11 +1781,16 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1502
1781
|
private runParseApproval(tail: string): { message: string; buttons: string[] } | null {
|
|
1503
1782
|
if (!this.cliScripts?.parseApproval) return null;
|
|
1504
1783
|
try {
|
|
1784
|
+
const screenText = this.terminalScreen.getText();
|
|
1785
|
+
const buffer = screenText || this.accumulatedBuffer;
|
|
1505
1786
|
return this.cliScripts.parseApproval({
|
|
1506
|
-
buffer
|
|
1507
|
-
screenText
|
|
1787
|
+
buffer,
|
|
1788
|
+
screenText,
|
|
1508
1789
|
rawBuffer: this.accumulatedRawBuffer,
|
|
1509
1790
|
tail,
|
|
1791
|
+
screen: buildCliScreenSnapshot(screenText),
|
|
1792
|
+
bufferScreen: buildCliScreenSnapshot(buffer),
|
|
1793
|
+
tailScreen: buildCliScreenSnapshot(tail),
|
|
1510
1794
|
});
|
|
1511
1795
|
} catch (e: any) {
|
|
1512
1796
|
LOG.warn('CLI', `[${this.cliType}] parseApproval error: ${e.message}`);
|
|
@@ -1525,6 +1809,28 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1525
1809
|
};
|
|
1526
1810
|
}
|
|
1527
1811
|
|
|
1812
|
+
seedCommittedMessages(messages: SeedCliChatMessage[]): void {
|
|
1813
|
+
const normalized = (Array.isArray(messages) ? messages : [])
|
|
1814
|
+
.filter((message) => message && (message.role === 'user' || message.role === 'assistant'))
|
|
1815
|
+
.map((message) => ({
|
|
1816
|
+
role: message.role as 'user' | 'assistant',
|
|
1817
|
+
content: typeof message.content === 'string' ? message.content : String(message.content || ''),
|
|
1818
|
+
timestamp: typeof message.timestamp === 'number' && Number.isFinite(message.timestamp)
|
|
1819
|
+
? message.timestamp
|
|
1820
|
+
: undefined,
|
|
1821
|
+
receivedAt: typeof message.receivedAt === 'number' && Number.isFinite(message.receivedAt)
|
|
1822
|
+
? message.receivedAt
|
|
1823
|
+
: undefined,
|
|
1824
|
+
kind: typeof message.kind === 'string' ? message.kind : undefined,
|
|
1825
|
+
id: typeof message.id === 'string' ? message.id : undefined,
|
|
1826
|
+
index: typeof message.index === 'number' ? message.index : undefined,
|
|
1827
|
+
meta: message.meta && typeof message.meta === 'object' ? { ...(message.meta as Record<string, any>) } : undefined,
|
|
1828
|
+
senderName: typeof message.senderName === 'string' ? message.senderName : undefined,
|
|
1829
|
+
}));
|
|
1830
|
+
this.committedMessages = normalized;
|
|
1831
|
+
this.syncMessageViews();
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1528
1834
|
/**
|
|
1529
1835
|
* Script-based full parse — returns ReadChatResult.
|
|
1530
1836
|
* Called by command handler / dashboard for rich content rendering.
|
|
@@ -1535,12 +1841,27 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1535
1841
|
this.responseBuffer,
|
|
1536
1842
|
this.currentTurnScope,
|
|
1537
1843
|
);
|
|
1844
|
+
const shouldPreferCommittedMessages =
|
|
1845
|
+
!this.currentTurnScope
|
|
1846
|
+
&& this.currentStatus === 'idle'
|
|
1847
|
+
&& !this.activeModal;
|
|
1538
1848
|
if (parsed && Array.isArray(parsed.messages)) {
|
|
1849
|
+
const hydratedMessages = shouldPreferCommittedMessages
|
|
1850
|
+
? this.committedMessages.map((message, index) => ({
|
|
1851
|
+
...message,
|
|
1852
|
+
id: (message as any).id || `msg_${index}`,
|
|
1853
|
+
index: typeof (message as any).index === 'number' ? (message as any).index : index,
|
|
1854
|
+
kind: (message as any).kind || 'standard',
|
|
1855
|
+
receivedAt: typeof (message as any).receivedAt === 'number'
|
|
1856
|
+
? (message as any).receivedAt
|
|
1857
|
+
: message.timestamp,
|
|
1858
|
+
}))
|
|
1859
|
+
: this.hydrateParsedMessages(parsed.messages, this.currentTurnScope);
|
|
1539
1860
|
return {
|
|
1540
1861
|
id: parsed.id || 'cli_session',
|
|
1541
1862
|
status: parsed.status || this.currentStatus,
|
|
1542
1863
|
title: parsed.title || this.cliName,
|
|
1543
|
-
messages:
|
|
1864
|
+
messages: hydratedMessages,
|
|
1544
1865
|
activeModal: parsed.activeModal ?? this.activeModal,
|
|
1545
1866
|
providerSessionId: typeof parsed.providerSessionId === 'string' ? parsed.providerSessionId : undefined,
|
|
1546
1867
|
};
|
|
@@ -1563,11 +1884,31 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1563
1884
|
};
|
|
1564
1885
|
}
|
|
1565
1886
|
|
|
1887
|
+
async invokeScript(scriptName: string, args?: Record<string, any>): Promise<any> {
|
|
1888
|
+
const fn = this.cliScripts?.[scriptName];
|
|
1889
|
+
if (typeof fn !== 'function') {
|
|
1890
|
+
throw new Error(`CLI script '${scriptName}' not available`);
|
|
1891
|
+
}
|
|
1892
|
+
const input = this.buildParseInput(
|
|
1893
|
+
this.committedMessages,
|
|
1894
|
+
this.responseBuffer,
|
|
1895
|
+
this.currentTurnScope,
|
|
1896
|
+
);
|
|
1897
|
+
return await Promise.resolve(fn({
|
|
1898
|
+
...input,
|
|
1899
|
+
args: args && typeof args === 'object' ? { ...args } : {},
|
|
1900
|
+
}));
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1566
1903
|
private parseCurrentTranscript(baseMessages: CliChatMessage[], partialResponse: string, scope?: TurnParseScope | null): any {
|
|
1567
1904
|
if (!this.cliScripts?.parseOutput) return null;
|
|
1568
1905
|
try {
|
|
1569
1906
|
const input = this.buildParseInput(baseMessages, partialResponse, scope);
|
|
1570
1907
|
const parsed = this.cliScripts.parseOutput(input);
|
|
1908
|
+
const refinedStatus = this.refineDetectedStatus(typeof parsed?.status === 'string' ? parsed.status : null, input.recentBuffer, input.screenText);
|
|
1909
|
+
if (parsed && refinedStatus && parsed.status !== refinedStatus) {
|
|
1910
|
+
parsed.status = refinedStatus;
|
|
1911
|
+
}
|
|
1571
1912
|
const promptForTrim = scope?.prompt || getLastUserPromptText(baseMessages);
|
|
1572
1913
|
if (parsed && Array.isArray(parsed.messages) && promptForTrim) {
|
|
1573
1914
|
const lastAssistant = [...parsed.messages].reverse().find((message: any) => message?.role === 'assistant' && typeof message.content === 'string');
|
|
@@ -1614,12 +1955,23 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1614
1955
|
if (this.startupParseGate) {
|
|
1615
1956
|
const deadline = Date.now() + 10000;
|
|
1616
1957
|
while (this.startupParseGate && Date.now() < deadline) {
|
|
1958
|
+
this.resolveStartupState('send_wait');
|
|
1617
1959
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
1618
1960
|
}
|
|
1619
1961
|
}
|
|
1962
|
+
await this.waitForInteractivePrompt();
|
|
1963
|
+
if (!this.ready) {
|
|
1964
|
+
this.resolveStartupState('send_precheck');
|
|
1965
|
+
const screenText = this.terminalScreen.getText() || '';
|
|
1966
|
+
const hasPrompt = this.looksLikeVisibleIdlePrompt(screenText);
|
|
1967
|
+
if (hasPrompt && this.currentStatus === 'idle') {
|
|
1968
|
+
this.ready = true;
|
|
1969
|
+
this.startupParseGate = false;
|
|
1970
|
+
LOG.info('CLI', `[${this.cliType}] sendMessage recovered idle prompt readiness`);
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1620
1973
|
if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
|
|
1621
1974
|
if (this.isWaitingForResponse) return;
|
|
1622
|
-
await this.waitForInteractivePrompt();
|
|
1623
1975
|
const blockingModal = this.activeModal || this.getStartupConfirmationModal(this.terminalScreen.getText() || '');
|
|
1624
1976
|
if (blockingModal || this.currentStatus === 'waiting_approval') {
|
|
1625
1977
|
throw new Error(`${this.cliName} is awaiting confirmation before it can accept a prompt`);
|
|
@@ -1661,8 +2013,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1661
2013
|
}
|
|
1662
2014
|
this.responseEpoch += 1;
|
|
1663
2015
|
this.responseSettleIgnoreUntil = Date.now() + submitDelayMs + this.timeouts.outputSettle + 250;
|
|
1664
|
-
this.setStatus('generating', 'sendMessage');
|
|
1665
|
-
this.onStatusChange?.();
|
|
1666
2016
|
const startResponseTimeout = () => {
|
|
1667
2017
|
if (this.responseTimeout) clearTimeout(this.responseTimeout);
|
|
1668
2018
|
this.responseTimeout = setTimeout(() => {
|
|
@@ -1682,7 +2032,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1682
2032
|
const retrySubmitIfStuck = (attempt: number) => {
|
|
1683
2033
|
this.submitRetryTimer = null;
|
|
1684
2034
|
if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
|
|
1685
|
-
if (this.currentStatus
|
|
2035
|
+
if (this.currentStatus === 'waiting_approval') return;
|
|
1686
2036
|
if ((this.responseBuffer || '').trim()) return;
|
|
1687
2037
|
const screenText = this.terminalScreen.getText();
|
|
1688
2038
|
if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
|
|
@@ -1718,7 +2068,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1718
2068
|
this.submitRetryTimer = setTimeout(() => {
|
|
1719
2069
|
this.submitRetryTimer = null;
|
|
1720
2070
|
if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
|
|
1721
|
-
if (this.currentStatus
|
|
2071
|
+
if (this.currentStatus === 'waiting_approval') return;
|
|
1722
2072
|
if ((this.responseBuffer || '').trim()) return;
|
|
1723
2073
|
const screenText = this.terminalScreen.getText();
|
|
1724
2074
|
if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
|