@adhdev/daemon-core 0.6.56 → 0.6.57
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 +6 -0
- package/dist/index.js +175 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +191 -21
package/package.json
CHANGED
|
@@ -193,6 +193,56 @@ function shSingleQuote(arg: string): string {
|
|
|
193
193
|
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
function estimatePromptDisplayLines(text: string, cols = 100): number {
|
|
197
|
+
const normalized = String(text || '').replace(/\r/g, '');
|
|
198
|
+
if (!normalized) return 1;
|
|
199
|
+
return normalized
|
|
200
|
+
.split('\n')
|
|
201
|
+
.reduce((sum, line) => sum + Math.max(1, Math.ceil(Math.max(1, line.length) / cols)), 0);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function extractPromptRetrySnippet(text: string): string {
|
|
205
|
+
const lines = String(text || '')
|
|
206
|
+
.replace(/\r/g, '')
|
|
207
|
+
.split('\n')
|
|
208
|
+
.map(line => line.trim())
|
|
209
|
+
.filter(Boolean);
|
|
210
|
+
const candidate = lines[lines.length - 1] || lines[0] || '';
|
|
211
|
+
return candidate.slice(-120);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function normalizePromptText(text: string): string {
|
|
215
|
+
return String(text || '').replace(/\s+/g, ' ').trim();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function compactPromptText(text: string): string {
|
|
219
|
+
return String(text || '').replace(/\s+/g, '').trim();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function promptLikelyVisible(screenText: string, promptSnippet: string): boolean {
|
|
223
|
+
const snippet = normalizePromptText(promptSnippet);
|
|
224
|
+
if (!snippet) return false;
|
|
225
|
+
|
|
226
|
+
const normalizedScreen = normalizePromptText(screenText);
|
|
227
|
+
if (normalizedScreen.includes(snippet)) return true;
|
|
228
|
+
|
|
229
|
+
const compactScreen = compactPromptText(screenText);
|
|
230
|
+
const compactSnippet = compactPromptText(promptSnippet);
|
|
231
|
+
if (compactSnippet && compactScreen.includes(compactSnippet)) return true;
|
|
232
|
+
|
|
233
|
+
const tokens = snippet
|
|
234
|
+
.split(/[^A-Za-z0-9_.:/-]+/)
|
|
235
|
+
.map(token => token.trim())
|
|
236
|
+
.filter(token => token.length >= 4);
|
|
237
|
+
if (tokens.length === 0) return false;
|
|
238
|
+
|
|
239
|
+
const required = Math.min(tokens.length, 3);
|
|
240
|
+
const matched = tokens.filter(token =>
|
|
241
|
+
normalizedScreen.includes(token) || compactScreen.includes(compactPromptText(token)),
|
|
242
|
+
).length;
|
|
243
|
+
return matched >= required;
|
|
244
|
+
}
|
|
245
|
+
|
|
196
246
|
/**
|
|
197
247
|
* Normalize provider.json for auto-implement approval detection.
|
|
198
248
|
* Kept for backward compat with dev-server auto-impl pipeline only.
|
|
@@ -273,6 +323,11 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
273
323
|
private settleTimer: NodeJS.Timeout | null = null;
|
|
274
324
|
private settledBuffer: string = '';
|
|
275
325
|
private submitPendingUntil = 0;
|
|
326
|
+
private responseSettleIgnoreUntil = 0;
|
|
327
|
+
private responseEpoch = 0;
|
|
328
|
+
private submitRetryTimer: NodeJS.Timeout | null = null;
|
|
329
|
+
private submitRetryUsed = false;
|
|
330
|
+
private submitRetryPromptSnippet = '';
|
|
276
331
|
|
|
277
332
|
// Resize redraw suppression
|
|
278
333
|
private resizeSuppressUntil: number = 0;
|
|
@@ -459,7 +514,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
459
514
|
this.startupParseGate = true;
|
|
460
515
|
this.startupBuffer = '';
|
|
461
516
|
this.terminalScreen.reset(40, 120);
|
|
462
|
-
this.ready =
|
|
517
|
+
this.ready = false;
|
|
463
518
|
this.setStatus('idle', 'pty_ready');
|
|
464
519
|
this.onStatusChange?.();
|
|
465
520
|
}
|
|
@@ -521,7 +576,9 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
521
576
|
|
|
522
577
|
if (isReady) {
|
|
523
578
|
this.startupParseGate = false;
|
|
579
|
+
this.ready = true;
|
|
524
580
|
LOG.info('CLI', `[${this.cliType}] Startup gate end (${elapsed}ms, scriptStatus=${scriptStatus})`);
|
|
581
|
+
this.onStatusChange?.();
|
|
525
582
|
} else {
|
|
526
583
|
return;
|
|
527
584
|
}
|
|
@@ -533,6 +590,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
533
590
|
|
|
534
591
|
private scheduleSettle(): void {
|
|
535
592
|
if (this.settleTimer) clearTimeout(this.settleTimer);
|
|
593
|
+
const settleEpoch = this.responseEpoch;
|
|
536
594
|
const delay = Math.max(
|
|
537
595
|
this.timeouts.outputSettle,
|
|
538
596
|
this.submitPendingUntil > Date.now()
|
|
@@ -541,14 +599,40 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
541
599
|
);
|
|
542
600
|
this.settleTimer = setTimeout(() => {
|
|
543
601
|
this.settleTimer = null;
|
|
602
|
+
if (settleEpoch !== this.responseEpoch) return;
|
|
544
603
|
this.settledBuffer = this.recentOutputBuffer;
|
|
545
604
|
this.evaluateSettled();
|
|
546
605
|
}, delay);
|
|
547
606
|
}
|
|
548
607
|
|
|
608
|
+
private armApprovalExitTimeout(): void {
|
|
609
|
+
if (this.approvalExitTimeout) clearTimeout(this.approvalExitTimeout);
|
|
610
|
+
this.approvalExitTimeout = setTimeout(() => {
|
|
611
|
+
if (this.currentStatus !== 'waiting_approval') return;
|
|
612
|
+
const tail = this.recentOutputBuffer;
|
|
613
|
+
const modal = this.runParseApproval(tail);
|
|
614
|
+
const stillWaiting = this.runDetectStatus(tail) === 'waiting_approval' || !!modal;
|
|
615
|
+
if (stillWaiting) {
|
|
616
|
+
this.activeModal = modal || this.activeModal || { message: 'Approval required', buttons: ['Allow', 'Deny'] };
|
|
617
|
+
this.onStatusChange?.();
|
|
618
|
+
this.armApprovalExitTimeout();
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
LOG.warn('CLI', `[${this.cliType}] Approval timeout — auto-clearing`);
|
|
622
|
+
this.activeModal = null;
|
|
623
|
+
this.lastApprovalResolvedAt = Date.now();
|
|
624
|
+
this.setStatus('idle', 'approval_timeout');
|
|
625
|
+
this.onStatusChange?.();
|
|
626
|
+
}, 60000);
|
|
627
|
+
}
|
|
628
|
+
|
|
549
629
|
private evaluateSettled(): void {
|
|
630
|
+
if (this.submitPendingUntil > Date.now()) return;
|
|
631
|
+
if (this.responseSettleIgnoreUntil > Date.now()) return;
|
|
550
632
|
const tail = this.settledBuffer;
|
|
551
|
-
const
|
|
633
|
+
const modal = this.runParseApproval(tail);
|
|
634
|
+
const rawScriptStatus = this.runDetectStatus(tail);
|
|
635
|
+
const scriptStatus = rawScriptStatus === 'waiting_approval' || modal ? 'waiting_approval' : rawScriptStatus;
|
|
552
636
|
if (!scriptStatus) return;
|
|
553
637
|
|
|
554
638
|
const prevStatus = this.currentStatus;
|
|
@@ -560,20 +644,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
560
644
|
this.setStatus('waiting_approval', 'script_detect');
|
|
561
645
|
|
|
562
646
|
// Use parseApproval script for modal info
|
|
563
|
-
const modal = this.runParseApproval(tail);
|
|
564
647
|
this.activeModal = modal || { message: 'Approval required', buttons: ['Allow', 'Deny'] };
|
|
565
648
|
|
|
566
649
|
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
567
|
-
|
|
568
|
-
this.approvalExitTimeout = setTimeout(() => {
|
|
569
|
-
if (this.currentStatus === 'waiting_approval') {
|
|
570
|
-
LOG.warn('CLI', `[${this.cliType}] Approval timeout — auto-clearing`);
|
|
571
|
-
this.activeModal = null;
|
|
572
|
-
this.lastApprovalResolvedAt = Date.now();
|
|
573
|
-
this.setStatus('idle', 'approval_timeout');
|
|
574
|
-
this.onStatusChange?.();
|
|
575
|
-
}
|
|
576
|
-
}, 60000);
|
|
650
|
+
this.armApprovalExitTimeout();
|
|
577
651
|
this.onStatusChange?.();
|
|
578
652
|
return;
|
|
579
653
|
}
|
|
@@ -607,7 +681,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
607
681
|
this.lastApprovalResolvedAt = Date.now();
|
|
608
682
|
}
|
|
609
683
|
if (this.isWaitingForResponse) {
|
|
610
|
-
this.
|
|
684
|
+
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
685
|
+
this.idleTimeout = setTimeout(() => {
|
|
686
|
+
if (this.isWaitingForResponse && this.currentStatus !== 'waiting_approval') {
|
|
687
|
+
this.finishResponse();
|
|
688
|
+
}
|
|
689
|
+
}, this.timeouts.idleFinish);
|
|
611
690
|
} else if (prevStatus !== 'idle') {
|
|
612
691
|
this.setStatus('idle', 'script_detect');
|
|
613
692
|
this.onStatusChange?.();
|
|
@@ -616,12 +695,18 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
616
695
|
}
|
|
617
696
|
|
|
618
697
|
private finishResponse(): void {
|
|
698
|
+
if (this.submitPendingUntil > Date.now()) return;
|
|
699
|
+
if (this.responseSettleIgnoreUntil > Date.now()) return;
|
|
619
700
|
if (this.responseTimeout) { clearTimeout(this.responseTimeout); this.responseTimeout = null; }
|
|
620
701
|
if (this.idleTimeout) { clearTimeout(this.idleTimeout); this.idleTimeout = null; }
|
|
621
702
|
if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
|
|
703
|
+
if (this.submitRetryTimer) { clearTimeout(this.submitRetryTimer); this.submitRetryTimer = null; }
|
|
622
704
|
|
|
623
705
|
this.responseBuffer = '';
|
|
624
706
|
this.isWaitingForResponse = false;
|
|
707
|
+
this.responseSettleIgnoreUntil = 0;
|
|
708
|
+
this.submitRetryUsed = false;
|
|
709
|
+
this.submitRetryPromptSnippet = '';
|
|
625
710
|
this.activeModal = null;
|
|
626
711
|
this.setStatus('idle', 'response_finished');
|
|
627
712
|
this.onStatusChange?.();
|
|
@@ -744,6 +829,12 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
744
829
|
|
|
745
830
|
async sendMessage(text: string): Promise<void> {
|
|
746
831
|
if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
|
|
832
|
+
if (this.startupParseGate) {
|
|
833
|
+
const deadline = Date.now() + 10000;
|
|
834
|
+
while (this.startupParseGate && Date.now() < deadline) {
|
|
835
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
836
|
+
}
|
|
837
|
+
}
|
|
747
838
|
if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
|
|
748
839
|
if (this.isWaitingForResponse) return;
|
|
749
840
|
|
|
@@ -751,24 +842,87 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
751
842
|
this.structuredMessages.push({ role: 'user', content: text, timestamp: Date.now() });
|
|
752
843
|
this.isWaitingForResponse = true;
|
|
753
844
|
this.responseBuffer = '';
|
|
845
|
+
this.submitRetryUsed = false;
|
|
846
|
+
this.submitRetryPromptSnippet = extractPromptRetrySnippet(text);
|
|
847
|
+
const normalizedPromptSnippet = normalizePromptText(this.submitRetryPromptSnippet);
|
|
848
|
+
if (this.submitRetryTimer) {
|
|
849
|
+
clearTimeout(this.submitRetryTimer);
|
|
850
|
+
this.submitRetryTimer = null;
|
|
851
|
+
}
|
|
852
|
+
const estimatedLines = estimatePromptDisplayLines(text);
|
|
853
|
+
const submitDelayMs = this.sendDelayMs + Math.min(2000, Math.max(0, estimatedLines - 1) * 350);
|
|
854
|
+
const maxEchoWaitMs = submitDelayMs + Math.max(1500, Math.min(5000, estimatedLines * 500));
|
|
855
|
+
const retryDelayMs = Math.max(350, Math.min(1500, Math.max(this.sendDelayMs, submitDelayMs)));
|
|
856
|
+
if (this.settleTimer) {
|
|
857
|
+
clearTimeout(this.settleTimer);
|
|
858
|
+
this.settleTimer = null;
|
|
859
|
+
}
|
|
860
|
+
this.responseEpoch += 1;
|
|
861
|
+
this.responseSettleIgnoreUntil = Date.now() + submitDelayMs + this.timeouts.outputSettle + 250;
|
|
754
862
|
this.setStatus('generating', 'sendMessage');
|
|
755
863
|
this.onStatusChange?.();
|
|
864
|
+
if (submitDelayMs > 0) {
|
|
865
|
+
this.submitPendingUntil = Date.now() + submitDelayMs;
|
|
866
|
+
}
|
|
756
867
|
this.ptyProcess.write(text);
|
|
757
868
|
|
|
758
869
|
const submit = () => {
|
|
759
870
|
if (!this.ptyProcess) return;
|
|
760
871
|
this.submitPendingUntil = 0;
|
|
761
872
|
this.ptyProcess.write(this.sendKey);
|
|
873
|
+
const retrySubmitIfStuck = (attempt: number) => {
|
|
874
|
+
this.submitRetryTimer = null;
|
|
875
|
+
if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
|
|
876
|
+
if (this.currentStatus !== 'generating') return;
|
|
877
|
+
if ((this.responseBuffer || '').trim()) return;
|
|
878
|
+
const screenText = this.terminalScreen.getText();
|
|
879
|
+
if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
|
|
880
|
+
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;
|
|
881
|
+
this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
882
|
+
LOG.info('CLI', `[${this.cliType}] Retrying submit key for stuck prompt (attempt ${attempt})`);
|
|
883
|
+
this.ptyProcess.write(this.sendKey);
|
|
884
|
+
if (attempt >= 3) {
|
|
885
|
+
this.submitRetryUsed = true;
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
this.submitRetryTimer = setTimeout(() => retrySubmitIfStuck(attempt + 1), retryDelayMs);
|
|
889
|
+
};
|
|
890
|
+
this.submitRetryTimer = setTimeout(() => retrySubmitIfStuck(1), retryDelayMs);
|
|
762
891
|
this.responseTimeout = setTimeout(() => {
|
|
763
892
|
if (this.isWaitingForResponse) this.finishResponse();
|
|
764
893
|
}, this.timeouts.maxResponse);
|
|
765
894
|
};
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
895
|
+
const submitStartedAt = Date.now();
|
|
896
|
+
let lastNormalizedScreen = '';
|
|
897
|
+
let lastScreenChangeAt = submitStartedAt;
|
|
898
|
+
const waitForEchoAndSubmit = () => {
|
|
899
|
+
if (!this.ptyProcess) return;
|
|
900
|
+
const now = Date.now();
|
|
901
|
+
const elapsed = now - submitStartedAt;
|
|
902
|
+
const screenText = this.terminalScreen.getText();
|
|
903
|
+
const normalizedScreen = normalizePromptText(screenText);
|
|
904
|
+
if (normalizedScreen !== lastNormalizedScreen) {
|
|
905
|
+
lastNormalizedScreen = normalizedScreen;
|
|
906
|
+
lastScreenChangeAt = now;
|
|
907
|
+
}
|
|
908
|
+
const echoVisible = !normalizedPromptSnippet || promptLikelyVisible(screenText, normalizedPromptSnippet);
|
|
909
|
+
|
|
910
|
+
if (echoVisible) {
|
|
911
|
+
const screenSettled = (now - lastScreenChangeAt) >= 500;
|
|
912
|
+
if (elapsed >= submitDelayMs && screenSettled) {
|
|
913
|
+
submit();
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
if (elapsed >= maxEchoWaitMs) {
|
|
919
|
+
submit();
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
setTimeout(waitForEchoAndSubmit, 50);
|
|
924
|
+
};
|
|
925
|
+
waitForEchoAndSubmit();
|
|
772
926
|
}
|
|
773
927
|
|
|
774
928
|
getPartialResponse(): string {
|
|
@@ -781,6 +935,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
781
935
|
shutdown(): void {
|
|
782
936
|
if (this.settleTimer) { clearTimeout(this.settleTimer); this.settleTimer = null; }
|
|
783
937
|
if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
|
|
938
|
+
if (this.submitRetryTimer) { clearTimeout(this.submitRetryTimer); this.submitRetryTimer = null; }
|
|
784
939
|
if (this.ptyProcess) {
|
|
785
940
|
this.ptyProcess.write('\x03');
|
|
786
941
|
setTimeout(() => {
|
|
@@ -800,6 +955,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
800
955
|
this.structuredMessages = [];
|
|
801
956
|
this.accumulatedBuffer = '';
|
|
802
957
|
this.accumulatedRawBuffer = '';
|
|
958
|
+
this.submitRetryUsed = false;
|
|
959
|
+
this.submitRetryPromptSnippet = '';
|
|
803
960
|
this.terminalScreen.reset();
|
|
804
961
|
this.onStatusChange?.();
|
|
805
962
|
}
|
|
@@ -812,7 +969,16 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
812
969
|
}
|
|
813
970
|
|
|
814
971
|
resolveModal(buttonIndex: number): void {
|
|
815
|
-
if (!this.ptyProcess || this.currentStatus !== 'waiting_approval') return;
|
|
972
|
+
if (!this.ptyProcess || (this.currentStatus !== 'waiting_approval' && !this.activeModal)) return;
|
|
973
|
+
this.activeModal = null;
|
|
974
|
+
this.lastApprovalResolvedAt = Date.now();
|
|
975
|
+
this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
976
|
+
if (this.approvalExitTimeout) {
|
|
977
|
+
clearTimeout(this.approvalExitTimeout);
|
|
978
|
+
this.approvalExitTimeout = null;
|
|
979
|
+
}
|
|
980
|
+
this.setStatus('generating', 'approval_resolved');
|
|
981
|
+
this.onStatusChange?.();
|
|
816
982
|
if (buttonIndex in this.approvalKeys) {
|
|
817
983
|
this.ptyProcess.write(this.approvalKeys[buttonIndex]);
|
|
818
984
|
} else {
|
|
@@ -855,6 +1021,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
855
1021
|
isWaitingForResponse: this.isWaitingForResponse,
|
|
856
1022
|
activeModal: this.activeModal,
|
|
857
1023
|
lastApprovalResolvedAt: this.lastApprovalResolvedAt,
|
|
1024
|
+
sendDelayMs: this.sendDelayMs,
|
|
1025
|
+
sendKey: this.sendKey,
|
|
1026
|
+
submitPendingUntil: this.submitPendingUntil,
|
|
1027
|
+
responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
|
|
858
1028
|
resizeSuppressUntil: this.resizeSuppressUntil,
|
|
859
1029
|
hasCliScripts: this.hasCliScripts(),
|
|
860
1030
|
scriptNames: Object.keys(this.cliScripts).filter(k => typeof (this.cliScripts as any)[k] === 'function'),
|