@adhdev/daemon-core 0.7.46 → 0.8.0
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 +33 -0
- package/dist/cli-adapters/session-host-transport.d.ts +1 -0
- package/dist/config/chat-history.d.ts +3 -0
- package/dist/config/config.d.ts +1 -1
- package/dist/daemon/dev-auto-implement.d.ts +18 -2
- package/dist/daemon/dev-cli-debug.d.ts +82 -0
- package/dist/daemon/dev-server.d.ts +7 -0
- package/dist/index.js +1625 -191
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1625 -191
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +5 -0
- package/dist/providers/contracts.d.ts +8 -0
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +417 -6
- package/src/cli-adapters/session-host-transport.ts +13 -1
- package/src/commands/chat-commands.ts +8 -0
- package/src/config/chat-history.ts +5 -1
- package/src/config/config.ts +2 -2
- package/src/daemon/dev-auto-implement.ts +371 -38
- package/src/daemon/dev-cli-debug.ts +839 -0
- package/src/daemon/dev-server.ts +29 -1
- package/src/providers/cli-provider-instance.ts +79 -1
- package/src/providers/contracts.ts +8 -0
- package/src/providers/provider-loader.ts +39 -0
|
@@ -100,6 +100,24 @@ interface TurnParseScope {
|
|
|
100
100
|
rawBufferStart: number;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
interface IdleFinishCandidate {
|
|
104
|
+
armedAt: number;
|
|
105
|
+
lastOutputAt: number;
|
|
106
|
+
lastScreenChangeAt: number;
|
|
107
|
+
responseEpoch: number;
|
|
108
|
+
assistantLength: number;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface CliTraceEntry {
|
|
112
|
+
id: number;
|
|
113
|
+
at: number;
|
|
114
|
+
type: string;
|
|
115
|
+
status: CliSessionStatus['status'];
|
|
116
|
+
isWaitingForResponse: boolean;
|
|
117
|
+
activeModal: { message: string; buttons: string[] } | null;
|
|
118
|
+
payload: Record<string, any>;
|
|
119
|
+
}
|
|
120
|
+
|
|
103
121
|
export interface CliProviderModule {
|
|
104
122
|
type: string;
|
|
105
123
|
name: string;
|
|
@@ -140,13 +158,15 @@ export interface CliProviderModule {
|
|
|
140
158
|
function stripAnsi(str: string): string {
|
|
141
159
|
// eslint-disable-next-line no-control-regex
|
|
142
160
|
return str
|
|
161
|
+
// OSC sequences (title bar etc) — strip before generic ESC removal so payload cannot leak.
|
|
162
|
+
.replace(/\x1B\][^\x07]*\x07/g, '')
|
|
163
|
+
.replace(/\x1B\][\s\S]*?\x1B\\/g, '')
|
|
164
|
+
// DCS / APC / PM / SOS control strings terminated by ST or BEL.
|
|
165
|
+
.replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, '')
|
|
143
166
|
// Cursor movement sequences → space (prevents word concatenation)
|
|
144
167
|
.replace(/\x1B\[\d*[A-HJKSTfG]/g, ' ')
|
|
145
168
|
// SGR and other CSI sequences → remove
|
|
146
169
|
.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '')
|
|
147
|
-
// OSC sequences (title bar etc)
|
|
148
|
-
.replace(/\x1B\][^\x07]*\x07/g, '')
|
|
149
|
-
.replace(/\x1B\][^\x1B]*\x1B\\/g, '')
|
|
150
170
|
// Collapse multiple spaces
|
|
151
171
|
.replace(/ +/g, ' ');
|
|
152
172
|
}
|
|
@@ -160,6 +180,11 @@ function stripTerminalNoise(str: string): string {
|
|
|
160
180
|
.replace(/(^|[\s([])(?:\??\d{1,4}(?:;\d{1,4})*[A-Za-z])(?=$|[\s)\]])/g, '$1')
|
|
161
181
|
.replace(/(^|[\s([])(?:\[\??\d{1,4}(?:;\d{1,4})*[A-Za-z])(?=$|[\s)\]])/g, '$1')
|
|
162
182
|
.replace(/(^|[\s([])(?:\d{1,4};\?)(?=$|[\s)\]])/g, '$1')
|
|
183
|
+
// Drop common leftover DCS/OSC payload fragments when a control string was split across PTY chunks.
|
|
184
|
+
.replace(/(^|[\s([])(?:\d+\$r[0-9;\" ]*[A-Za-z]?)(?=$|[\s)\]])/g, '$1')
|
|
185
|
+
.replace(/(^|[\s([])(?:>\|[A-Za-z0-9_.:-]+(?:\([^)]*\))?)(?=$|[\s)\]])/g, '$1')
|
|
186
|
+
.replace(/(^|[\s([])(?:[A-Z]\d(?:\s+[A-Z]\d)+)(?=$|[\s)\]])/g, '$1')
|
|
187
|
+
.replace(/(^|[\s([])(?:\d+;[^\s)\]]+)(?=$|[\s)\]])/g, '$1')
|
|
163
188
|
.replace(/\r+/g, '\n')
|
|
164
189
|
.replace(/[ \t]+\n/g, '\n')
|
|
165
190
|
.replace(/\n{3,}/g, '\n\n')
|
|
@@ -326,6 +351,12 @@ function promptLikelyVisible(screenText: string, promptSnippet: string): boolean
|
|
|
326
351
|
return matched >= required;
|
|
327
352
|
}
|
|
328
353
|
|
|
354
|
+
function normalizeScreenSnapshot(text: string): string {
|
|
355
|
+
return sanitizeTerminalText(String(text || ''))
|
|
356
|
+
.replace(/\s+/g, ' ')
|
|
357
|
+
.trim();
|
|
358
|
+
}
|
|
359
|
+
|
|
329
360
|
/**
|
|
330
361
|
* Normalize provider.json for auto-implement approval detection.
|
|
331
362
|
* Kept for backward compat with dev-server auto-impl pipeline only.
|
|
@@ -395,6 +426,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
395
426
|
private ptyOutputBuffer = '';
|
|
396
427
|
private ptyOutputFlushTimer: NodeJS.Timeout | null = null;
|
|
397
428
|
private pendingTerminalQueryTail = '';
|
|
429
|
+
private lastOutputAt = 0;
|
|
430
|
+
private lastNonEmptyOutputAt = 0;
|
|
431
|
+
private lastScreenChangeAt = 0;
|
|
432
|
+
private lastScreenSnapshot = '';
|
|
398
433
|
|
|
399
434
|
// Server log forwarding
|
|
400
435
|
private serverConn: any = null;
|
|
@@ -419,6 +454,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
419
454
|
private submitRetryTimer: NodeJS.Timeout | null = null;
|
|
420
455
|
private submitRetryUsed = false;
|
|
421
456
|
private submitRetryPromptSnippet = '';
|
|
457
|
+
private idleFinishCandidate: IdleFinishCandidate | null = null;
|
|
422
458
|
|
|
423
459
|
// Resize redraw suppression
|
|
424
460
|
private resizeSuppressUntil: number = 0;
|
|
@@ -437,6 +473,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
437
473
|
/** Max accumulated buffer size (last 50KB) */
|
|
438
474
|
private static readonly MAX_ACCUMULATED_BUFFER = 50000;
|
|
439
475
|
private currentTurnScope: TurnParseScope | null = null;
|
|
476
|
+
private traceEntries: CliTraceEntry[] = [];
|
|
477
|
+
private traceSeq = 0;
|
|
478
|
+
private traceSessionId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
479
|
+
private static readonly MAX_TRACE_ENTRIES = 250;
|
|
480
|
+
private readonly providerResolutionMeta: Record<string, any>;
|
|
481
|
+
private static readonly IDLE_FINISH_CONFIRM_MS = 900;
|
|
440
482
|
|
|
441
483
|
private syncMessageViews(): void {
|
|
442
484
|
this.messages = [...this.committedMessages];
|
|
@@ -486,9 +528,103 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
486
528
|
this.currentStatus = status;
|
|
487
529
|
this.statusHistory.push({ status, at: Date.now(), trigger });
|
|
488
530
|
if (this.statusHistory.length > 50) this.statusHistory.shift();
|
|
531
|
+
this.recordTrace('status', {
|
|
532
|
+
previousStatus: prev,
|
|
533
|
+
trigger: trigger || null,
|
|
534
|
+
});
|
|
489
535
|
LOG.info('CLI', `[${this.cliType}] status: ${prev} → ${status}${trigger ? ` (${trigger})` : ''}`);
|
|
490
536
|
}
|
|
491
537
|
|
|
538
|
+
private clearIdleFinishCandidate(reason: string): void {
|
|
539
|
+
if (!this.idleFinishCandidate) return;
|
|
540
|
+
this.recordTrace('idle_candidate_reset', {
|
|
541
|
+
reason,
|
|
542
|
+
candidate: this.idleFinishCandidate,
|
|
543
|
+
});
|
|
544
|
+
this.idleFinishCandidate = null;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
private armIdleFinishCandidate(assistantLength: number): void {
|
|
548
|
+
const now = Date.now();
|
|
549
|
+
this.idleFinishCandidate = {
|
|
550
|
+
armedAt: now,
|
|
551
|
+
lastOutputAt: this.lastOutputAt,
|
|
552
|
+
lastScreenChangeAt: this.lastScreenChangeAt,
|
|
553
|
+
responseEpoch: this.responseEpoch,
|
|
554
|
+
assistantLength,
|
|
555
|
+
};
|
|
556
|
+
this.recordTrace('idle_candidate_armed', {
|
|
557
|
+
confirmMs: ProviderCliAdapter.IDLE_FINISH_CONFIRM_MS,
|
|
558
|
+
candidate: this.idleFinishCandidate,
|
|
559
|
+
...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer),
|
|
560
|
+
});
|
|
561
|
+
if (this.settleTimer) clearTimeout(this.settleTimer);
|
|
562
|
+
this.settleTimer = setTimeout(() => {
|
|
563
|
+
this.settleTimer = null;
|
|
564
|
+
this.settledBuffer = this.recentOutputBuffer;
|
|
565
|
+
this.evaluateSettled();
|
|
566
|
+
}, ProviderCliAdapter.IDLE_FINISH_CONFIRM_MS);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
private summarizeTraceText(text: string, max = 800): string {
|
|
570
|
+
const value = sanitizeTerminalText(String(text || ''));
|
|
571
|
+
if (value.length <= max) return value;
|
|
572
|
+
return `…${value.slice(-max)}`;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
private summarizeTraceMessages(messages: CliChatMessage[], limit = 3): { role: string; content: string; timestamp?: number }[] {
|
|
576
|
+
return messages.slice(-limit).map((message) => ({
|
|
577
|
+
role: message.role,
|
|
578
|
+
content: this.summarizeTraceText(message.content, 240),
|
|
579
|
+
timestamp: message.timestamp,
|
|
580
|
+
}));
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
private buildTraceParseSnapshot(scope?: TurnParseScope | null, partialResponse = ''): Record<string, any> {
|
|
584
|
+
const scopedBuffer = scope
|
|
585
|
+
? (this.sliceFromOffset(this.accumulatedBuffer, scope.bufferStart) || this.accumulatedBuffer)
|
|
586
|
+
: this.accumulatedBuffer;
|
|
587
|
+
const scopedRawBuffer = scope
|
|
588
|
+
? (this.sliceFromOffset(this.accumulatedRawBuffer, scope.rawBufferStart) || this.accumulatedRawBuffer)
|
|
589
|
+
: this.accumulatedRawBuffer;
|
|
590
|
+
return {
|
|
591
|
+
currentTurnScope: scope || null,
|
|
592
|
+
responseBuffer: this.summarizeTraceText(this.responseBuffer, 1200),
|
|
593
|
+
partialResponse: this.summarizeTraceText(partialResponse || this.responseBuffer, 1200),
|
|
594
|
+
turnBuffer: this.summarizeTraceText(scopedBuffer, 1600),
|
|
595
|
+
turnRawPreview: this.summarizeTraceText(scopedRawBuffer, 1600),
|
|
596
|
+
turnSanitizedRawPreview: this.summarizeTraceText(sanitizeTerminalText(scopedRawBuffer), 1600),
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
private recordTrace(type: string, payload: Record<string, any> = {}): void {
|
|
601
|
+
const entry: CliTraceEntry = {
|
|
602
|
+
id: ++this.traceSeq,
|
|
603
|
+
at: Date.now(),
|
|
604
|
+
type,
|
|
605
|
+
status: this.currentStatus,
|
|
606
|
+
isWaitingForResponse: this.isWaitingForResponse,
|
|
607
|
+
activeModal: this.activeModal
|
|
608
|
+
? { message: this.activeModal.message, buttons: [...this.activeModal.buttons] }
|
|
609
|
+
: null,
|
|
610
|
+
payload,
|
|
611
|
+
};
|
|
612
|
+
this.traceEntries.push(entry);
|
|
613
|
+
if (this.traceEntries.length > ProviderCliAdapter.MAX_TRACE_ENTRIES) {
|
|
614
|
+
this.traceEntries.splice(0, this.traceEntries.length - ProviderCliAdapter.MAX_TRACE_ENTRIES);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
private resetTraceSession(): void {
|
|
619
|
+
this.traceEntries = [];
|
|
620
|
+
this.traceSeq = 0;
|
|
621
|
+
this.traceSessionId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
622
|
+
this.recordTrace('session_start', {
|
|
623
|
+
providerType: this.cliType,
|
|
624
|
+
workingDir: this.workingDir,
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
|
|
492
628
|
// Resolved timeouts
|
|
493
629
|
private readonly timeouts: Required<NonNullable<CliProviderModule['timeouts']>>;
|
|
494
630
|
|
|
@@ -532,12 +668,27 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
532
668
|
? (provider as any).sendKey
|
|
533
669
|
: '\r';
|
|
534
670
|
this.submitStrategy = (provider as any).submitStrategy === 'immediate' ? 'immediate' : 'wait_for_echo';
|
|
671
|
+
this.providerResolutionMeta = {
|
|
672
|
+
type: provider.type,
|
|
673
|
+
name: provider.name,
|
|
674
|
+
resolvedVersion: (provider as any)._resolvedVersion || null,
|
|
675
|
+
resolvedOs: (provider as any)._resolvedOs || null,
|
|
676
|
+
providerDir: (provider as any)._resolvedProviderDir || null,
|
|
677
|
+
scriptDir: (provider as any)._resolvedScriptDir || null,
|
|
678
|
+
scriptsPath: (provider as any)._resolvedScriptsPath || null,
|
|
679
|
+
scriptsSource: (provider as any)._resolvedScriptsSource || null,
|
|
680
|
+
versionWarning: (provider as any)._versionWarning || null,
|
|
681
|
+
};
|
|
535
682
|
|
|
536
683
|
// Scripts are required — loaded by ProviderLoader via compatibility array
|
|
537
684
|
this.cliScripts = (provider as any).scripts || {};
|
|
538
685
|
const scriptNames = Object.keys(this.cliScripts).filter(k => typeof (this.cliScripts as any)[k] === 'function');
|
|
539
686
|
if (scriptNames.length > 0) {
|
|
540
687
|
LOG.info('CLI', `[${this.cliType}] CLI scripts: [${scriptNames.join(', ')}]`);
|
|
688
|
+
LOG.info(
|
|
689
|
+
'CLI',
|
|
690
|
+
`[${this.cliType}] Provider resolution: providerDir=${this.providerResolutionMeta.providerDir || '-'} scriptDir=${this.providerResolutionMeta.scriptDir || '-'} scriptsPath=${this.providerResolutionMeta.scriptsPath || '-'} source=${this.providerResolutionMeta.scriptsSource || '-'} version=${this.providerResolutionMeta.resolvedVersion || '-'}`
|
|
691
|
+
);
|
|
541
692
|
} else {
|
|
542
693
|
LOG.warn('CLI', `[${this.cliType}] ⚠ No CLI scripts loaded! Provider needs scripts/{version}/scripts.js`);
|
|
543
694
|
}
|
|
@@ -588,6 +739,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
588
739
|
const allArgs = [...spawnConfig.args, ...this.extraArgs];
|
|
589
740
|
|
|
590
741
|
LOG.info('CLI', `[${this.cliType}] Spawning in ${this.workingDir}`);
|
|
742
|
+
this.resetTraceSession();
|
|
591
743
|
|
|
592
744
|
let shellCmd: string;
|
|
593
745
|
let shellArgs: string[];
|
|
@@ -625,6 +777,14 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
625
777
|
cwd: this.workingDir,
|
|
626
778
|
env: buildCliSpawnEnv(process.env, spawnConfig.env),
|
|
627
779
|
};
|
|
780
|
+
this.recordTrace('spawn', {
|
|
781
|
+
shellCommand: shellCmd,
|
|
782
|
+
shellArgs,
|
|
783
|
+
cwd: ptyOpts.cwd,
|
|
784
|
+
cols: ptyOpts.cols,
|
|
785
|
+
rows: ptyOpts.rows,
|
|
786
|
+
providerResolution: this.providerResolutionMeta,
|
|
787
|
+
});
|
|
628
788
|
|
|
629
789
|
try {
|
|
630
790
|
this.ptyProcess = this.transportFactory.spawn(shellCmd, shellArgs, ptyOpts);
|
|
@@ -673,6 +833,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
673
833
|
this.ptyProcess.onExit(({ exitCode }: { exitCode: number }) => {
|
|
674
834
|
LOG.info('CLI', `[${this.cliType}] Exit code ${exitCode}`);
|
|
675
835
|
this.flushPendingOutputParse();
|
|
836
|
+
this.recordTrace('exit', { exitCode });
|
|
676
837
|
this.ptyProcess = null;
|
|
677
838
|
this.setStatus('stopped', 'pty_exit');
|
|
678
839
|
this.ready = false;
|
|
@@ -689,6 +850,9 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
689
850
|
this.currentTurnScope = null;
|
|
690
851
|
this.ready = false;
|
|
691
852
|
await this.ptyProcess.ready;
|
|
853
|
+
this.recordTrace('ready', {
|
|
854
|
+
runtimeMeta: this.getRuntimeMetadata(),
|
|
855
|
+
});
|
|
692
856
|
this.setStatus('idle', 'pty_ready');
|
|
693
857
|
this.onStatusChange?.();
|
|
694
858
|
}
|
|
@@ -698,6 +862,24 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
698
862
|
private handleOutput(rawData: string): void {
|
|
699
863
|
this.terminalScreen.write(rawData);
|
|
700
864
|
const cleanData = sanitizeTerminalText(rawData);
|
|
865
|
+
const now = Date.now();
|
|
866
|
+
const normalizedScreenSnapshot = normalizeScreenSnapshot(this.terminalScreen.getText());
|
|
867
|
+
this.lastOutputAt = now;
|
|
868
|
+
if (cleanData.trim()) this.lastNonEmptyOutputAt = now;
|
|
869
|
+
if (normalizedScreenSnapshot !== this.lastScreenSnapshot) {
|
|
870
|
+
this.lastScreenSnapshot = normalizedScreenSnapshot;
|
|
871
|
+
this.lastScreenChangeAt = now;
|
|
872
|
+
}
|
|
873
|
+
if (this.idleFinishCandidate && (rawData.length > 0 || cleanData.length > 0)) {
|
|
874
|
+
this.clearIdleFinishCandidate('new_output');
|
|
875
|
+
}
|
|
876
|
+
this.recordTrace('output', {
|
|
877
|
+
rawLength: rawData.length,
|
|
878
|
+
cleanLength: cleanData.length,
|
|
879
|
+
rawPreview: this.summarizeTraceText(rawData, 300),
|
|
880
|
+
cleanPreview: this.summarizeTraceText(cleanData, 300),
|
|
881
|
+
screenText: this.summarizeTraceText(this.terminalScreen.getText(), 1200),
|
|
882
|
+
});
|
|
701
883
|
|
|
702
884
|
if (this.isWaitingForResponse && cleanData) {
|
|
703
885
|
this.responseBuffer = (this.responseBuffer + cleanData).slice(-8000);
|
|
@@ -722,15 +904,20 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
722
904
|
this.startupBuffer += cleanData;
|
|
723
905
|
const elapsed = Date.now() - this.spawnAt;
|
|
724
906
|
const scriptStatus = this.runDetectStatus(this.startupBuffer);
|
|
725
|
-
const
|
|
726
|
-
|
|
907
|
+
const screenText = this.terminalScreen.getText() || '';
|
|
908
|
+
const hasInteractivePrompt = this.looksLikeVisibleIdlePrompt(screenText);
|
|
909
|
+
const startupStableMs = this.lastScreenChangeAt ? (now - this.lastScreenChangeAt) : 0;
|
|
910
|
+
const isReady = ((scriptStatus === 'idle' || scriptStatus === 'waiting_approval') && hasInteractivePrompt && startupStableMs >= 700)
|
|
727
911
|
|| elapsed > 8000
|
|
728
912
|
|| this.startupBuffer.length > 12000;
|
|
729
913
|
|
|
730
914
|
if (isReady) {
|
|
731
915
|
this.startupParseGate = false;
|
|
732
916
|
this.ready = true;
|
|
733
|
-
LOG.info(
|
|
917
|
+
LOG.info(
|
|
918
|
+
'CLI',
|
|
919
|
+
`[${this.cliType}] Startup ready (${elapsed}ms, scriptStatus=${scriptStatus}, prompt=${hasInteractivePrompt}, stableMs=${startupStableMs}) providerDir=${this.providerResolutionMeta.providerDir || '-'} scriptDir=${this.providerResolutionMeta.scriptDir || '-'} scriptsPath=${this.providerResolutionMeta.scriptsPath || '-'}`
|
|
920
|
+
);
|
|
734
921
|
this.onStatusChange?.();
|
|
735
922
|
}
|
|
736
923
|
// No early return — status detection runs from the start
|
|
@@ -789,6 +976,50 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
789
976
|
|| /for\s*shortcuts/i.test(text);
|
|
790
977
|
}
|
|
791
978
|
|
|
979
|
+
private async waitForInteractivePrompt(maxWaitMs = 5000): Promise<void> {
|
|
980
|
+
const startedAt = Date.now();
|
|
981
|
+
let loggedWait = false;
|
|
982
|
+
|
|
983
|
+
while (Date.now() - startedAt < maxWaitMs) {
|
|
984
|
+
const screenText = this.terminalScreen.getText() || '';
|
|
985
|
+
const hasPrompt = this.looksLikeVisibleIdlePrompt(screenText);
|
|
986
|
+
const stableMs = this.lastScreenChangeAt ? (Date.now() - this.lastScreenChangeAt) : 0;
|
|
987
|
+
const recentlyOutput = this.lastNonEmptyOutputAt ? (Date.now() - this.lastNonEmptyOutputAt) : Number.MAX_SAFE_INTEGER;
|
|
988
|
+
const status = this.runDetectStatus(this.recentOutputBuffer) || this.currentStatus;
|
|
989
|
+
const startupLikelyActive = /Welcome back|Tips for getting|Recent activity|Claude Code v\d/i.test(screenText);
|
|
990
|
+
const interactiveReady = hasPrompt
|
|
991
|
+
&& stableMs >= 700
|
|
992
|
+
&& recentlyOutput >= 350
|
|
993
|
+
&& status !== 'starting'
|
|
994
|
+
&& status !== 'generating';
|
|
995
|
+
|
|
996
|
+
if (interactiveReady) {
|
|
997
|
+
if (loggedWait) {
|
|
998
|
+
LOG.info(
|
|
999
|
+
'CLI',
|
|
1000
|
+
`[${this.cliType}] Interactive prompt ready after ${Date.now() - startedAt}ms (stableMs=${stableMs}, recentOutputMs=${recentlyOutput}, startup=${startupLikelyActive})`
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
if (!loggedWait && (Date.now() - startedAt) >= 400) {
|
|
1007
|
+
loggedWait = true;
|
|
1008
|
+
LOG.info(
|
|
1009
|
+
'CLI',
|
|
1010
|
+
`[${this.cliType}] Waiting for interactive prompt: hasPrompt=${hasPrompt} stableMs=${stableMs} recentOutputMs=${recentlyOutput} status=${status} startup=${startupLikelyActive} screen=${JSON.stringify(this.summarizeTraceText(screenText, 220)).slice(0, 260)}`
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
const finalScreenText = this.terminalScreen.getText() || '';
|
|
1017
|
+
LOG.warn(
|
|
1018
|
+
'CLI',
|
|
1019
|
+
`[${this.cliType}] Interactive prompt wait timed out after ${maxWaitMs}ms; proceeding with screen=${JSON.stringify(this.summarizeTraceText(finalScreenText, 240)).slice(0, 280)}`
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
792
1023
|
private evaluateSettled(): void {
|
|
793
1024
|
const now = Date.now();
|
|
794
1025
|
if (this.submitPendingUntil > now || this.responseSettleIgnoreUntil > now) {
|
|
@@ -807,6 +1038,32 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
807
1038
|
const rawScriptStatus = this.runDetectStatus(tail);
|
|
808
1039
|
// detectStatus is the sole authority for status. parseApproval only enriches modal info.
|
|
809
1040
|
const scriptStatus = rawScriptStatus;
|
|
1041
|
+
const parsedTranscript = this.parseCurrentTranscript(
|
|
1042
|
+
this.committedMessages,
|
|
1043
|
+
this.responseBuffer,
|
|
1044
|
+
this.currentTurnScope,
|
|
1045
|
+
);
|
|
1046
|
+
const parsedMessages = Array.isArray(parsedTranscript?.messages)
|
|
1047
|
+
? this.normalizeParsedMessages(parsedTranscript.messages)
|
|
1048
|
+
: [];
|
|
1049
|
+
const lastParsedAssistant = [...parsedMessages].reverse().find((message) => message.role === 'assistant');
|
|
1050
|
+
this.recordTrace('settled', {
|
|
1051
|
+
tail: this.summarizeTraceText(tail, 500),
|
|
1052
|
+
screenText: this.summarizeTraceText(screenText, 1200),
|
|
1053
|
+
detectStatus: scriptStatus,
|
|
1054
|
+
parsedStatus: parsedTranscript?.status || null,
|
|
1055
|
+
parsedMessageCount: parsedMessages.length,
|
|
1056
|
+
parsedLastAssistant: lastParsedAssistant ? this.summarizeTraceText(lastParsedAssistant.content, 280) : '',
|
|
1057
|
+
parsedActiveModal: parsedTranscript?.activeModal ?? null,
|
|
1058
|
+
approval: modal,
|
|
1059
|
+
...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer),
|
|
1060
|
+
});
|
|
1061
|
+
if (this.currentTurnScope && !lastParsedAssistant) {
|
|
1062
|
+
LOG.info(
|
|
1063
|
+
'CLI',
|
|
1064
|
+
`[${this.cliType}] Settled without assistant: prompt=${JSON.stringify(this.currentTurnScope.prompt).slice(0, 140)} responseBuffer=${JSON.stringify(this.summarizeTraceText(this.responseBuffer, 220)).slice(0, 260)} screen=${JSON.stringify(this.summarizeTraceText(screenText, 220)).slice(0, 260)} providerDir=${this.providerResolutionMeta.providerDir || '-'} scriptDir=${this.providerResolutionMeta.scriptDir || '-'}`
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
810
1067
|
if (!scriptStatus) return;
|
|
811
1068
|
|
|
812
1069
|
const prevStatus = this.currentStatus;
|
|
@@ -850,6 +1107,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
850
1107
|
}
|
|
851
1108
|
|
|
852
1109
|
if (scriptStatus === 'waiting_approval') {
|
|
1110
|
+
this.clearIdleFinishCandidate('waiting_approval');
|
|
853
1111
|
const inCooldown = this.lastApprovalResolvedAt && (Date.now() - this.lastApprovalResolvedAt) < this.timeouts.approvalCooldown;
|
|
854
1112
|
const visibleIdlePrompt = this.looksLikeVisibleIdlePrompt(screenText);
|
|
855
1113
|
if ((inCooldown || visibleIdlePrompt) && !modal) {
|
|
@@ -884,6 +1142,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
884
1142
|
}
|
|
885
1143
|
|
|
886
1144
|
if (scriptStatus === 'generating') {
|
|
1145
|
+
this.clearIdleFinishCandidate('generating');
|
|
887
1146
|
const effectiveScreenText = screenText || this.accumulatedBuffer;
|
|
888
1147
|
const noActiveTurn = !this.currentTurnScope;
|
|
889
1148
|
const looksIdleChrome = /(^|\n)\s*[❯›>]\s*(?:\n|$)/m.test(effectiveScreenText)
|
|
@@ -921,13 +1180,71 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
921
1180
|
this.lastApprovalResolvedAt = Date.now();
|
|
922
1181
|
}
|
|
923
1182
|
if (this.isWaitingForResponse) {
|
|
1183
|
+
const visibleIdlePrompt = this.looksLikeVisibleIdlePrompt(screenText);
|
|
1184
|
+
const quietForMs = this.lastNonEmptyOutputAt ? (now - this.lastNonEmptyOutputAt) : Number.MAX_SAFE_INTEGER;
|
|
1185
|
+
const screenStableMs = this.lastScreenChangeAt ? (now - this.lastScreenChangeAt) : 0;
|
|
1186
|
+
const hasAssistantTurn = !!lastParsedAssistant;
|
|
1187
|
+
const assistantLength = lastParsedAssistant?.content?.length || 0;
|
|
1188
|
+
const idleQuietThresholdMs = Math.max(220, this.timeouts.outputSettle);
|
|
1189
|
+
const idleStableThresholdMs = Math.max(120, Math.min(220, this.timeouts.outputSettle));
|
|
1190
|
+
const idleReady = visibleIdlePrompt
|
|
1191
|
+
&& !modal
|
|
1192
|
+
&& hasAssistantTurn
|
|
1193
|
+
&& quietForMs >= idleQuietThresholdMs
|
|
1194
|
+
&& screenStableMs >= idleStableThresholdMs;
|
|
1195
|
+
const candidate = this.idleFinishCandidate;
|
|
1196
|
+
const candidateQuiet = !!candidate
|
|
1197
|
+
&& candidate.responseEpoch === this.responseEpoch
|
|
1198
|
+
&& candidate.lastOutputAt === this.lastOutputAt
|
|
1199
|
+
&& candidate.lastScreenChangeAt === this.lastScreenChangeAt
|
|
1200
|
+
&& assistantLength >= candidate.assistantLength
|
|
1201
|
+
&& (now - candidate.armedAt) >= ProviderCliAdapter.IDLE_FINISH_CONFIRM_MS;
|
|
1202
|
+
const canFinishImmediately = idleReady && candidateQuiet;
|
|
1203
|
+
|
|
1204
|
+
this.recordTrace('idle_decision', {
|
|
1205
|
+
visibleIdlePrompt,
|
|
1206
|
+
quietForMs,
|
|
1207
|
+
screenStableMs,
|
|
1208
|
+
hasAssistantTurn,
|
|
1209
|
+
assistantLength,
|
|
1210
|
+
hasModal: !!modal,
|
|
1211
|
+
idleQuietThresholdMs,
|
|
1212
|
+
idleStableThresholdMs,
|
|
1213
|
+
idleReady,
|
|
1214
|
+
idleFinishConfirmMs: ProviderCliAdapter.IDLE_FINISH_CONFIRM_MS,
|
|
1215
|
+
idleFinishCandidate: candidate,
|
|
1216
|
+
candidateQuiet,
|
|
1217
|
+
canFinishImmediately,
|
|
1218
|
+
submitPendingUntil: this.submitPendingUntil,
|
|
1219
|
+
responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
|
|
1220
|
+
...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer),
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
if (canFinishImmediately) {
|
|
1224
|
+
this.clearIdleFinishCandidate('finish_response');
|
|
1225
|
+
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
1226
|
+
this.finishResponse();
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
if (idleReady) {
|
|
1231
|
+
if (!candidate) {
|
|
1232
|
+
this.armIdleFinishCandidate(assistantLength);
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
} else {
|
|
1236
|
+
this.clearIdleFinishCandidate('idle_not_ready');
|
|
1237
|
+
}
|
|
1238
|
+
|
|
924
1239
|
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
925
1240
|
this.idleTimeout = setTimeout(() => {
|
|
926
1241
|
if (this.isWaitingForResponse && this.currentStatus !== 'waiting_approval') {
|
|
1242
|
+
this.clearIdleFinishCandidate('idle_timeout_finish');
|
|
927
1243
|
this.finishResponse();
|
|
928
1244
|
}
|
|
929
1245
|
}, this.timeouts.idleFinish);
|
|
930
1246
|
} else if (prevStatus !== 'idle') {
|
|
1247
|
+
this.clearIdleFinishCandidate('idle_without_response');
|
|
931
1248
|
this.setStatus('idle', 'script_detect');
|
|
932
1249
|
this.onStatusChange?.();
|
|
933
1250
|
}
|
|
@@ -937,6 +1254,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
937
1254
|
private finishResponse(): void {
|
|
938
1255
|
if (this.submitPendingUntil > Date.now()) return;
|
|
939
1256
|
if (this.responseSettleIgnoreUntil > Date.now()) return;
|
|
1257
|
+
this.clearIdleFinishCandidate('finish_response_enter');
|
|
1258
|
+
this.recordTrace('finish_response', {
|
|
1259
|
+
...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer),
|
|
1260
|
+
});
|
|
940
1261
|
this.commitCurrentTranscript();
|
|
941
1262
|
if (this.responseTimeout) { clearTimeout(this.responseTimeout); this.responseTimeout = null; }
|
|
942
1263
|
if (this.idleTimeout) { clearTimeout(this.idleTimeout); this.idleTimeout = null; }
|
|
@@ -963,6 +1284,20 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
963
1284
|
if (parsed && Array.isArray(parsed.messages)) {
|
|
964
1285
|
this.committedMessages = this.normalizeParsedMessages(parsed.messages);
|
|
965
1286
|
this.syncMessageViews();
|
|
1287
|
+
const lastAssistant = [...this.committedMessages].reverse().find((message) => message.role === 'assistant');
|
|
1288
|
+
this.recordTrace('commit_transcript', {
|
|
1289
|
+
parsedStatus: parsed.status || null,
|
|
1290
|
+
messageCount: this.committedMessages.length,
|
|
1291
|
+
lastAssistant: lastAssistant ? this.summarizeTraceText(lastAssistant.content, 320) : '',
|
|
1292
|
+
messages: this.summarizeTraceMessages(this.committedMessages),
|
|
1293
|
+
...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer),
|
|
1294
|
+
});
|
|
1295
|
+
if (!lastAssistant && this.currentTurnScope) {
|
|
1296
|
+
LOG.warn(
|
|
1297
|
+
'CLI',
|
|
1298
|
+
`[${this.cliType}] Commit without assistant turn: prompt=${JSON.stringify(this.currentTurnScope.prompt).slice(0, 140)} responseBuffer=${JSON.stringify(this.summarizeTraceText(this.responseBuffer, 220)).slice(0, 260)} providerDir=${this.providerResolutionMeta.providerDir || '-'} scriptDir=${this.providerResolutionMeta.scriptDir || '-'} scriptsPath=${this.providerResolutionMeta.scriptsPath || '-'}`
|
|
1299
|
+
);
|
|
1300
|
+
}
|
|
966
1301
|
}
|
|
967
1302
|
}
|
|
968
1303
|
|
|
@@ -1094,17 +1429,24 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1094
1429
|
}
|
|
1095
1430
|
if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
|
|
1096
1431
|
if (this.isWaitingForResponse) return;
|
|
1432
|
+
await this.waitForInteractivePrompt();
|
|
1097
1433
|
|
|
1098
1434
|
this.committedMessages.push({ role: 'user', content: text, timestamp: Date.now() });
|
|
1099
1435
|
this.syncMessageViews();
|
|
1100
1436
|
this.isWaitingForResponse = true;
|
|
1101
1437
|
this.responseBuffer = '';
|
|
1438
|
+
this.clearIdleFinishCandidate('send_message');
|
|
1102
1439
|
this.currentTurnScope = {
|
|
1103
1440
|
prompt: text,
|
|
1104
1441
|
startedAt: Date.now(),
|
|
1105
1442
|
bufferStart: this.accumulatedBuffer.length,
|
|
1106
1443
|
rawBufferStart: this.accumulatedRawBuffer.length,
|
|
1107
1444
|
};
|
|
1445
|
+
this.recordTrace('send_message', {
|
|
1446
|
+
text: this.summarizeTraceText(text, 500),
|
|
1447
|
+
estimatedLines: estimatePromptDisplayLines(text),
|
|
1448
|
+
turnScope: this.currentTurnScope,
|
|
1449
|
+
});
|
|
1108
1450
|
LOG.info('CLI', `[${this.cliType}] sendMessage turn scope buffer=${this.currentTurnScope.bufferStart} raw=${this.currentTurnScope.rawBufferStart} prompt=${JSON.stringify(text).slice(0, 120)}`);
|
|
1109
1451
|
this.submitRetryUsed = false;
|
|
1110
1452
|
this.submitRetryPromptSnippet = extractPromptRetrySnippet(text);
|
|
@@ -1135,6 +1477,11 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1135
1477
|
const submit = () => {
|
|
1136
1478
|
if (!this.ptyProcess) return;
|
|
1137
1479
|
this.submitPendingUntil = 0;
|
|
1480
|
+
this.recordTrace('submit_write', {
|
|
1481
|
+
mode: 'submit_key',
|
|
1482
|
+
sendKey: this.sendKey,
|
|
1483
|
+
screenText: this.summarizeTraceText(this.terminalScreen.getText(), 500),
|
|
1484
|
+
});
|
|
1138
1485
|
this.ptyProcess.write(this.sendKey);
|
|
1139
1486
|
const retrySubmitIfStuck = (attempt: number) => {
|
|
1140
1487
|
this.submitRetryTimer = null;
|
|
@@ -1146,6 +1493,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1146
1493
|
if (/Esc to interrupt|Do you want to proceed|This command requires approval|Allow Codex to|Approve and run now|Always approve this session|Running…|Running\.\.\./i.test(screenText)) return;
|
|
1147
1494
|
this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
1148
1495
|
LOG.info('CLI', `[${this.cliType}] Retrying submit key for stuck prompt (attempt ${attempt})`);
|
|
1496
|
+
this.recordTrace('submit_write', {
|
|
1497
|
+
mode: 'submit_retry',
|
|
1498
|
+
attempt,
|
|
1499
|
+
sendKey: this.sendKey,
|
|
1500
|
+
screenText: this.summarizeTraceText(screenText, 500),
|
|
1501
|
+
});
|
|
1149
1502
|
this.ptyProcess.write(this.sendKey);
|
|
1150
1503
|
if (attempt >= 3) {
|
|
1151
1504
|
this.submitRetryUsed = true;
|
|
@@ -1159,6 +1512,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1159
1512
|
|
|
1160
1513
|
if (this.submitStrategy === 'immediate') {
|
|
1161
1514
|
this.submitPendingUntil = 0;
|
|
1515
|
+
this.recordTrace('submit_write', {
|
|
1516
|
+
mode: 'immediate',
|
|
1517
|
+
text: this.summarizeTraceText(text, 500),
|
|
1518
|
+
sendKey: this.sendKey,
|
|
1519
|
+
screenText: this.summarizeTraceText(this.terminalScreen.getText(), 500),
|
|
1520
|
+
});
|
|
1162
1521
|
this.ptyProcess.write(text + this.sendKey);
|
|
1163
1522
|
this.submitRetryTimer = setTimeout(() => {
|
|
1164
1523
|
this.submitRetryTimer = null;
|
|
@@ -1169,6 +1528,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1169
1528
|
if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
|
|
1170
1529
|
LOG.info('CLI', `[${this.cliType}] Retrying submit key for stuck prompt (attempt 1)`);
|
|
1171
1530
|
this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
1531
|
+
this.recordTrace('submit_write', {
|
|
1532
|
+
mode: 'immediate_retry',
|
|
1533
|
+
attempt: 1,
|
|
1534
|
+
sendKey: this.sendKey,
|
|
1535
|
+
screenText: this.summarizeTraceText(screenText, 500),
|
|
1536
|
+
});
|
|
1172
1537
|
this.ptyProcess.write(this.sendKey);
|
|
1173
1538
|
this.submitRetryUsed = true;
|
|
1174
1539
|
}, retryDelayMs);
|
|
@@ -1180,6 +1545,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1180
1545
|
this.submitPendingUntil = Date.now() + submitDelayMs;
|
|
1181
1546
|
}
|
|
1182
1547
|
this.ptyProcess.write(text);
|
|
1548
|
+
this.recordTrace('submit_write', {
|
|
1549
|
+
mode: 'type_then_submit',
|
|
1550
|
+
text: this.summarizeTraceText(text, 500),
|
|
1551
|
+
sendKey: this.sendKey,
|
|
1552
|
+
screenText: this.summarizeTraceText(this.terminalScreen.getText(), 500),
|
|
1553
|
+
});
|
|
1183
1554
|
const submitStartedAt = Date.now();
|
|
1184
1555
|
let lastNormalizedScreen = '';
|
|
1185
1556
|
let lastScreenChangeAt = submitStartedAt;
|
|
@@ -1293,6 +1664,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1293
1664
|
}
|
|
1294
1665
|
|
|
1295
1666
|
shutdown(): void {
|
|
1667
|
+
this.clearIdleFinishCandidate('shutdown');
|
|
1296
1668
|
if (this.settleTimer) { clearTimeout(this.settleTimer); this.settleTimer = null; }
|
|
1297
1669
|
if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
|
|
1298
1670
|
if (this.submitRetryTimer) { clearTimeout(this.submitRetryTimer); this.submitRetryTimer = null; }
|
|
@@ -1316,6 +1688,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1316
1688
|
}
|
|
1317
1689
|
|
|
1318
1690
|
detach(): void {
|
|
1691
|
+
this.clearIdleFinishCandidate('detach');
|
|
1319
1692
|
if (this.settleTimer) { clearTimeout(this.settleTimer); this.settleTimer = null; }
|
|
1320
1693
|
if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
|
|
1321
1694
|
if (this.submitRetryTimer) { clearTimeout(this.submitRetryTimer); this.submitRetryTimer = null; }
|
|
@@ -1341,6 +1714,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1341
1714
|
}
|
|
1342
1715
|
|
|
1343
1716
|
clearHistory(): void {
|
|
1717
|
+
this.clearIdleFinishCandidate('clear_history');
|
|
1344
1718
|
this.committedMessages = [];
|
|
1345
1719
|
this.syncMessageViews();
|
|
1346
1720
|
this.accumulatedBuffer = '';
|
|
@@ -1362,11 +1736,20 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1362
1736
|
isReady(): boolean { return this.ready; }
|
|
1363
1737
|
|
|
1364
1738
|
writeRaw(data: string): void {
|
|
1739
|
+
this.recordTrace('write_raw', {
|
|
1740
|
+
keys: JSON.stringify(data),
|
|
1741
|
+
length: data.length,
|
|
1742
|
+
});
|
|
1365
1743
|
this.ptyProcess?.write(data);
|
|
1366
1744
|
}
|
|
1367
1745
|
|
|
1368
1746
|
resolveModal(buttonIndex: number): void {
|
|
1369
1747
|
if (!this.ptyProcess || (this.currentStatus !== 'waiting_approval' && !this.activeModal)) return;
|
|
1748
|
+
this.clearIdleFinishCandidate('resolve_modal');
|
|
1749
|
+
this.recordTrace('resolve_modal', {
|
|
1750
|
+
buttonIndex,
|
|
1751
|
+
activeModal: this.activeModal,
|
|
1752
|
+
});
|
|
1370
1753
|
this.activeModal = null;
|
|
1371
1754
|
this.lastApprovalResolvedAt = Date.now();
|
|
1372
1755
|
this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
@@ -1399,6 +1782,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1399
1782
|
return {
|
|
1400
1783
|
type: this.cliType,
|
|
1401
1784
|
name: this.cliName,
|
|
1785
|
+
providerResolution: this.providerResolutionMeta,
|
|
1402
1786
|
status: this.currentStatus,
|
|
1403
1787
|
ready: this.ready,
|
|
1404
1788
|
startupParseGate: this.startupParseGate,
|
|
@@ -1418,6 +1802,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1418
1802
|
rawBufferPreview: this.accumulatedRawBuffer.slice(-1000),
|
|
1419
1803
|
sanitizedRawPreview: sanitizeTerminalText(this.accumulatedRawBuffer).slice(-1000),
|
|
1420
1804
|
responseBuffer: this.responseBuffer.slice(-1000),
|
|
1805
|
+
lastOutputAt: this.lastOutputAt,
|
|
1806
|
+
lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
|
|
1807
|
+
lastScreenChangeAt: this.lastScreenChangeAt,
|
|
1808
|
+
lastScreenSnapshot: this.lastScreenSnapshot.slice(-500),
|
|
1421
1809
|
isWaitingForResponse: this.isWaitingForResponse,
|
|
1422
1810
|
activeModal: this.activeModal,
|
|
1423
1811
|
lastApprovalResolvedAt: this.lastApprovalResolvedAt,
|
|
@@ -1429,6 +1817,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1429
1817
|
resizeSuppressUntil: this.resizeSuppressUntil,
|
|
1430
1818
|
hasCliScripts: this.hasCliScripts(),
|
|
1431
1819
|
scriptNames: Object.keys(this.cliScripts).filter(k => typeof (this.cliScripts as any)[k] === 'function'),
|
|
1820
|
+
traceSessionId: this.traceSessionId,
|
|
1821
|
+
traceEntryCount: this.traceEntries.length,
|
|
1432
1822
|
statusHistory: this.statusHistory.slice(-30),
|
|
1433
1823
|
timeouts: this.timeouts,
|
|
1434
1824
|
pendingOutputParseBufferLength: this.pendingOutputParseBuffer.length,
|
|
@@ -1437,6 +1827,27 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1437
1827
|
};
|
|
1438
1828
|
}
|
|
1439
1829
|
|
|
1830
|
+
getTraceState(limit = 120): Record<string, any> {
|
|
1831
|
+
const cappedLimit = Math.max(1, Math.min(500, Number.isFinite(limit) ? Math.floor(limit) : 120));
|
|
1832
|
+
return {
|
|
1833
|
+
sessionId: this.traceSessionId,
|
|
1834
|
+
providerResolution: this.providerResolutionMeta,
|
|
1835
|
+
entryCount: this.traceEntries.length,
|
|
1836
|
+
entries: this.traceEntries.slice(-cappedLimit),
|
|
1837
|
+
screenText: this.summarizeTraceText(this.terminalScreen.getText(), 4000),
|
|
1838
|
+
recentOutputBuffer: this.summarizeTraceText(this.recentOutputBuffer, 1000),
|
|
1839
|
+
responseBuffer: this.summarizeTraceText(this.responseBuffer, 1200),
|
|
1840
|
+
status: this.currentStatus,
|
|
1841
|
+
activeModal: this.activeModal,
|
|
1842
|
+
currentTurnScope: this.currentTurnScope,
|
|
1843
|
+
messages: this.summarizeTraceMessages(this.committedMessages, 5),
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
getProviderResolutionMeta(): Record<string, any> {
|
|
1848
|
+
return { ...this.providerResolutionMeta };
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1440
1851
|
private respondToTerminalQueries(data: string): void {
|
|
1441
1852
|
if (!this.ptyProcess || !data) return;
|
|
1442
1853
|
|