@adhdev/daemon-core 0.8.14 → 0.8.16
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 +9 -0
- package/dist/daemon/dev-auto-implement.d.ts +1 -0
- package/dist/index.js +278 -59
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +281 -62
- package/dist/index.mjs.map +1 -1
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +189 -18
- package/src/cli-adapters/pty-transport.ts +17 -6
- package/src/commands/upgrade-helper.ts +9 -3
- package/src/daemon/dev-auto-implement.ts +9 -0
- package/src/daemon/dev-cli-debug.ts +58 -21
|
@@ -66,6 +66,7 @@ export interface CliScriptInput {
|
|
|
66
66
|
screenText: string;
|
|
67
67
|
messages: CliChatMessage[];
|
|
68
68
|
partialResponse: string;
|
|
69
|
+
promptText?: string;
|
|
69
70
|
}
|
|
70
71
|
export interface CliTraceEntry {
|
|
71
72
|
id: number;
|
|
@@ -172,6 +173,8 @@ export declare class ProviderCliAdapter implements CliAdapter {
|
|
|
172
173
|
private submitRetryUsed;
|
|
173
174
|
private submitRetryPromptSnippet;
|
|
174
175
|
private idleFinishCandidate;
|
|
176
|
+
private finishRetryTimer;
|
|
177
|
+
private finishRetryCount;
|
|
175
178
|
private resizeSuppressUntil;
|
|
176
179
|
private statusHistory;
|
|
177
180
|
private cliScripts;
|
|
@@ -190,6 +193,8 @@ export declare class ProviderCliAdapter implements CliAdapter {
|
|
|
190
193
|
private static readonly MAX_TRACE_ENTRIES;
|
|
191
194
|
private readonly providerResolutionMeta;
|
|
192
195
|
private static readonly IDLE_FINISH_CONFIRM_MS;
|
|
196
|
+
private static readonly FINISH_RETRY_DELAY_MS;
|
|
197
|
+
private static readonly MAX_FINISH_RETRIES;
|
|
193
198
|
private syncMessageViews;
|
|
194
199
|
private normalizeParsedMessages;
|
|
195
200
|
private sliceFromOffset;
|
|
@@ -220,6 +225,10 @@ export declare class ProviderCliAdapter implements CliAdapter {
|
|
|
220
225
|
private scheduleSettle;
|
|
221
226
|
private armApprovalExitTimeout;
|
|
222
227
|
private looksLikeVisibleIdlePrompt;
|
|
228
|
+
private looksLikeVisibleAssistantCandidate;
|
|
229
|
+
private shouldRetryFinishResponse;
|
|
230
|
+
private getStartupConfirmationModal;
|
|
231
|
+
private shouldResolveModalWithEnter;
|
|
223
232
|
private waitForInteractivePrompt;
|
|
224
233
|
private evaluateSettled;
|
|
225
234
|
private finishResponse;
|
package/dist/index.js
CHANGED
|
@@ -705,17 +705,32 @@ var init_terminal_screen = __esm({
|
|
|
705
705
|
}
|
|
706
706
|
});
|
|
707
707
|
|
|
708
|
+
// src/cli-adapters/spawn-env.ts
|
|
709
|
+
var import_session_host_core;
|
|
710
|
+
var init_spawn_env = __esm({
|
|
711
|
+
"src/cli-adapters/spawn-env.ts"() {
|
|
712
|
+
"use strict";
|
|
713
|
+
import_session_host_core = require("@adhdev/session-host-core");
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
|
|
708
717
|
// src/cli-adapters/pty-transport.ts
|
|
709
|
-
|
|
718
|
+
function loadNodePty() {
|
|
719
|
+
if (cachedPty !== void 0) return cachedPty;
|
|
720
|
+
try {
|
|
721
|
+
cachedPty = require("node-pty");
|
|
722
|
+
(0, import_session_host_core.ensureNodePtySpawnHelperPermissions)();
|
|
723
|
+
} catch {
|
|
724
|
+
cachedPty = null;
|
|
725
|
+
}
|
|
726
|
+
return cachedPty;
|
|
727
|
+
}
|
|
728
|
+
var os7, cachedPty, NodePtyRuntimeTransport, NodePtyTransportFactory;
|
|
710
729
|
var init_pty_transport = __esm({
|
|
711
730
|
"src/cli-adapters/pty-transport.ts"() {
|
|
712
731
|
"use strict";
|
|
713
732
|
os7 = __toESM(require("os"));
|
|
714
|
-
|
|
715
|
-
pty = require("node-pty");
|
|
716
|
-
} catch {
|
|
717
|
-
pty = null;
|
|
718
|
-
}
|
|
733
|
+
init_spawn_env();
|
|
719
734
|
NodePtyRuntimeTransport = class {
|
|
720
735
|
constructor(handle) {
|
|
721
736
|
this.handle = handle;
|
|
@@ -746,6 +761,7 @@ var init_pty_transport = __esm({
|
|
|
746
761
|
};
|
|
747
762
|
NodePtyTransportFactory = class {
|
|
748
763
|
spawn(command, args, options) {
|
|
764
|
+
const pty = loadNodePty();
|
|
749
765
|
if (!pty) throw new Error("node-pty is not installed");
|
|
750
766
|
let cwd = options.cwd;
|
|
751
767
|
if (cwd) {
|
|
@@ -770,15 +786,6 @@ var init_pty_transport = __esm({
|
|
|
770
786
|
}
|
|
771
787
|
});
|
|
772
788
|
|
|
773
|
-
// src/cli-adapters/spawn-env.ts
|
|
774
|
-
var import_session_host_core;
|
|
775
|
-
var init_spawn_env = __esm({
|
|
776
|
-
"src/cli-adapters/spawn-env.ts"() {
|
|
777
|
-
"use strict";
|
|
778
|
-
import_session_host_core = require("@adhdev/session-host-core");
|
|
779
|
-
}
|
|
780
|
-
});
|
|
781
|
-
|
|
782
789
|
// src/cli-adapters/provider-cli-adapter.ts
|
|
783
790
|
var provider_cli_adapter_exports = {};
|
|
784
791
|
__export(provider_cli_adapter_exports, {
|
|
@@ -898,6 +905,40 @@ function normalizeScreenSnapshot(text) {
|
|
|
898
905
|
function normalizeComparableMessageContent(text) {
|
|
899
906
|
return String(text || "").replace(/\s+/g, " ").trim();
|
|
900
907
|
}
|
|
908
|
+
function trimPromptEchoPrefix(text, promptText) {
|
|
909
|
+
const prompt = normalizeComparableMessageContent(String(promptText || ""));
|
|
910
|
+
if (!prompt) return String(text || "");
|
|
911
|
+
const lines = String(text || "").split(/\r\n|\n|\r/g);
|
|
912
|
+
let dropCount = 0;
|
|
913
|
+
for (let index = 0; index < Math.min(lines.length, 6); index += 1) {
|
|
914
|
+
const fragment = normalizeComparableMessageContent(lines[index].replace(/^[.…]+\s*/, ""));
|
|
915
|
+
if (!fragment) {
|
|
916
|
+
if (dropCount === index) dropCount = index + 1;
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
const fragmentWordCount = fragment ? fragment.split(/\s+/).filter(Boolean).length : 0;
|
|
920
|
+
const canBePromptEcho = fragment.length >= 16 || fragmentWordCount >= 4;
|
|
921
|
+
if (canBePromptEcho && prompt.includes(fragment)) {
|
|
922
|
+
dropCount = index + 1;
|
|
923
|
+
continue;
|
|
924
|
+
}
|
|
925
|
+
break;
|
|
926
|
+
}
|
|
927
|
+
return lines.slice(dropCount).join("\n").trim();
|
|
928
|
+
}
|
|
929
|
+
function getLastUserPromptText(messages) {
|
|
930
|
+
const items = Array.isArray(messages) ? messages : [];
|
|
931
|
+
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
932
|
+
const message = items[index];
|
|
933
|
+
if (message?.role === "user" && typeof message.content === "string" && message.content.trim()) {
|
|
934
|
+
return message.content;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return "";
|
|
938
|
+
}
|
|
939
|
+
function looksLikeConfirmOnlyLabel(label) {
|
|
940
|
+
return /^(?:continue|confirm|ok|yes|trust|proceed|enter)$/i.test(String(label || "").trim());
|
|
941
|
+
}
|
|
901
942
|
function parsePatternEntry(x) {
|
|
902
943
|
if (x instanceof RegExp) return x;
|
|
903
944
|
if (x && typeof x === "object" && typeof x.source === "string") {
|
|
@@ -922,7 +963,7 @@ function normalizeCliProviderForRuntime(raw) {
|
|
|
922
963
|
}
|
|
923
964
|
};
|
|
924
965
|
}
|
|
925
|
-
var os8, path7, import_child_process4,
|
|
966
|
+
var os8, path7, import_child_process4, buildCliSpawnEnv, ProviderCliAdapter;
|
|
926
967
|
var init_provider_cli_adapter = __esm({
|
|
927
968
|
"src/cli-adapters/provider-cli-adapter.ts"() {
|
|
928
969
|
"use strict";
|
|
@@ -933,12 +974,6 @@ var init_provider_cli_adapter = __esm({
|
|
|
933
974
|
init_terminal_screen();
|
|
934
975
|
init_pty_transport();
|
|
935
976
|
init_spawn_env();
|
|
936
|
-
try {
|
|
937
|
-
pty2 = require("node-pty");
|
|
938
|
-
(0, import_session_host_core.ensureNodePtySpawnHelperPermissions)((msg) => LOG.info("CLI", msg));
|
|
939
|
-
} catch {
|
|
940
|
-
LOG.error("CLI", "[ProviderCliAdapter] node-pty not found. Terminal features disabled.");
|
|
941
|
-
}
|
|
942
977
|
buildCliSpawnEnv = import_session_host_core.sanitizeSpawnEnv;
|
|
943
978
|
ProviderCliAdapter = class _ProviderCliAdapter {
|
|
944
979
|
constructor(provider, workingDir, extraArgs = [], transportFactory = new NodePtyTransportFactory()) {
|
|
@@ -1040,6 +1075,8 @@ var init_provider_cli_adapter = __esm({
|
|
|
1040
1075
|
submitRetryUsed = false;
|
|
1041
1076
|
submitRetryPromptSnippet = "";
|
|
1042
1077
|
idleFinishCandidate = null;
|
|
1078
|
+
finishRetryTimer = null;
|
|
1079
|
+
finishRetryCount = 0;
|
|
1043
1080
|
// Resize redraw suppression
|
|
1044
1081
|
resizeSuppressUntil = 0;
|
|
1045
1082
|
// Debug: status transition history
|
|
@@ -1061,6 +1098,8 @@ var init_provider_cli_adapter = __esm({
|
|
|
1061
1098
|
static MAX_TRACE_ENTRIES = 250;
|
|
1062
1099
|
providerResolutionMeta;
|
|
1063
1100
|
static IDLE_FINISH_CONFIRM_MS = 900;
|
|
1101
|
+
static FINISH_RETRY_DELAY_MS = 300;
|
|
1102
|
+
static MAX_FINISH_RETRIES = 2;
|
|
1064
1103
|
syncMessageViews() {
|
|
1065
1104
|
this.messages = [...this.committedMessages];
|
|
1066
1105
|
this.structuredMessages = [...this.committedMessages];
|
|
@@ -1120,7 +1159,8 @@ var init_provider_cli_adapter = __esm({
|
|
|
1120
1159
|
recentBuffer: buffer.slice(-1e3) || this.recentOutputBuffer,
|
|
1121
1160
|
screenText: this.terminalScreen.getText(),
|
|
1122
1161
|
messages: [...baseMessages],
|
|
1123
|
-
partialResponse
|
|
1162
|
+
partialResponse,
|
|
1163
|
+
promptText: scope?.prompt || ""
|
|
1124
1164
|
};
|
|
1125
1165
|
}
|
|
1126
1166
|
setStatus(status, trigger) {
|
|
@@ -1363,6 +1403,11 @@ var init_provider_cli_adapter = __esm({
|
|
|
1363
1403
|
this.terminalScreen.reset(24, 80);
|
|
1364
1404
|
this.pendingTerminalQueryTail = "";
|
|
1365
1405
|
this.currentTurnScope = null;
|
|
1406
|
+
this.finishRetryCount = 0;
|
|
1407
|
+
if (this.finishRetryTimer) {
|
|
1408
|
+
clearTimeout(this.finishRetryTimer);
|
|
1409
|
+
this.finishRetryTimer = null;
|
|
1410
|
+
}
|
|
1366
1411
|
this.ready = false;
|
|
1367
1412
|
await this.ptyProcess.ready;
|
|
1368
1413
|
this.recordTrace("ready", {
|
|
@@ -1409,11 +1454,12 @@ var init_provider_cli_adapter = __esm({
|
|
|
1409
1454
|
if (this.startupParseGate) {
|
|
1410
1455
|
this.startupBuffer += cleanData;
|
|
1411
1456
|
const elapsed = Date.now() - this.spawnAt;
|
|
1412
|
-
const scriptStatus = this.runDetectStatus(this.startupBuffer);
|
|
1413
1457
|
const screenText = this.terminalScreen.getText() || "";
|
|
1458
|
+
const startupModal = this.getStartupConfirmationModal(screenText);
|
|
1459
|
+
const scriptStatus = startupModal ? "waiting_approval" : this.runDetectStatus(this.startupBuffer);
|
|
1414
1460
|
const hasInteractivePrompt = this.looksLikeVisibleIdlePrompt(screenText);
|
|
1415
1461
|
const startupStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
|
|
1416
|
-
const isReady = (scriptStatus === "idle" || scriptStatus === "waiting_approval") && hasInteractivePrompt && startupStableMs >= 700 || elapsed > 8e3 || this.startupBuffer.length > 12e3;
|
|
1462
|
+
const isReady = (scriptStatus === "idle" || scriptStatus === "waiting_approval") && hasInteractivePrompt && startupStableMs >= 700 || !!startupModal && startupStableMs >= 700 || elapsed > 8e3 || this.startupBuffer.length > 12e3;
|
|
1417
1463
|
if (isReady) {
|
|
1418
1464
|
this.startupParseGate = false;
|
|
1419
1465
|
this.ready = true;
|
|
@@ -1445,7 +1491,9 @@ var init_provider_cli_adapter = __esm({
|
|
|
1445
1491
|
this.approvalExitTimeout = setTimeout(() => {
|
|
1446
1492
|
if (this.currentStatus !== "waiting_approval") return;
|
|
1447
1493
|
const tail = this.recentOutputBuffer;
|
|
1448
|
-
const
|
|
1494
|
+
const screenText = this.terminalScreen.getText() || "";
|
|
1495
|
+
const startupModal = this.getStartupConfirmationModal(screenText);
|
|
1496
|
+
const modal = this.runParseApproval(tail) || startupModal;
|
|
1449
1497
|
const stillWaiting = this.runDetectStatus(tail) === "waiting_approval" || !!modal;
|
|
1450
1498
|
if (stillWaiting) {
|
|
1451
1499
|
this.activeModal = modal || this.activeModal || { message: "Approval required", buttons: ["Allow", "Deny"] };
|
|
@@ -1465,6 +1513,63 @@ var init_provider_cli_adapter = __esm({
|
|
|
1465
1513
|
if (!text.trim()) return false;
|
|
1466
1514
|
return /(^|\n)\s*[❯›>]\s*(?:\n|$)/m.test(text) || /⏎\s+send/i.test(text) || /\?\s*for\s*shortcuts/i.test(text) || /Type your message(?:\s+or\s+@path\/to\/file)?/i.test(text) || /workspace\s*\(\/directory\)/i.test(text) || /for\s*shortcuts/i.test(text);
|
|
1467
1515
|
}
|
|
1516
|
+
looksLikeVisibleAssistantCandidate(screenText) {
|
|
1517
|
+
const lines = sanitizeTerminalText(String(screenText || "")).split(/\r\n|\n|\r/g);
|
|
1518
|
+
for (const line of lines) {
|
|
1519
|
+
const trimmed = String(line || "").trim();
|
|
1520
|
+
if (!trimmed) continue;
|
|
1521
|
+
if (/^➜\s+\S+/.test(trimmed)) continue;
|
|
1522
|
+
if (/^Update available!/i.test(trimmed)) continue;
|
|
1523
|
+
if (/Claude Code v\d/i.test(trimmed)) continue;
|
|
1524
|
+
if (/^⏵⏵\s+accept edits on/i.test(trimmed)) continue;
|
|
1525
|
+
if (/^[◐◑◒◓◴◵◶◷◸◹◺◿].*\/effort/i.test(trimmed)) continue;
|
|
1526
|
+
if (/^[✻✶✳✢✽⠂⠐⠒⠓⠦⠴⠶⠷⠿]+$/.test(trimmed)) continue;
|
|
1527
|
+
if (/esc to (cancel|interrupt|stop)/i.test(trimmed)) continue;
|
|
1528
|
+
const assistantMatch = trimmed.match(/^⏺\s+(.+)$/);
|
|
1529
|
+
if (!assistantMatch) continue;
|
|
1530
|
+
const content = assistantMatch[1].trim();
|
|
1531
|
+
if (!content) continue;
|
|
1532
|
+
if (/^(?:Bash|Read|Write|Edit|MultiEdit|Task|Glob|Grep|LS|NotebookEdit)\(/.test(content)) continue;
|
|
1533
|
+
if (/This command requires approval|Do you want to proceed|Allow once|Always allow/i.test(content)) continue;
|
|
1534
|
+
return true;
|
|
1535
|
+
}
|
|
1536
|
+
return false;
|
|
1537
|
+
}
|
|
1538
|
+
shouldRetryFinishResponse(commitResult) {
|
|
1539
|
+
if (!this.currentTurnScope) return false;
|
|
1540
|
+
if (this.currentStatus === "waiting_approval" || this.activeModal) return false;
|
|
1541
|
+
if (this.finishRetryCount >= _ProviderCliAdapter.MAX_FINISH_RETRIES) return false;
|
|
1542
|
+
if (commitResult.hasAssistant && commitResult.assistantContent.trim()) return false;
|
|
1543
|
+
const screenText = this.terminalScreen.getText() || "";
|
|
1544
|
+
if (!this.looksLikeVisibleAssistantCandidate(screenText)) return false;
|
|
1545
|
+
const now = Date.now();
|
|
1546
|
+
const quietForMs = this.lastNonEmptyOutputAt ? now - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
|
|
1547
|
+
const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
|
|
1548
|
+
return quietForMs < 1200 || screenStableMs < 1200 || !commitResult.hasAssistant;
|
|
1549
|
+
}
|
|
1550
|
+
getStartupConfirmationModal(screenText) {
|
|
1551
|
+
const text = sanitizeTerminalText(String(screenText || ""));
|
|
1552
|
+
if (!text.trim()) return null;
|
|
1553
|
+
if (this.cliType === "claude-cli") {
|
|
1554
|
+
const hasTrustPrompt = /Quick safety check/i.test(text) || /Is this a project you trust/i.test(text) || /Do you trust (?:this project|the contents of this directory|the files in this folder)/i.test(text);
|
|
1555
|
+
const hasConfirmFooter = /Press Enter to (?:continue|confirm)/i.test(text) || /Enter to confirm/i.test(text) || /Esc to (?:cancel|exit)/i.test(text);
|
|
1556
|
+
if (hasTrustPrompt || hasConfirmFooter && /trust/i.test(text)) {
|
|
1557
|
+
return {
|
|
1558
|
+
message: "Confirm Claude Code project trust",
|
|
1559
|
+
buttons: ["Continue"]
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
return null;
|
|
1564
|
+
}
|
|
1565
|
+
shouldResolveModalWithEnter(modal, buttonIndex) {
|
|
1566
|
+
if (!modal || buttonIndex !== 0) return false;
|
|
1567
|
+
const buttons = Array.isArray(modal.buttons) ? modal.buttons : [];
|
|
1568
|
+
if (buttons.length !== 1) return false;
|
|
1569
|
+
const buttonLabel = String(buttons[0] || "").trim();
|
|
1570
|
+
const modalText = `${modal.message || ""} ${buttonLabel}`.trim();
|
|
1571
|
+
return looksLikeConfirmOnlyLabel(buttonLabel) || /Quick safety check|project trust|trust (?:this project|the contents of this directory|the files in this folder)|Enter to confirm/i.test(modalText);
|
|
1572
|
+
}
|
|
1468
1573
|
async waitForInteractivePrompt(maxWaitMs = 5e3) {
|
|
1469
1574
|
const startedAt = Date.now();
|
|
1470
1575
|
let loggedWait = false;
|
|
@@ -1514,9 +1619,10 @@ var init_provider_cli_adapter = __esm({
|
|
|
1514
1619
|
}
|
|
1515
1620
|
const tail = this.settledBuffer;
|
|
1516
1621
|
const screenText = this.terminalScreen.getText() || "";
|
|
1517
|
-
const
|
|
1622
|
+
const startupModal = this.getStartupConfirmationModal(screenText);
|
|
1623
|
+
const modal = this.runParseApproval(tail) || startupModal;
|
|
1518
1624
|
const rawScriptStatus = this.runDetectStatus(tail);
|
|
1519
|
-
const scriptStatus = rawScriptStatus;
|
|
1625
|
+
const scriptStatus = startupModal ? "waiting_approval" : rawScriptStatus;
|
|
1520
1626
|
const parsedTranscript = this.parseCurrentTranscript(
|
|
1521
1627
|
this.committedMessages,
|
|
1522
1628
|
this.responseBuffer,
|
|
@@ -1711,7 +1817,24 @@ var init_provider_cli_adapter = __esm({
|
|
|
1711
1817
|
this.recordTrace("finish_response", {
|
|
1712
1818
|
...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
|
|
1713
1819
|
});
|
|
1714
|
-
this.commitCurrentTranscript();
|
|
1820
|
+
const commitResult = this.commitCurrentTranscript();
|
|
1821
|
+
if (this.shouldRetryFinishResponse(commitResult)) {
|
|
1822
|
+
this.finishRetryCount += 1;
|
|
1823
|
+
this.recordTrace("finish_response_retry", {
|
|
1824
|
+
retryCount: this.finishRetryCount,
|
|
1825
|
+
retryDelayMs: _ProviderCliAdapter.FINISH_RETRY_DELAY_MS,
|
|
1826
|
+
assistantContent: this.summarizeTraceText(commitResult.assistantContent, 220),
|
|
1827
|
+
...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
|
|
1828
|
+
});
|
|
1829
|
+
if (this.finishRetryTimer) clearTimeout(this.finishRetryTimer);
|
|
1830
|
+
this.finishRetryTimer = setTimeout(() => {
|
|
1831
|
+
this.finishRetryTimer = null;
|
|
1832
|
+
if (this.isWaitingForResponse && this.currentStatus !== "waiting_approval") {
|
|
1833
|
+
this.finishResponse();
|
|
1834
|
+
}
|
|
1835
|
+
}, _ProviderCliAdapter.FINISH_RETRY_DELAY_MS);
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1715
1838
|
if (this.responseTimeout) {
|
|
1716
1839
|
clearTimeout(this.responseTimeout);
|
|
1717
1840
|
this.responseTimeout = null;
|
|
@@ -1728,11 +1851,16 @@ var init_provider_cli_adapter = __esm({
|
|
|
1728
1851
|
clearTimeout(this.submitRetryTimer);
|
|
1729
1852
|
this.submitRetryTimer = null;
|
|
1730
1853
|
}
|
|
1854
|
+
if (this.finishRetryTimer) {
|
|
1855
|
+
clearTimeout(this.finishRetryTimer);
|
|
1856
|
+
this.finishRetryTimer = null;
|
|
1857
|
+
}
|
|
1731
1858
|
this.responseBuffer = "";
|
|
1732
1859
|
this.isWaitingForResponse = false;
|
|
1733
1860
|
this.responseSettleIgnoreUntil = 0;
|
|
1734
1861
|
this.submitRetryUsed = false;
|
|
1735
1862
|
this.submitRetryPromptSnippet = "";
|
|
1863
|
+
this.finishRetryCount = 0;
|
|
1736
1864
|
this.currentTurnScope = null;
|
|
1737
1865
|
this.activeModal = null;
|
|
1738
1866
|
this.setStatus("idle", "response_finished");
|
|
@@ -1746,6 +1874,13 @@ var init_provider_cli_adapter = __esm({
|
|
|
1746
1874
|
);
|
|
1747
1875
|
if (parsed && Array.isArray(parsed.messages)) {
|
|
1748
1876
|
this.committedMessages = this.normalizeParsedMessages(parsed.messages);
|
|
1877
|
+
const promptForTrim = this.currentTurnScope?.prompt || getLastUserPromptText(this.committedMessages);
|
|
1878
|
+
if (promptForTrim) {
|
|
1879
|
+
const lastAssistantForTrim = [...this.committedMessages].reverse().find((message) => message.role === "assistant");
|
|
1880
|
+
if (lastAssistantForTrim) {
|
|
1881
|
+
lastAssistantForTrim.content = trimPromptEchoPrefix(lastAssistantForTrim.content, promptForTrim);
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1749
1884
|
this.syncMessageViews();
|
|
1750
1885
|
const lastAssistant = [...this.committedMessages].reverse().find((message) => message.role === "assistant");
|
|
1751
1886
|
this.recordTrace("commit_transcript", {
|
|
@@ -1761,7 +1896,15 @@ var init_provider_cli_adapter = __esm({
|
|
|
1761
1896
|
`[${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 || "-"}`
|
|
1762
1897
|
);
|
|
1763
1898
|
}
|
|
1899
|
+
return {
|
|
1900
|
+
hasAssistant: !!lastAssistant,
|
|
1901
|
+
assistantContent: lastAssistant?.content || ""
|
|
1902
|
+
};
|
|
1764
1903
|
}
|
|
1904
|
+
return {
|
|
1905
|
+
hasAssistant: false,
|
|
1906
|
+
assistantContent: ""
|
|
1907
|
+
};
|
|
1765
1908
|
}
|
|
1766
1909
|
// ─── Script Execution ──────────────────────────
|
|
1767
1910
|
runDetectStatus(text) {
|
|
@@ -1840,7 +1983,15 @@ var init_provider_cli_adapter = __esm({
|
|
|
1840
1983
|
if (!this.cliScripts?.parseOutput) return null;
|
|
1841
1984
|
try {
|
|
1842
1985
|
const input = this.buildParseInput(baseMessages, partialResponse, scope);
|
|
1843
|
-
|
|
1986
|
+
const parsed = this.cliScripts.parseOutput(input);
|
|
1987
|
+
const promptForTrim = scope?.prompt || getLastUserPromptText(baseMessages);
|
|
1988
|
+
if (parsed && Array.isArray(parsed.messages) && promptForTrim) {
|
|
1989
|
+
const lastAssistant = [...parsed.messages].reverse().find((message) => message?.role === "assistant" && typeof message.content === "string");
|
|
1990
|
+
if (lastAssistant) {
|
|
1991
|
+
lastAssistant.content = trimPromptEchoPrefix(lastAssistant.content, promptForTrim);
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
return parsed;
|
|
1844
1995
|
} catch (e) {
|
|
1845
1996
|
LOG.warn("CLI", `[${this.cliType}] parseOutput error: ${e.message}`);
|
|
1846
1997
|
return null;
|
|
@@ -1885,10 +2036,19 @@ ${data.message || ""}`.trim();
|
|
|
1885
2036
|
if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
|
|
1886
2037
|
if (this.isWaitingForResponse) return;
|
|
1887
2038
|
await this.waitForInteractivePrompt();
|
|
2039
|
+
const blockingModal = this.activeModal || this.getStartupConfirmationModal(this.terminalScreen.getText() || "");
|
|
2040
|
+
if (blockingModal || this.currentStatus === "waiting_approval") {
|
|
2041
|
+
throw new Error(`${this.cliName} is awaiting confirmation before it can accept a prompt`);
|
|
2042
|
+
}
|
|
1888
2043
|
this.committedMessages.push({ role: "user", content: text, timestamp: Date.now() });
|
|
1889
2044
|
this.syncMessageViews();
|
|
1890
2045
|
this.isWaitingForResponse = true;
|
|
1891
2046
|
this.responseBuffer = "";
|
|
2047
|
+
this.finishRetryCount = 0;
|
|
2048
|
+
if (this.finishRetryTimer) {
|
|
2049
|
+
clearTimeout(this.finishRetryTimer);
|
|
2050
|
+
this.finishRetryTimer = null;
|
|
2051
|
+
}
|
|
1892
2052
|
this.clearIdleFinishCandidate("send_message");
|
|
1893
2053
|
this.currentTurnScope = {
|
|
1894
2054
|
prompt: text,
|
|
@@ -2116,6 +2276,10 @@ ${data.message || ""}`.trim();
|
|
|
2116
2276
|
clearTimeout(this.submitRetryTimer);
|
|
2117
2277
|
this.submitRetryTimer = null;
|
|
2118
2278
|
}
|
|
2279
|
+
if (this.finishRetryTimer) {
|
|
2280
|
+
clearTimeout(this.finishRetryTimer);
|
|
2281
|
+
this.finishRetryTimer = null;
|
|
2282
|
+
}
|
|
2119
2283
|
if (this.responseTimeout) {
|
|
2120
2284
|
clearTimeout(this.responseTimeout);
|
|
2121
2285
|
this.responseTimeout = null;
|
|
@@ -2139,6 +2303,7 @@ ${data.message || ""}`.trim();
|
|
|
2139
2303
|
this.ptyOutputFlushTimer = null;
|
|
2140
2304
|
}
|
|
2141
2305
|
this.ptyOutputBuffer = "";
|
|
2306
|
+
this.finishRetryCount = 0;
|
|
2142
2307
|
if (this.ptyProcess) {
|
|
2143
2308
|
this.ptyProcess.write("");
|
|
2144
2309
|
setTimeout(() => {
|
|
@@ -2169,6 +2334,10 @@ ${data.message || ""}`.trim();
|
|
|
2169
2334
|
clearTimeout(this.submitRetryTimer);
|
|
2170
2335
|
this.submitRetryTimer = null;
|
|
2171
2336
|
}
|
|
2337
|
+
if (this.finishRetryTimer) {
|
|
2338
|
+
clearTimeout(this.finishRetryTimer);
|
|
2339
|
+
this.finishRetryTimer = null;
|
|
2340
|
+
}
|
|
2172
2341
|
if (this.responseTimeout) {
|
|
2173
2342
|
clearTimeout(this.responseTimeout);
|
|
2174
2343
|
this.responseTimeout = null;
|
|
@@ -2192,6 +2361,7 @@ ${data.message || ""}`.trim();
|
|
|
2192
2361
|
this.ptyOutputFlushTimer = null;
|
|
2193
2362
|
}
|
|
2194
2363
|
this.ptyOutputBuffer = "";
|
|
2364
|
+
this.finishRetryCount = 0;
|
|
2195
2365
|
if (this.ptyProcess) {
|
|
2196
2366
|
try {
|
|
2197
2367
|
if (typeof this.ptyProcess.detach === "function") {
|
|
@@ -2228,6 +2398,11 @@ ${data.message || ""}`.trim();
|
|
|
2228
2398
|
this.ptyOutputFlushTimer = null;
|
|
2229
2399
|
}
|
|
2230
2400
|
this.ptyOutputBuffer = "";
|
|
2401
|
+
if (this.finishRetryTimer) {
|
|
2402
|
+
clearTimeout(this.finishRetryTimer);
|
|
2403
|
+
this.finishRetryTimer = null;
|
|
2404
|
+
}
|
|
2405
|
+
this.finishRetryCount = 0;
|
|
2231
2406
|
this.terminalScreen.reset();
|
|
2232
2407
|
this.ptyProcess?.clearBuffer?.();
|
|
2233
2408
|
this.onStatusChange?.();
|
|
@@ -2247,10 +2422,11 @@ ${data.message || ""}`.trim();
|
|
|
2247
2422
|
}
|
|
2248
2423
|
resolveModal(buttonIndex) {
|
|
2249
2424
|
if (!this.ptyProcess || this.currentStatus !== "waiting_approval" && !this.activeModal) return;
|
|
2425
|
+
const modal = this.activeModal;
|
|
2250
2426
|
this.clearIdleFinishCandidate("resolve_modal");
|
|
2251
2427
|
this.recordTrace("resolve_modal", {
|
|
2252
2428
|
buttonIndex,
|
|
2253
|
-
activeModal:
|
|
2429
|
+
activeModal: modal
|
|
2254
2430
|
});
|
|
2255
2431
|
this.activeModal = null;
|
|
2256
2432
|
this.lastApprovalResolvedAt = Date.now();
|
|
@@ -2261,7 +2437,9 @@ ${data.message || ""}`.trim();
|
|
|
2261
2437
|
}
|
|
2262
2438
|
this.setStatus("generating", "approval_resolved");
|
|
2263
2439
|
this.onStatusChange?.();
|
|
2264
|
-
if (
|
|
2440
|
+
if (this.shouldResolveModalWithEnter(modal, buttonIndex)) {
|
|
2441
|
+
this.ptyProcess.write("\r");
|
|
2442
|
+
} else if (buttonIndex in this.approvalKeys) {
|
|
2265
2443
|
this.ptyProcess.write(this.approvalKeys[buttonIndex]);
|
|
2266
2444
|
} else {
|
|
2267
2445
|
const DOWN = "\x1B[B";
|
|
@@ -11673,7 +11851,10 @@ function appendUpgradeLog(message) {
|
|
|
11673
11851
|
}
|
|
11674
11852
|
}
|
|
11675
11853
|
function getNpmExecutable() {
|
|
11676
|
-
return
|
|
11854
|
+
return "npm";
|
|
11855
|
+
}
|
|
11856
|
+
function getNpmExecOptions() {
|
|
11857
|
+
return { shell: process.platform === "win32" };
|
|
11677
11858
|
}
|
|
11678
11859
|
function killPid(pid) {
|
|
11679
11860
|
try {
|
|
@@ -11735,9 +11916,10 @@ function removeDaemonPidFile() {
|
|
|
11735
11916
|
}
|
|
11736
11917
|
}
|
|
11737
11918
|
function cleanupStaleGlobalInstallDirs(pkgName) {
|
|
11738
|
-
const
|
|
11919
|
+
const npmExecOpts = getNpmExecOptions();
|
|
11920
|
+
const npmRoot = (0, import_child_process7.execFileSync)(getNpmExecutable(), ["root", "-g"], { encoding: "utf8", ...npmExecOpts }).trim();
|
|
11739
11921
|
if (!npmRoot) return;
|
|
11740
|
-
const npmPrefix = (0, import_child_process7.execFileSync)(getNpmExecutable(), ["prefix", "-g"], { encoding: "utf8" }).trim();
|
|
11922
|
+
const npmPrefix = (0, import_child_process7.execFileSync)(getNpmExecutable(), ["prefix", "-g"], { encoding: "utf8", ...npmExecOpts }).trim();
|
|
11741
11923
|
const binDir = process.platform === "win32" ? npmPrefix : path13.join(npmPrefix, "bin");
|
|
11742
11924
|
const packageBaseName = pkgName.startsWith("@") ? pkgName.split("/")[1] : pkgName;
|
|
11743
11925
|
const binNames = /* @__PURE__ */ new Set([packageBaseName]);
|
|
@@ -11798,7 +11980,8 @@ async function runDaemonUpgradeHelper(payload) {
|
|
|
11798
11980
|
{
|
|
11799
11981
|
encoding: "utf8",
|
|
11800
11982
|
stdio: "pipe",
|
|
11801
|
-
maxBuffer: 20 * 1024 * 1024
|
|
11983
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
11984
|
+
...getNpmExecOptions()
|
|
11802
11985
|
}
|
|
11803
11986
|
);
|
|
11804
11987
|
if (installOutput.trim()) {
|
|
@@ -15020,6 +15203,53 @@ async function runCliExerciseInternal(ctx, body) {
|
|
|
15020
15203
|
let lastModalKey = "";
|
|
15021
15204
|
let idleSince = 0;
|
|
15022
15205
|
let sawBusy = false;
|
|
15206
|
+
const noteStatus = (status) => {
|
|
15207
|
+
if (status !== lastStatus) {
|
|
15208
|
+
statusesSeen.push(status);
|
|
15209
|
+
lastStatus = status;
|
|
15210
|
+
}
|
|
15211
|
+
};
|
|
15212
|
+
const resolveActiveModalIfNeeded = (status, modal) => {
|
|
15213
|
+
if (!autoResolveApprovals || status !== "waiting_approval" || !modal || !Array.isArray(modal.buttons) || modal.buttons.length === 0) {
|
|
15214
|
+
return false;
|
|
15215
|
+
}
|
|
15216
|
+
const clampedIndex = Math.max(0, Math.min(Number(approvalButtonIndex) || 0, modal.buttons.length - 1));
|
|
15217
|
+
const modalKey = JSON.stringify({
|
|
15218
|
+
message: modal.message || "",
|
|
15219
|
+
buttons: modal.buttons,
|
|
15220
|
+
index: clampedIndex
|
|
15221
|
+
});
|
|
15222
|
+
if (modalKey === lastModalKey || typeof bundle?.adapter?.resolveModal !== "function") {
|
|
15223
|
+
return false;
|
|
15224
|
+
}
|
|
15225
|
+
lastModalKey = modalKey;
|
|
15226
|
+
approvalsResolved.push({
|
|
15227
|
+
at: Date.now(),
|
|
15228
|
+
buttonIndex: clampedIndex,
|
|
15229
|
+
label: modal.buttons[clampedIndex] || null
|
|
15230
|
+
});
|
|
15231
|
+
bundle.adapter.resolveModal(clampedIndex);
|
|
15232
|
+
return true;
|
|
15233
|
+
};
|
|
15234
|
+
const preflightStartedAt = Date.now();
|
|
15235
|
+
while (Date.now() - preflightStartedAt < Math.max(1e3, readyTimeoutMs)) {
|
|
15236
|
+
bundle = getCliTargetBundle(ctx, type, bundle.target.instanceId);
|
|
15237
|
+
if (!bundle) {
|
|
15238
|
+
throw new Error("CLI instance disappeared before exercise send");
|
|
15239
|
+
}
|
|
15240
|
+
const debug = typeof bundle.adapter.getDebugState === "function" ? bundle.adapter.getDebugState() : null;
|
|
15241
|
+
const trace = typeof bundle.adapter.getTraceState === "function" ? bundle.adapter.getTraceState(traceLimit) : null;
|
|
15242
|
+
const status = String(debug?.status || bundle.target.status || "unknown");
|
|
15243
|
+
const modal = debug?.activeModal || trace?.activeModal || null;
|
|
15244
|
+
noteStatus(status);
|
|
15245
|
+
if (resolveActiveModalIfNeeded(status, modal)) {
|
|
15246
|
+
await sleep(150);
|
|
15247
|
+
continue;
|
|
15248
|
+
}
|
|
15249
|
+
const startupParseGate = !!debug?.startupParseGate;
|
|
15250
|
+
if (status === "idle" && !startupParseGate) break;
|
|
15251
|
+
await sleep(150);
|
|
15252
|
+
}
|
|
15023
15253
|
ctx.instanceManager.sendEvent(bundle.target.instanceId, "send_message", { text });
|
|
15024
15254
|
while (Date.now() - startAt < Math.max(1e3, timeoutMs)) {
|
|
15025
15255
|
await sleep(150);
|
|
@@ -15034,32 +15264,14 @@ async function runCliExerciseInternal(ctx, body) {
|
|
|
15034
15264
|
const sawSendMessage = traceEntries.some((entry) => entry?.type === "send_message");
|
|
15035
15265
|
const sawSubmitWrite = traceEntries.some((entry) => entry?.type === "submit_write");
|
|
15036
15266
|
const hasTurnStarted = sawSendMessage || sawSubmitWrite || !!debug?.currentTurnScope;
|
|
15037
|
-
|
|
15038
|
-
statusesSeen.push(status);
|
|
15039
|
-
lastStatus = status;
|
|
15040
|
-
}
|
|
15267
|
+
noteStatus(status);
|
|
15041
15268
|
if (status === "generating" || status === "waiting_approval") {
|
|
15042
15269
|
sawBusy = true;
|
|
15043
15270
|
idleSince = 0;
|
|
15044
15271
|
}
|
|
15045
15272
|
const modal = debug?.activeModal || trace?.activeModal || null;
|
|
15046
|
-
if (
|
|
15047
|
-
|
|
15048
|
-
const modalKey = JSON.stringify({
|
|
15049
|
-
message: modal.message || "",
|
|
15050
|
-
buttons: modal.buttons,
|
|
15051
|
-
index: clampedIndex
|
|
15052
|
-
});
|
|
15053
|
-
if (modalKey !== lastModalKey && typeof bundle.adapter.resolveModal === "function") {
|
|
15054
|
-
lastModalKey = modalKey;
|
|
15055
|
-
approvalsResolved.push({
|
|
15056
|
-
at: Date.now(),
|
|
15057
|
-
buttonIndex: clampedIndex,
|
|
15058
|
-
label: modal.buttons[clampedIndex] || null
|
|
15059
|
-
});
|
|
15060
|
-
bundle.adapter.resolveModal(clampedIndex);
|
|
15061
|
-
continue;
|
|
15062
|
-
}
|
|
15273
|
+
if (resolveActiveModalIfNeeded(status, modal)) {
|
|
15274
|
+
continue;
|
|
15063
15275
|
}
|
|
15064
15276
|
const traceCount = Number(trace?.entryCount || 0);
|
|
15065
15277
|
const hasProgress = hasTurnStarted && (traceCount > preTraceCount || statusesSeen.length > 1 || approvalsResolved.length > 0);
|
|
@@ -16055,10 +16267,10 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
16055
16267
|
let isPty = false;
|
|
16056
16268
|
const { spawn: spawnFn } = await import("child_process");
|
|
16057
16269
|
try {
|
|
16058
|
-
const
|
|
16270
|
+
const pty = require("node-pty");
|
|
16059
16271
|
ctx.log(`Auto-implement spawn (PTY): ${shellCmd}`);
|
|
16060
16272
|
const isWin2 = os17.platform() === "win32";
|
|
16061
|
-
child =
|
|
16273
|
+
child = pty.spawn(isWin2 ? "cmd.exe" : process.env.SHELL || "/bin/zsh", [isWin2 ? "/c" : "-c", shellCmd], {
|
|
16062
16274
|
name: "xterm-256color",
|
|
16063
16275
|
cols: 120,
|
|
16064
16276
|
rows: 40,
|
|
@@ -16714,6 +16926,13 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
16714
16926
|
lines.push("19. Literal string checks are allowed only for stable proper nouns or exact product chrome that cannot be expressed safely as a broader pattern. Everything else should generalize.");
|
|
16715
16927
|
lines.push("20. When a bug comes from noisy PTY text, first normalize and classify the line family; do NOT just append another special-case substring to the parser.");
|
|
16716
16928
|
lines.push("");
|
|
16929
|
+
if (verification?.focusAreas?.length) {
|
|
16930
|
+
lines.push("## Provider-Specific Focus Areas");
|
|
16931
|
+
for (const area of verification.focusAreas) {
|
|
16932
|
+
lines.push(`- ${area}`);
|
|
16933
|
+
}
|
|
16934
|
+
lines.push("");
|
|
16935
|
+
}
|
|
16717
16936
|
lines.push("## Task");
|
|
16718
16937
|
lines.push(`Edit files in \`${providerDir}\` to implement: **${functions.join(", ")}**`);
|
|
16719
16938
|
lines.push("");
|