@adhdev/daemon-core 0.6.57 → 0.6.58
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/index.d.ts +17 -0
- package/dist/index.js +252 -78
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/providers/_builtin/cli/aider-cli/scripts/1.0/parse_output.js +51 -3
- package/providers/_builtin/cli/claude-cli/provider.json +18 -6
- package/providers/_builtin/cli/claude-cli/scripts/1.0/detect_status.js +68 -16
- package/providers/_builtin/cli/claude-cli/scripts/1.0/parse_approval.js +81 -22
- package/providers/_builtin/cli/claude-cli/scripts/1.0/parse_output.js +347 -94
- package/providers/_builtin/cli/codex-cli/provider.json +2 -0
- package/providers/_builtin/cli/codex-cli/scripts/1.0/detect_status.js +44 -10
- package/providers/_builtin/cli/codex-cli/scripts/1.0/parse_approval.js +83 -7
- package/providers/_builtin/cli/codex-cli/scripts/1.0/parse_output.js +501 -47
- package/providers/_builtin/cli/cursor-cli/scripts/1.0/parse_output.js +1 -1
- package/providers/_builtin/cli/github-copilot-cli/scripts/1.0/parse_output.js +1 -1
- package/providers/_builtin/cli/goose-cli/scripts/1.0/parse_output.js +1 -1
- package/providers/_builtin/cli/opencode-cli/scripts/1.0/parse_output.js +1 -1
- package/providers/_builtin/ide/vscode/provider.json +5 -1
- package/providers/_builtin/ide/vscode/scripts/1.0/focus_editor.js +1 -0
- package/providers/_builtin/ide/vscode/scripts/1.0/list_models.js +1 -0
- package/providers/_builtin/ide/vscode/scripts/1.0/list_sessions.js +1 -0
- package/providers/_builtin/ide/vscode/scripts/1.0/new_session.js +1 -0
- package/providers/_builtin/ide/vscode/scripts/1.0/open_panel.js +1 -0
- package/providers/_builtin/ide/vscode/scripts/1.0/read_chat.js +1 -0
- package/providers/_builtin/ide/vscode/scripts/1.0/resolve_action.js +1 -0
- package/providers/_builtin/ide/vscode/scripts/1.0/scripts.js +25 -0
- package/providers/_builtin/ide/vscode/scripts/1.0/send_message.js +1 -0
- package/providers/_builtin/ide/vscode/scripts/1.0/set_model.js +1 -0
- package/providers/_builtin/ide/vscode/scripts/1.0/switch_session.js +1 -0
- package/providers/_builtin/registry.json +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +223 -48
- package/src/commands/chat-commands.ts +7 -1
- package/src/config/chat-history.ts +53 -1
- package/src/daemon/dev-server.ts +7 -9
- package/src/providers/cli-provider-instance.ts +10 -23
- package/src/providers/provider-instance.ts +1 -0
- package/src/providers/version-archive.ts +4 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = async function(args, context) { return { error: "Not implemented" }; };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = async function(args, context) { return { error: "Not implemented" }; };
|
|
@@ -57,6 +57,7 @@ export interface CliSessionStatus {
|
|
|
57
57
|
messages: CliChatMessage[];
|
|
58
58
|
workingDir: string;
|
|
59
59
|
activeModal: { message: string; buttons: string[] } | null;
|
|
60
|
+
terminalHistory?: string;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
/**
|
|
@@ -82,10 +83,19 @@ export interface CliScriptInput {
|
|
|
82
83
|
rawBuffer: string; // Raw PTY output (with ANSI)
|
|
83
84
|
recentBuffer: string; // Recent 1000 chars (ANSI-stripped)
|
|
84
85
|
screenText: string; // Current visible screen snapshot
|
|
86
|
+
terminalHistory?: string; // Rolling append-only terminal transcript
|
|
85
87
|
messages: CliChatMessage[]; // Previously parsed messages
|
|
86
88
|
partialResponse: string; // Current partial response being generated
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
interface TurnParseScope {
|
|
92
|
+
prompt: string;
|
|
93
|
+
startedAt: number;
|
|
94
|
+
bufferStart: number;
|
|
95
|
+
rawBufferStart: number;
|
|
96
|
+
terminalHistoryStart: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
89
99
|
export interface CliProviderModule {
|
|
90
100
|
type: string;
|
|
91
101
|
name: string;
|
|
@@ -93,6 +103,7 @@ export interface CliProviderModule {
|
|
|
93
103
|
binary: string;
|
|
94
104
|
sendDelayMs?: number;
|
|
95
105
|
sendKey?: string;
|
|
106
|
+
submitStrategy?: 'wait_for_echo' | 'immediate';
|
|
96
107
|
spawn: {
|
|
97
108
|
command: string;
|
|
98
109
|
args: string[];
|
|
@@ -243,6 +254,44 @@ function promptLikelyVisible(screenText: string, promptSnippet: string): boolean
|
|
|
243
254
|
return matched >= required;
|
|
244
255
|
}
|
|
245
256
|
|
|
257
|
+
function splitHistoryLines(text: string): string[] {
|
|
258
|
+
return String(text || '')
|
|
259
|
+
.split('\n')
|
|
260
|
+
.map(line => line.replace(/\s+$/, ''));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function normalizeHistoryLine(line: string): string {
|
|
264
|
+
return String(line || '').replace(/\s+/g, ' ').trim();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function mergeTerminalHistory(existing: string, snapshot: string): string {
|
|
268
|
+
const next = String(snapshot || '').trim();
|
|
269
|
+
if (!next) return existing;
|
|
270
|
+
const prev = String(existing || '').trim();
|
|
271
|
+
if (!prev) return next;
|
|
272
|
+
if (prev === next || prev.endsWith(next)) return prev;
|
|
273
|
+
|
|
274
|
+
const prevLines = splitHistoryLines(prev);
|
|
275
|
+
const nextLines = splitHistoryLines(next);
|
|
276
|
+
const prevNorm = prevLines.map(normalizeHistoryLine);
|
|
277
|
+
const nextNorm = nextLines.map(normalizeHistoryLine);
|
|
278
|
+
|
|
279
|
+
const maxOverlap = Math.min(prevLines.length, nextLines.length);
|
|
280
|
+
for (let overlap = maxOverlap; overlap >= 1; overlap -= 1) {
|
|
281
|
+
const prevTail = prevNorm.slice(prevNorm.length - overlap);
|
|
282
|
+
const nextHead = nextNorm.slice(0, overlap);
|
|
283
|
+
if (prevTail.every((line, index) => line === nextHead[index])) {
|
|
284
|
+
return [...prevLines, ...nextLines.slice(overlap)].join('\n').trim();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const compactPrev = prevNorm.join('\n');
|
|
289
|
+
const compactNext = nextNorm.join('\n');
|
|
290
|
+
if (compactPrev.includes(compactNext)) return prev;
|
|
291
|
+
|
|
292
|
+
return `${prev}\n${next}`.trim();
|
|
293
|
+
}
|
|
294
|
+
|
|
246
295
|
/**
|
|
247
296
|
* Normalize provider.json for auto-implement approval detection.
|
|
248
297
|
* Kept for backward compat with dev-server auto-impl pipeline only.
|
|
@@ -288,6 +337,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
288
337
|
private provider: CliProviderModule;
|
|
289
338
|
private ptyProcess: any = null;
|
|
290
339
|
private messages: CliChatMessage[] = [];
|
|
340
|
+
private committedMessages: CliChatMessage[] = [];
|
|
291
341
|
private structuredMessages: CliChatMessage[] = [];
|
|
292
342
|
private currentStatus: CliSessionStatus['status'] = 'starting';
|
|
293
343
|
private onStatusChange: (() => void) | null = null;
|
|
@@ -343,8 +393,47 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
343
393
|
private accumulatedRawBuffer: string = '';
|
|
344
394
|
/** Current visible terminal screen snapshot */
|
|
345
395
|
private terminalScreen = new TerminalScreen(40, 120);
|
|
396
|
+
/** Rolling append-only terminal transcript built from screen snapshots */
|
|
397
|
+
private terminalHistory: string = '';
|
|
346
398
|
/** Max accumulated buffer size (last 50KB) */
|
|
347
399
|
private static readonly MAX_ACCUMULATED_BUFFER = 50000;
|
|
400
|
+
private currentTurnScope: TurnParseScope | null = null;
|
|
401
|
+
|
|
402
|
+
private syncMessageViews(): void {
|
|
403
|
+
this.messages = [...this.committedMessages];
|
|
404
|
+
this.structuredMessages = [...this.committedMessages];
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private sliceFromOffset(text: string, start: number): string {
|
|
408
|
+
if (!text) return '';
|
|
409
|
+
if (!Number.isFinite(start) || start <= 0) return text;
|
|
410
|
+
if (start >= text.length) return '';
|
|
411
|
+
return text.slice(start);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private buildParseInput(baseMessages: CliChatMessage[], partialResponse: string, scope?: TurnParseScope | null): CliScriptInput {
|
|
415
|
+
const buffer = scope
|
|
416
|
+
? (this.sliceFromOffset(this.terminalHistory, scope.terminalHistoryStart)
|
|
417
|
+
|| this.sliceFromOffset(this.accumulatedBuffer, scope.bufferStart)
|
|
418
|
+
|| this.accumulatedBuffer)
|
|
419
|
+
: this.accumulatedBuffer;
|
|
420
|
+
const rawBuffer = scope
|
|
421
|
+
? (this.sliceFromOffset(this.accumulatedRawBuffer, scope.rawBufferStart) || this.accumulatedRawBuffer)
|
|
422
|
+
: this.accumulatedRawBuffer;
|
|
423
|
+
const terminalHistory = scope
|
|
424
|
+
? (this.sliceFromOffset(this.terminalHistory, scope.terminalHistoryStart) || this.terminalHistory)
|
|
425
|
+
: this.terminalHistory;
|
|
426
|
+
|
|
427
|
+
return {
|
|
428
|
+
buffer,
|
|
429
|
+
rawBuffer,
|
|
430
|
+
recentBuffer: buffer.slice(-1000) || this.recentOutputBuffer,
|
|
431
|
+
screenText: this.terminalScreen.getText(),
|
|
432
|
+
terminalHistory,
|
|
433
|
+
messages: [...baseMessages],
|
|
434
|
+
partialResponse,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
348
437
|
|
|
349
438
|
private setStatus(status: CliSessionStatus['status'], trigger?: string): void {
|
|
350
439
|
const prev = this.currentStatus;
|
|
@@ -362,6 +451,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
362
451
|
private readonly approvalKeys: Record<number, string>;
|
|
363
452
|
private readonly sendDelayMs: number;
|
|
364
453
|
private readonly sendKey: string;
|
|
454
|
+
private readonly submitStrategy: 'wait_for_echo' | 'immediate';
|
|
365
455
|
|
|
366
456
|
constructor(provider: CliProviderModule, workingDir: string, private extraArgs: string[] = []) {
|
|
367
457
|
this.provider = provider;
|
|
@@ -389,6 +479,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
389
479
|
this.sendKey = typeof (provider as any).sendKey === 'string' && (provider as any).sendKey.length > 0
|
|
390
480
|
? (provider as any).sendKey
|
|
391
481
|
: '\r';
|
|
482
|
+
this.submitStrategy = (provider as any).submitStrategy === 'immediate' ? 'immediate' : 'wait_for_echo';
|
|
392
483
|
|
|
393
484
|
// Scripts are required — loaded by ProviderLoader via compatibility array
|
|
394
485
|
this.cliScripts = (provider as any).scripts || {};
|
|
@@ -514,6 +605,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
514
605
|
this.startupParseGate = true;
|
|
515
606
|
this.startupBuffer = '';
|
|
516
607
|
this.terminalScreen.reset(40, 120);
|
|
608
|
+
this.terminalHistory = '';
|
|
609
|
+
this.currentTurnScope = null;
|
|
517
610
|
this.ready = false;
|
|
518
611
|
this.setStatus('idle', 'pty_ready');
|
|
519
612
|
this.onStatusChange?.();
|
|
@@ -530,6 +623,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
530
623
|
}
|
|
531
624
|
|
|
532
625
|
this.terminalScreen.write(rawData);
|
|
626
|
+
this.terminalHistory = mergeTerminalHistory(this.terminalHistory, this.terminalScreen.getText());
|
|
533
627
|
const cleanData = stripAnsi(rawData);
|
|
534
628
|
|
|
535
629
|
if (this.isWaitingForResponse && cleanData) {
|
|
@@ -697,6 +791,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
697
791
|
private finishResponse(): void {
|
|
698
792
|
if (this.submitPendingUntil > Date.now()) return;
|
|
699
793
|
if (this.responseSettleIgnoreUntil > Date.now()) return;
|
|
794
|
+
this.commitCurrentTranscript();
|
|
700
795
|
if (this.responseTimeout) { clearTimeout(this.responseTimeout); this.responseTimeout = null; }
|
|
701
796
|
if (this.idleTimeout) { clearTimeout(this.idleTimeout); this.idleTimeout = null; }
|
|
702
797
|
if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
|
|
@@ -707,11 +802,65 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
707
802
|
this.responseSettleIgnoreUntil = 0;
|
|
708
803
|
this.submitRetryUsed = false;
|
|
709
804
|
this.submitRetryPromptSnippet = '';
|
|
805
|
+
this.currentTurnScope = null;
|
|
710
806
|
this.activeModal = null;
|
|
711
807
|
this.setStatus('idle', 'response_finished');
|
|
712
808
|
this.onStatusChange?.();
|
|
713
809
|
}
|
|
714
810
|
|
|
811
|
+
private commitCurrentTranscript(): void {
|
|
812
|
+
const baseMessages = [...this.committedMessages];
|
|
813
|
+
const parsed = this.parseCurrentTranscript(baseMessages, '', this.currentTurnScope);
|
|
814
|
+
if (parsed && Array.isArray(parsed.messages) && parsed.messages.length > 0) {
|
|
815
|
+
const parsedMessages = parsed.messages
|
|
816
|
+
.filter((m: any) => m && (m.role === 'user' || m.role === 'assistant'))
|
|
817
|
+
.map((m: any) => ({
|
|
818
|
+
role: m.role,
|
|
819
|
+
content: typeof m.content === 'string' ? m.content : String(m.content || ''),
|
|
820
|
+
timestamp: m.timestamp,
|
|
821
|
+
}));
|
|
822
|
+
const latestAssistant = [...parsedMessages]
|
|
823
|
+
.reverse()
|
|
824
|
+
.find((m: CliChatMessage) => m.role === 'assistant' && m.content.trim());
|
|
825
|
+
if (latestAssistant) {
|
|
826
|
+
LOG.info('CLI', `[${this.cliType}] commitCurrentTranscript parsed assistant len=${latestAssistant.content.length} scopePrompt=${JSON.stringify(this.currentTurnScope?.prompt || '').slice(0, 120)}`);
|
|
827
|
+
const nextMessages = [...baseMessages];
|
|
828
|
+
const last = nextMessages[nextMessages.length - 1];
|
|
829
|
+
if (last?.role === 'assistant') {
|
|
830
|
+
last.content = latestAssistant.content;
|
|
831
|
+
last.timestamp = latestAssistant.timestamp || last.timestamp;
|
|
832
|
+
} else if (last?.role === 'user') {
|
|
833
|
+
nextMessages.push({
|
|
834
|
+
role: 'assistant',
|
|
835
|
+
content: latestAssistant.content,
|
|
836
|
+
timestamp: latestAssistant.timestamp || Date.now(),
|
|
837
|
+
});
|
|
838
|
+
} else {
|
|
839
|
+
nextMessages.push({
|
|
840
|
+
role: 'assistant',
|
|
841
|
+
content: latestAssistant.content,
|
|
842
|
+
timestamp: latestAssistant.timestamp || Date.now(),
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
this.committedMessages = nextMessages;
|
|
846
|
+
this.syncMessageViews();
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
const fallback = String(this.responseBuffer || '').trim();
|
|
852
|
+
LOG.info('CLI', `[${this.cliType}] commitCurrentTranscript fallback len=${fallback.length} scopePrompt=${JSON.stringify(this.currentTurnScope?.prompt || '').slice(0, 120)}`);
|
|
853
|
+
if (!fallback) return;
|
|
854
|
+
const last = baseMessages[baseMessages.length - 1];
|
|
855
|
+
if (last?.role === 'assistant') {
|
|
856
|
+
last.content = fallback;
|
|
857
|
+
} else {
|
|
858
|
+
baseMessages.push({ role: 'assistant', content: fallback, timestamp: Date.now() });
|
|
859
|
+
}
|
|
860
|
+
this.committedMessages = baseMessages;
|
|
861
|
+
this.syncMessageViews();
|
|
862
|
+
}
|
|
863
|
+
|
|
715
864
|
// ─── Script Execution ──────────────────────────
|
|
716
865
|
|
|
717
866
|
private runDetectStatus(text: string): string | null {
|
|
@@ -745,26 +894,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
745
894
|
// ─── Public API (CliAdapter) ───────────────────
|
|
746
895
|
|
|
747
896
|
getStatus(): CliSessionStatus {
|
|
748
|
-
// Use parseOutput script for full result when available
|
|
749
|
-
const scriptResult = this.getScriptParsedStatus();
|
|
750
|
-
if (scriptResult) {
|
|
751
|
-
return {
|
|
752
|
-
status: this.currentStatus,
|
|
753
|
-
messages: (scriptResult.messages || []).map((m: any) => ({
|
|
754
|
-
role: m.role,
|
|
755
|
-
content: m.content,
|
|
756
|
-
timestamp: m.timestamp,
|
|
757
|
-
})),
|
|
758
|
-
workingDir: this.workingDir,
|
|
759
|
-
activeModal: this.activeModal,
|
|
760
|
-
};
|
|
761
|
-
}
|
|
762
|
-
|
|
763
897
|
return {
|
|
764
898
|
status: this.currentStatus,
|
|
765
|
-
messages: [...this.
|
|
899
|
+
messages: [...this.committedMessages],
|
|
766
900
|
workingDir: this.workingDir,
|
|
767
901
|
activeModal: this.activeModal,
|
|
902
|
+
terminalHistory: this.terminalHistory,
|
|
768
903
|
};
|
|
769
904
|
}
|
|
770
905
|
|
|
@@ -773,31 +908,33 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
773
908
|
* Called by command handler / dashboard for rich content rendering.
|
|
774
909
|
*/
|
|
775
910
|
getScriptParsedStatus(): any {
|
|
911
|
+
const messages = [...this.committedMessages];
|
|
912
|
+
return {
|
|
913
|
+
id: 'cli_session',
|
|
914
|
+
status: this.currentStatus,
|
|
915
|
+
title: this.cliName,
|
|
916
|
+
terminalHistory: this.terminalHistory,
|
|
917
|
+
messages: messages.slice(-50).map((message, index) => ({
|
|
918
|
+
id: `msg_${index}`,
|
|
919
|
+
role: message.role,
|
|
920
|
+
content: message.content,
|
|
921
|
+
timestamp: message.timestamp,
|
|
922
|
+
index,
|
|
923
|
+
kind: 'standard',
|
|
924
|
+
})),
|
|
925
|
+
activeModal: this.activeModal,
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
private parseCurrentTranscript(baseMessages: CliChatMessage[], partialResponse: string, scope?: TurnParseScope | null): any {
|
|
776
930
|
if (!this.cliScripts?.parseOutput) return null;
|
|
777
931
|
try {
|
|
778
|
-
const input
|
|
779
|
-
|
|
780
|
-
rawBuffer: this.accumulatedRawBuffer,
|
|
781
|
-
recentBuffer: this.recentOutputBuffer,
|
|
782
|
-
screenText: this.terminalScreen.getText(),
|
|
783
|
-
messages: [...(this.structuredMessages.length > 0 ? this.structuredMessages : this.messages)],
|
|
784
|
-
partialResponse: this.responseBuffer,
|
|
785
|
-
};
|
|
786
|
-
const result = this.cliScripts.parseOutput(input);
|
|
787
|
-
if (result && typeof result === 'object') {
|
|
788
|
-
if (Array.isArray((result as any).messages)) {
|
|
789
|
-
this.structuredMessages = (result as any).messages.map((m: any) => ({
|
|
790
|
-
role: m.role,
|
|
791
|
-
content: m.content,
|
|
792
|
-
timestamp: m.timestamp,
|
|
793
|
-
}));
|
|
794
|
-
}
|
|
795
|
-
return result;
|
|
796
|
-
}
|
|
932
|
+
const input = this.buildParseInput(baseMessages, partialResponse, scope);
|
|
933
|
+
return this.cliScripts.parseOutput(input);
|
|
797
934
|
} catch (e: any) {
|
|
798
935
|
LOG.warn('CLI', `[${this.cliType}] parseOutput error: ${e.message}`);
|
|
936
|
+
return null;
|
|
799
937
|
}
|
|
800
|
-
return null;
|
|
801
938
|
}
|
|
802
939
|
|
|
803
940
|
/** Whether this adapter has CLI scripts loaded */
|
|
@@ -838,10 +975,18 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
838
975
|
if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
|
|
839
976
|
if (this.isWaitingForResponse) return;
|
|
840
977
|
|
|
841
|
-
this.
|
|
842
|
-
this.
|
|
978
|
+
this.committedMessages.push({ role: 'user', content: text, timestamp: Date.now() });
|
|
979
|
+
this.syncMessageViews();
|
|
843
980
|
this.isWaitingForResponse = true;
|
|
844
981
|
this.responseBuffer = '';
|
|
982
|
+
this.currentTurnScope = {
|
|
983
|
+
prompt: text,
|
|
984
|
+
startedAt: Date.now(),
|
|
985
|
+
bufferStart: this.accumulatedBuffer.length,
|
|
986
|
+
rawBufferStart: this.accumulatedRawBuffer.length,
|
|
987
|
+
terminalHistoryStart: this.terminalHistory.length,
|
|
988
|
+
};
|
|
989
|
+
LOG.info('CLI', `[${this.cliType}] sendMessage turn scope buffer=${this.currentTurnScope.bufferStart} raw=${this.currentTurnScope.rawBufferStart} terminal=${this.currentTurnScope.terminalHistoryStart} prompt=${JSON.stringify(text).slice(0, 120)}`);
|
|
845
990
|
this.submitRetryUsed = false;
|
|
846
991
|
this.submitRetryPromptSnippet = extractPromptRetrySnippet(text);
|
|
847
992
|
const normalizedPromptSnippet = normalizePromptText(this.submitRetryPromptSnippet);
|
|
@@ -861,10 +1006,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
861
1006
|
this.responseSettleIgnoreUntil = Date.now() + submitDelayMs + this.timeouts.outputSettle + 250;
|
|
862
1007
|
this.setStatus('generating', 'sendMessage');
|
|
863
1008
|
this.onStatusChange?.();
|
|
864
|
-
|
|
865
|
-
this.
|
|
866
|
-
|
|
867
|
-
|
|
1009
|
+
const startResponseTimeout = () => {
|
|
1010
|
+
if (this.responseTimeout) clearTimeout(this.responseTimeout);
|
|
1011
|
+
this.responseTimeout = setTimeout(() => {
|
|
1012
|
+
if (this.isWaitingForResponse) this.finishResponse();
|
|
1013
|
+
}, this.timeouts.maxResponse);
|
|
1014
|
+
};
|
|
868
1015
|
|
|
869
1016
|
const submit = () => {
|
|
870
1017
|
if (!this.ptyProcess) return;
|
|
@@ -888,10 +1035,32 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
888
1035
|
this.submitRetryTimer = setTimeout(() => retrySubmitIfStuck(attempt + 1), retryDelayMs);
|
|
889
1036
|
};
|
|
890
1037
|
this.submitRetryTimer = setTimeout(() => retrySubmitIfStuck(1), retryDelayMs);
|
|
891
|
-
|
|
892
|
-
if (this.isWaitingForResponse) this.finishResponse();
|
|
893
|
-
}, this.timeouts.maxResponse);
|
|
1038
|
+
startResponseTimeout();
|
|
894
1039
|
};
|
|
1040
|
+
|
|
1041
|
+
if (this.submitStrategy === 'immediate') {
|
|
1042
|
+
this.submitPendingUntil = 0;
|
|
1043
|
+
this.ptyProcess.write(text + this.sendKey);
|
|
1044
|
+
this.submitRetryTimer = setTimeout(() => {
|
|
1045
|
+
this.submitRetryTimer = null;
|
|
1046
|
+
if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
|
|
1047
|
+
if (this.currentStatus !== 'generating') return;
|
|
1048
|
+
if ((this.responseBuffer || '').trim()) return;
|
|
1049
|
+
const screenText = this.terminalScreen.getText();
|
|
1050
|
+
if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
|
|
1051
|
+
LOG.info('CLI', `[${this.cliType}] Retrying submit key for stuck prompt (attempt 1)`);
|
|
1052
|
+
this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
1053
|
+
this.ptyProcess.write(this.sendKey);
|
|
1054
|
+
this.submitRetryUsed = true;
|
|
1055
|
+
}, retryDelayMs);
|
|
1056
|
+
startResponseTimeout();
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
if (submitDelayMs > 0) {
|
|
1061
|
+
this.submitPendingUntil = Date.now() + submitDelayMs;
|
|
1062
|
+
}
|
|
1063
|
+
this.ptyProcess.write(text);
|
|
895
1064
|
const submitStartedAt = Date.now();
|
|
896
1065
|
let lastNormalizedScreen = '';
|
|
897
1066
|
let lastScreenChangeAt = submitStartedAt;
|
|
@@ -951,10 +1120,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
951
1120
|
}
|
|
952
1121
|
|
|
953
1122
|
clearHistory(): void {
|
|
954
|
-
this.
|
|
955
|
-
this.
|
|
1123
|
+
this.committedMessages = [];
|
|
1124
|
+
this.syncMessageViews();
|
|
956
1125
|
this.accumulatedBuffer = '';
|
|
957
1126
|
this.accumulatedRawBuffer = '';
|
|
1127
|
+
this.terminalHistory = '';
|
|
1128
|
+
this.currentTurnScope = null;
|
|
958
1129
|
this.submitRetryUsed = false;
|
|
959
1130
|
this.submitRetryPromptSnippet = '';
|
|
960
1131
|
this.terminalScreen.reset();
|
|
@@ -1008,9 +1179,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1008
1179
|
spawnAt: this.spawnAt,
|
|
1009
1180
|
workingDir: this.workingDir,
|
|
1010
1181
|
messages: this.messages.slice(-20),
|
|
1182
|
+
committedMessages: this.committedMessages.slice(-20),
|
|
1011
1183
|
structuredMessages: this.structuredMessages.slice(-20),
|
|
1012
|
-
messageCount: this.
|
|
1184
|
+
messageCount: this.committedMessages.length,
|
|
1013
1185
|
screenText: this.terminalScreen.getText().slice(-4000),
|
|
1186
|
+
terminalHistory: this.terminalHistory.slice(-8000),
|
|
1187
|
+
currentTurnScope: this.currentTurnScope,
|
|
1014
1188
|
startupBuffer: this.startupBuffer.slice(-4000),
|
|
1015
1189
|
recentOutputBuffer: this.recentOutputBuffer.slice(-500),
|
|
1016
1190
|
settledBuffer: this.settledBuffer.slice(-500),
|
|
@@ -1023,6 +1197,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1023
1197
|
lastApprovalResolvedAt: this.lastApprovalResolvedAt,
|
|
1024
1198
|
sendDelayMs: this.sendDelayMs,
|
|
1025
1199
|
sendKey: this.sendKey,
|
|
1200
|
+
submitStrategy: this.submitStrategy,
|
|
1026
1201
|
submitPendingUntil: this.submitPendingUntil,
|
|
1027
1202
|
responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
|
|
1028
1203
|
resizeSuppressUntil: this.resizeSuppressUntil,
|
|
@@ -35,7 +35,13 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
35
35
|
_log(`${provider.category} adapter: ${(adapter as any).cliType}`);
|
|
36
36
|
const status = (adapter as any).getStatus?.();
|
|
37
37
|
if (status) {
|
|
38
|
-
return {
|
|
38
|
+
return {
|
|
39
|
+
success: true,
|
|
40
|
+
messages: status.messages || [],
|
|
41
|
+
status: status.status,
|
|
42
|
+
activeModal: status.activeModal,
|
|
43
|
+
terminalHistory: status.terminalHistory || '',
|
|
44
|
+
};
|
|
39
45
|
}
|
|
40
46
|
}
|
|
41
47
|
return { success: false, error: `${provider.category} adapter not found` };
|
|
@@ -31,6 +31,8 @@ export class ChatHistoryWriter {
|
|
|
31
31
|
private lastSeenCounts = new Map<string, number>();
|
|
32
32
|
/** Last seen message hash per agent (deduplication) */
|
|
33
33
|
private lastSeenHashes = new Map<string, Set<string>>();
|
|
34
|
+
/** Last seen append-only terminal transcript per agent */
|
|
35
|
+
private lastSeenTerminal = new Map<string, string>();
|
|
34
36
|
private rotated = false;
|
|
35
37
|
|
|
36
38
|
/**
|
|
@@ -107,10 +109,60 @@ export class ChatHistoryWriter {
|
|
|
107
109
|
}
|
|
108
110
|
}
|
|
109
111
|
|
|
112
|
+
appendTerminalHistory(
|
|
113
|
+
agentType: string,
|
|
114
|
+
terminalHistory: string,
|
|
115
|
+
sessionTitle?: string,
|
|
116
|
+
instanceId?: string,
|
|
117
|
+
): void {
|
|
118
|
+
const next = String(terminalHistory || '');
|
|
119
|
+
if (!next.trim()) return;
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const dedupKey = instanceId ? `${agentType}:${instanceId}:terminal` : `${agentType}:terminal`;
|
|
123
|
+
const prev = this.lastSeenTerminal.get(dedupKey) || '';
|
|
124
|
+
if (prev === next) return;
|
|
125
|
+
|
|
126
|
+
let delta = '';
|
|
127
|
+
if (!prev) {
|
|
128
|
+
delta = next;
|
|
129
|
+
} else if (next.startsWith(prev)) {
|
|
130
|
+
delta = next.slice(prev.length);
|
|
131
|
+
} else if (prev.includes(next)) {
|
|
132
|
+
this.lastSeenTerminal.set(dedupKey, next);
|
|
133
|
+
return;
|
|
134
|
+
} else {
|
|
135
|
+
delta = `\n\n[terminal snapshot reset ${new Date().toISOString()} | ${sessionTitle || agentType}]\n${next}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!delta) {
|
|
139
|
+
this.lastSeenTerminal.set(dedupKey, next);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const dir = path.join(HISTORY_DIR, this.sanitize(agentType));
|
|
144
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
145
|
+
|
|
146
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
147
|
+
const filePrefix = instanceId ? `${this.sanitize(instanceId)}_` : '';
|
|
148
|
+
const filePath = path.join(dir, `${filePrefix}${date}.terminal.log`);
|
|
149
|
+
fs.appendFileSync(filePath, delta, 'utf-8');
|
|
150
|
+
this.lastSeenTerminal.set(dedupKey, next);
|
|
151
|
+
|
|
152
|
+
if (!this.rotated) {
|
|
153
|
+
this.rotated = true;
|
|
154
|
+
this.rotateOldFiles().catch(() => {});
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
157
|
+
// Ignore terminal history save failures
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
110
161
|
/** Called when agent session is explicitly changed */
|
|
111
162
|
onSessionChange(agentType: string): void {
|
|
112
163
|
this.lastSeenHashes.delete(agentType);
|
|
113
164
|
this.lastSeenCounts.delete(agentType);
|
|
165
|
+
this.lastSeenTerminal.delete(`${agentType}:terminal`);
|
|
114
166
|
}
|
|
115
167
|
|
|
116
168
|
/** Delete history files older than 30 days */
|
|
@@ -125,7 +177,7 @@ export class ChatHistoryWriter {
|
|
|
125
177
|
for (const dir of agentDirs) {
|
|
126
178
|
const dirPath = path.join(HISTORY_DIR, dir.name);
|
|
127
179
|
const files = fs.readdirSync(dirPath)
|
|
128
|
-
.filter(f => f.endsWith('.jsonl'));
|
|
180
|
+
.filter(f => f.endsWith('.jsonl') || f.endsWith('.terminal.log'));
|
|
129
181
|
|
|
130
182
|
for (const file of files) {
|
|
131
183
|
const filePath = path.join(dirPath, file);
|
package/src/daemon/dev-server.ts
CHANGED
|
@@ -19,6 +19,7 @@ import * as fs from 'fs';
|
|
|
19
19
|
import * as path from 'path';
|
|
20
20
|
import * as os from 'os';
|
|
21
21
|
import type { ProviderLoader } from '../providers/provider-loader.js';
|
|
22
|
+
import type { ProviderCategory } from '../providers/contracts.js';
|
|
22
23
|
import type { ChildProcess } from 'child_process';
|
|
23
24
|
import type { DaemonCdpManager } from '../cdp/manager.js';
|
|
24
25
|
import type { ProviderInstanceManager } from '../providers/provider-instance-manager.js';
|
|
@@ -1107,11 +1108,7 @@ export class DevServer {
|
|
|
1107
1108
|
}
|
|
1108
1109
|
|
|
1109
1110
|
let targetDir: string;
|
|
1110
|
-
|
|
1111
|
-
targetDir = this.providerLoader.getUserProviderDir(category, type);
|
|
1112
|
-
} else {
|
|
1113
|
-
targetDir = this.providerLoader.getBuiltinProviderDir(category, type);
|
|
1114
|
-
}
|
|
1111
|
+
targetDir = this.providerLoader.getUserProviderDir(category, type);
|
|
1115
1112
|
|
|
1116
1113
|
const jsonPath = path.join(targetDir, 'provider.json');
|
|
1117
1114
|
if (fs.existsSync(jsonPath)) {
|
|
@@ -1883,11 +1880,10 @@ export class DevServer {
|
|
|
1883
1880
|
return fallback?.type || null;
|
|
1884
1881
|
}
|
|
1885
1882
|
|
|
1886
|
-
private loadAutoImplReferenceScripts(category:
|
|
1883
|
+
private loadAutoImplReferenceScripts(category: ProviderCategory, referenceType: string | null): Record<string, string> {
|
|
1887
1884
|
if (!referenceType) return {};
|
|
1888
1885
|
|
|
1889
|
-
const
|
|
1890
|
-
const refDir = path.join(builtinDir, category, referenceType);
|
|
1886
|
+
const refDir = this.providerLoader.getUpstreamProviderDir(category, referenceType);
|
|
1891
1887
|
if (!fs.existsSync(refDir)) return {};
|
|
1892
1888
|
|
|
1893
1889
|
const referenceScripts: Record<string, string> = {};
|
|
@@ -2138,7 +2134,7 @@ export class DevServer {
|
|
|
2138
2134
|
}
|
|
2139
2135
|
if (model) args.push('--model', model);
|
|
2140
2136
|
const escapedArgs = args.map(a => `'${a.replace(/'/g, "'\\''")}'`).join(' ');
|
|
2141
|
-
const metaPrompt = `Read the file at ${promptFile} and follow ALL instructions
|
|
2137
|
+
const metaPrompt = `Read the file at ${promptFile} and follow ALL instructions strictly. DO NOT spend time exploring the filesystem or other providers. You have full authority to implement ALL required script files and independently test them against 127.0.0.1:19280 via CDP CURL. Upon complete validation of ALL assigned files, print exactly "_PIPELINE_COMPLETE_SIGNAL_" to gracefully close the pipeline. DO NOT WAIT FOR APPROVAL, execute completely autonomously.`;
|
|
2142
2138
|
shellCmd = `${command} ${escapedArgs} "${metaPrompt}"`;
|
|
2143
2139
|
} else {
|
|
2144
2140
|
// Generic fallback: pipe prompt via stdin
|
|
@@ -2423,6 +2419,8 @@ export class DevServer {
|
|
|
2423
2419
|
lines.push('5. Do NOT modify `scripts.js` router — only edit individual `*.js` files');
|
|
2424
2420
|
lines.push('6. All scripts run in the browser (CDP evaluate) — use DOM APIs only');
|
|
2425
2421
|
lines.push('7. **Cross-Platform Compatibility**: If you use ARIA labels that contain keyboard shortcuts (e.g., `Cascade (⌘L)`), you MUST use substring matches (`aria-label*="Cascade"`) or handle both macOS (`⌘`, `Cmd`) and Windows (`Ctrl`) so the script does not break on other operating systems.');
|
|
2422
|
+
lines.push('8. **CRITICAL: DO NOT explore the filesystem or read other providers.** The reference implementation pattern is already provided below. Do not run `find`, `rg`, or `cat` on upstream providers. Doing so wastes context tokens and will crash the agent session. Focus entirely on modifying the target files.');
|
|
2423
|
+
lines.push('9. Do NOT delete any files. Implement the logic by replacing the empty stubs.');
|
|
2426
2424
|
lines.push('');
|
|
2427
2425
|
|
|
2428
2426
|
// ── Output contracts ──
|
|
@@ -81,15 +81,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
getState(): ProviderState {
|
|
84
|
-
const
|
|
85
|
-
const parsedStatus = this.adapter.getScriptParsedStatus();
|
|
86
|
-
|
|
87
|
-
// Prefer rich script parsed status if available
|
|
88
|
-
const adapterStatus = parsedStatus ? {
|
|
89
|
-
...rawStatus,
|
|
90
|
-
messages: parsedStatus.messages || rawStatus.messages,
|
|
91
|
-
activeModal: parsedStatus.activeModal || rawStatus.activeModal,
|
|
92
|
-
} : rawStatus;
|
|
84
|
+
const adapterStatus = this.adapter.getStatus();
|
|
93
85
|
|
|
94
86
|
const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
|
|
95
87
|
|
|
@@ -102,20 +94,6 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
102
94
|
});
|
|
103
95
|
|
|
104
96
|
// generating during partial response add
|
|
105
|
-
const partial = this.adapter.getPartialResponse();
|
|
106
|
-
const shouldAppendRawPartial = !parsedStatus;
|
|
107
|
-
if (shouldAppendRawPartial && adapterStatus.status === 'generating' && partial) {
|
|
108
|
-
const cleaned = partial.trim();
|
|
109
|
-
if (cleaned && cleaned !== '(generating...)') {
|
|
110
|
-
recentMessages.push({
|
|
111
|
-
role: 'assistant',
|
|
112
|
-
content: (cleaned.length > 8000 ? cleaned.slice(0, 8000) + '...' : cleaned) + '...',
|
|
113
|
-
timestamp: Date.now(),
|
|
114
|
-
meta: { streaming: true },
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
97
|
// Save history
|
|
120
98
|
if (recentMessages.length > 0) {
|
|
121
99
|
const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
|
|
@@ -126,6 +104,14 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
126
104
|
this.instanceId,
|
|
127
105
|
);
|
|
128
106
|
}
|
|
107
|
+
if (adapterStatus.terminalHistory?.trim()) {
|
|
108
|
+
this.historyWriter.appendTerminalHistory(
|
|
109
|
+
this.type,
|
|
110
|
+
adapterStatus.terminalHistory,
|
|
111
|
+
`${this.provider.name} · ${dirName}`,
|
|
112
|
+
this.instanceId,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
129
115
|
|
|
130
116
|
return {
|
|
131
117
|
type: this.type,
|
|
@@ -139,6 +125,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
139
125
|
status: adapterStatus.status,
|
|
140
126
|
messages: recentMessages,
|
|
141
127
|
activeModal: adapterStatus.activeModal,
|
|
128
|
+
terminalHistory: adapterStatus.terminalHistory,
|
|
142
129
|
inputContent: '',
|
|
143
130
|
},
|
|
144
131
|
workspace: this.workingDir,
|
|
@@ -200,7 +200,10 @@ export async function detectAllVersions(
|
|
|
200
200
|
detectedAt: new Date().toISOString(),
|
|
201
201
|
};
|
|
202
202
|
|
|
203
|
-
const
|
|
203
|
+
const verCmdConfig = (provider as any).versionCommand;
|
|
204
|
+
const versionCommand = typeof verCmdConfig === 'object' && verCmdConfig !== null
|
|
205
|
+
? verCmdConfig[currentOs]
|
|
206
|
+
: verCmdConfig;
|
|
204
207
|
|
|
205
208
|
if (provider.category === 'ide') {
|
|
206
209
|
// IDE: check app path + CLI
|