@adhdev/daemon-core 0.7.45 → 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 +34 -0
- package/dist/cli-adapters/pty-transport.d.ts +1 -0
- package/dist/cli-adapters/session-host-transport.d.ts +1 -0
- package/dist/commands/cli-manager.d.ts +11 -2
- package/dist/config/chat-history.d.ts +32 -2
- package/dist/config/config.d.ts +5 -1
- package/dist/config/recent-activity.d.ts +3 -1
- package/dist/config/saved-sessions.d.ts +22 -0
- 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.d.ts +2 -0
- package/dist/index.js +6122 -4038
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6114 -4032
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +29 -1
- package/dist/providers/contracts.d.ts +11 -0
- package/dist/providers/provider-instance.d.ts +1 -0
- package/dist/shared-types.d.ts +2 -0
- package/node_modules/@adhdev/session-host-core/dist/index.d.mts +12 -1
- package/node_modules/@adhdev/session-host-core/dist/index.d.ts +12 -1
- package/node_modules/@adhdev/session-host-core/dist/index.js +9 -0
- package/node_modules/@adhdev/session-host-core/dist/index.js.map +1 -1
- package/node_modules/@adhdev/session-host-core/dist/index.mjs +9 -0
- package/node_modules/@adhdev/session-host-core/dist/index.mjs.map +1 -1
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/boot/daemon-lifecycle.ts +19 -15
- package/src/cli-adapters/provider-cli-adapter.ts +424 -7
- package/src/cli-adapters/pty-transport.ts +1 -0
- package/src/cli-adapters/session-host-transport.ts +32 -1
- package/src/commands/chat-commands.ts +36 -8
- package/src/commands/cli-manager.ts +259 -22
- package/src/commands/router.ts +52 -1
- package/src/config/chat-history.ts +197 -10
- package/src/config/config.d.ts +4 -0
- package/src/config/config.ts +8 -2
- package/src/config/recent-activity.ts +13 -2
- package/src/config/saved-sessions.ts +73 -0
- package/src/daemon/dev-auto-implement.ts +394 -43
- package/src/daemon/dev-cli-debug.ts +839 -0
- package/src/daemon/dev-server.ts +51 -5
- package/src/index.ts +2 -0
- package/src/providers/cli-provider-instance.ts +283 -4
- package/src/providers/contracts.ts +11 -0
- package/src/providers/provider-instance.d.ts +1 -0
- package/src/providers/provider-instance.ts +1 -0
- package/src/providers/provider-loader.ts +39 -0
- package/src/session-host/runtime-support.ts +1 -0
- package/src/shared-types.d.ts +2 -0
- package/src/shared-types.ts +2 -0
- package/src/status/builders.ts +1 -0
- package/src/status/snapshot.ts +1 -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;
|
|
@@ -433,10 +469,16 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
433
469
|
/** Full accumulated raw PTY output (with ANSI) */
|
|
434
470
|
private accumulatedRawBuffer: string = '';
|
|
435
471
|
/** Current visible terminal screen snapshot */
|
|
436
|
-
private terminalScreen = new TerminalScreen(
|
|
472
|
+
private terminalScreen = new TerminalScreen(24, 80);
|
|
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
|
|
|
@@ -1025,6 +1360,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1025
1360
|
title: parsed.title || this.cliName,
|
|
1026
1361
|
messages: parsed.messages,
|
|
1027
1362
|
activeModal: parsed.activeModal ?? this.activeModal,
|
|
1363
|
+
providerSessionId: typeof parsed.providerSessionId === 'string' ? parsed.providerSessionId : undefined,
|
|
1028
1364
|
};
|
|
1029
1365
|
}
|
|
1030
1366
|
|
|
@@ -1093,17 +1429,24 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1093
1429
|
}
|
|
1094
1430
|
if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
|
|
1095
1431
|
if (this.isWaitingForResponse) return;
|
|
1432
|
+
await this.waitForInteractivePrompt();
|
|
1096
1433
|
|
|
1097
1434
|
this.committedMessages.push({ role: 'user', content: text, timestamp: Date.now() });
|
|
1098
1435
|
this.syncMessageViews();
|
|
1099
1436
|
this.isWaitingForResponse = true;
|
|
1100
1437
|
this.responseBuffer = '';
|
|
1438
|
+
this.clearIdleFinishCandidate('send_message');
|
|
1101
1439
|
this.currentTurnScope = {
|
|
1102
1440
|
prompt: text,
|
|
1103
1441
|
startedAt: Date.now(),
|
|
1104
1442
|
bufferStart: this.accumulatedBuffer.length,
|
|
1105
1443
|
rawBufferStart: this.accumulatedRawBuffer.length,
|
|
1106
1444
|
};
|
|
1445
|
+
this.recordTrace('send_message', {
|
|
1446
|
+
text: this.summarizeTraceText(text, 500),
|
|
1447
|
+
estimatedLines: estimatePromptDisplayLines(text),
|
|
1448
|
+
turnScope: this.currentTurnScope,
|
|
1449
|
+
});
|
|
1107
1450
|
LOG.info('CLI', `[${this.cliType}] sendMessage turn scope buffer=${this.currentTurnScope.bufferStart} raw=${this.currentTurnScope.rawBufferStart} prompt=${JSON.stringify(text).slice(0, 120)}`);
|
|
1108
1451
|
this.submitRetryUsed = false;
|
|
1109
1452
|
this.submitRetryPromptSnippet = extractPromptRetrySnippet(text);
|
|
@@ -1134,6 +1477,11 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1134
1477
|
const submit = () => {
|
|
1135
1478
|
if (!this.ptyProcess) return;
|
|
1136
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
|
+
});
|
|
1137
1485
|
this.ptyProcess.write(this.sendKey);
|
|
1138
1486
|
const retrySubmitIfStuck = (attempt: number) => {
|
|
1139
1487
|
this.submitRetryTimer = null;
|
|
@@ -1145,6 +1493,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1145
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;
|
|
1146
1494
|
this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
1147
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
|
+
});
|
|
1148
1502
|
this.ptyProcess.write(this.sendKey);
|
|
1149
1503
|
if (attempt >= 3) {
|
|
1150
1504
|
this.submitRetryUsed = true;
|
|
@@ -1158,6 +1512,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1158
1512
|
|
|
1159
1513
|
if (this.submitStrategy === 'immediate') {
|
|
1160
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
|
+
});
|
|
1161
1521
|
this.ptyProcess.write(text + this.sendKey);
|
|
1162
1522
|
this.submitRetryTimer = setTimeout(() => {
|
|
1163
1523
|
this.submitRetryTimer = null;
|
|
@@ -1168,6 +1528,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1168
1528
|
if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
|
|
1169
1529
|
LOG.info('CLI', `[${this.cliType}] Retrying submit key for stuck prompt (attempt 1)`);
|
|
1170
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
|
+
});
|
|
1171
1537
|
this.ptyProcess.write(this.sendKey);
|
|
1172
1538
|
this.submitRetryUsed = true;
|
|
1173
1539
|
}, retryDelayMs);
|
|
@@ -1179,6 +1545,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1179
1545
|
this.submitPendingUntil = Date.now() + submitDelayMs;
|
|
1180
1546
|
}
|
|
1181
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
|
+
});
|
|
1182
1554
|
const submitStartedAt = Date.now();
|
|
1183
1555
|
let lastNormalizedScreen = '';
|
|
1184
1556
|
let lastScreenChangeAt = submitStartedAt;
|
|
@@ -1222,6 +1594,11 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1222
1594
|
return this.ptyProcess.getMetadata();
|
|
1223
1595
|
}
|
|
1224
1596
|
|
|
1597
|
+
updateRuntimeMeta(meta: Record<string, unknown>, replace = false): void {
|
|
1598
|
+
if (!this.ptyProcess || typeof this.ptyProcess.updateMeta !== 'function') return;
|
|
1599
|
+
this.ptyProcess.updateMeta(meta, replace);
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1225
1602
|
cancel(): void { this.shutdown(); }
|
|
1226
1603
|
|
|
1227
1604
|
async saveAndStop(): Promise<void> {
|
|
@@ -1287,6 +1664,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1287
1664
|
}
|
|
1288
1665
|
|
|
1289
1666
|
shutdown(): void {
|
|
1667
|
+
this.clearIdleFinishCandidate('shutdown');
|
|
1290
1668
|
if (this.settleTimer) { clearTimeout(this.settleTimer); this.settleTimer = null; }
|
|
1291
1669
|
if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
|
|
1292
1670
|
if (this.submitRetryTimer) { clearTimeout(this.submitRetryTimer); this.submitRetryTimer = null; }
|
|
@@ -1310,6 +1688,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1310
1688
|
}
|
|
1311
1689
|
|
|
1312
1690
|
detach(): void {
|
|
1691
|
+
this.clearIdleFinishCandidate('detach');
|
|
1313
1692
|
if (this.settleTimer) { clearTimeout(this.settleTimer); this.settleTimer = null; }
|
|
1314
1693
|
if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
|
|
1315
1694
|
if (this.submitRetryTimer) { clearTimeout(this.submitRetryTimer); this.submitRetryTimer = null; }
|
|
@@ -1335,6 +1714,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1335
1714
|
}
|
|
1336
1715
|
|
|
1337
1716
|
clearHistory(): void {
|
|
1717
|
+
this.clearIdleFinishCandidate('clear_history');
|
|
1338
1718
|
this.committedMessages = [];
|
|
1339
1719
|
this.syncMessageViews();
|
|
1340
1720
|
this.accumulatedBuffer = '';
|
|
@@ -1356,11 +1736,20 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1356
1736
|
isReady(): boolean { return this.ready; }
|
|
1357
1737
|
|
|
1358
1738
|
writeRaw(data: string): void {
|
|
1739
|
+
this.recordTrace('write_raw', {
|
|
1740
|
+
keys: JSON.stringify(data),
|
|
1741
|
+
length: data.length,
|
|
1742
|
+
});
|
|
1359
1743
|
this.ptyProcess?.write(data);
|
|
1360
1744
|
}
|
|
1361
1745
|
|
|
1362
1746
|
resolveModal(buttonIndex: number): void {
|
|
1363
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
|
+
});
|
|
1364
1753
|
this.activeModal = null;
|
|
1365
1754
|
this.lastApprovalResolvedAt = Date.now();
|
|
1366
1755
|
this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
@@ -1393,6 +1782,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1393
1782
|
return {
|
|
1394
1783
|
type: this.cliType,
|
|
1395
1784
|
name: this.cliName,
|
|
1785
|
+
providerResolution: this.providerResolutionMeta,
|
|
1396
1786
|
status: this.currentStatus,
|
|
1397
1787
|
ready: this.ready,
|
|
1398
1788
|
startupParseGate: this.startupParseGate,
|
|
@@ -1412,6 +1802,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1412
1802
|
rawBufferPreview: this.accumulatedRawBuffer.slice(-1000),
|
|
1413
1803
|
sanitizedRawPreview: sanitizeTerminalText(this.accumulatedRawBuffer).slice(-1000),
|
|
1414
1804
|
responseBuffer: this.responseBuffer.slice(-1000),
|
|
1805
|
+
lastOutputAt: this.lastOutputAt,
|
|
1806
|
+
lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
|
|
1807
|
+
lastScreenChangeAt: this.lastScreenChangeAt,
|
|
1808
|
+
lastScreenSnapshot: this.lastScreenSnapshot.slice(-500),
|
|
1415
1809
|
isWaitingForResponse: this.isWaitingForResponse,
|
|
1416
1810
|
activeModal: this.activeModal,
|
|
1417
1811
|
lastApprovalResolvedAt: this.lastApprovalResolvedAt,
|
|
@@ -1423,6 +1817,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1423
1817
|
resizeSuppressUntil: this.resizeSuppressUntil,
|
|
1424
1818
|
hasCliScripts: this.hasCliScripts(),
|
|
1425
1819
|
scriptNames: Object.keys(this.cliScripts).filter(k => typeof (this.cliScripts as any)[k] === 'function'),
|
|
1820
|
+
traceSessionId: this.traceSessionId,
|
|
1821
|
+
traceEntryCount: this.traceEntries.length,
|
|
1426
1822
|
statusHistory: this.statusHistory.slice(-30),
|
|
1427
1823
|
timeouts: this.timeouts,
|
|
1428
1824
|
pendingOutputParseBufferLength: this.pendingOutputParseBuffer.length,
|
|
@@ -1431,6 +1827,27 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1431
1827
|
};
|
|
1432
1828
|
}
|
|
1433
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
|
+
|
|
1434
1851
|
private respondToTerminalQueries(data: string): void {
|
|
1435
1852
|
if (!this.ptyProcess || !data) return;
|
|
1436
1853
|
|