@adhdev/daemon-core 0.7.46 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-adapters/provider-cli-adapter.d.ts +33 -0
- package/dist/cli-adapters/session-host-transport.d.ts +1 -0
- package/dist/config/chat-history.d.ts +3 -0
- package/dist/config/config.d.ts +1 -1
- package/dist/daemon/dev-auto-implement.d.ts +18 -2
- package/dist/daemon/dev-cli-debug.d.ts +82 -0
- package/dist/daemon/dev-server.d.ts +7 -0
- package/dist/index.js +1625 -191
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1625 -191
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +5 -0
- package/dist/providers/contracts.d.ts +8 -0
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +417 -6
- package/src/cli-adapters/session-host-transport.ts +13 -1
- package/src/commands/chat-commands.ts +8 -0
- package/src/config/chat-history.ts +5 -1
- package/src/config/config.ts +2 -2
- package/src/daemon/dev-auto-implement.ts +371 -38
- package/src/daemon/dev-cli-debug.ts +839 -0
- package/src/daemon/dev-server.ts +29 -1
- package/src/providers/cli-provider-instance.ts +79 -1
- package/src/providers/contracts.ts +8 -0
- package/src/providers/provider-loader.ts +39 -0
package/dist/index.mjs
CHANGED
|
@@ -752,10 +752,10 @@ import * as os8 from "os";
|
|
|
752
752
|
import * as path7 from "path";
|
|
753
753
|
import { execSync as execSync3 } from "child_process";
|
|
754
754
|
function stripAnsi(str) {
|
|
755
|
-
return str.replace(/\x1B\[
|
|
755
|
+
return str.replace(/\x1B\][^\x07]*\x07/g, "").replace(/\x1B\][\s\S]*?\x1B\\/g, "").replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, "").replace(/\x1B\[\d*[A-HJKSTfG]/g, " ").replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "").replace(/ +/g, " ");
|
|
756
756
|
}
|
|
757
757
|
function stripTerminalNoise(str) {
|
|
758
|
-
return String(str || "").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/g, "").replace(/(^|[\s([])(?:\??\d{1,4}(?:;\d{1,4})*[A-Za-z])(?=$|[\s)\]])/g, "$1").replace(/(^|[\s([])(?:\[\??\d{1,4}(?:;\d{1,4})*[A-Za-z])(?=$|[\s)\]])/g, "$1").replace(/(^|[\s([])(?:\d{1,4};\?)(?=$|[\s)\]])/g, "$1").replace(/\r+/g, "\n").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").replace(/ {2,}/g, " ");
|
|
758
|
+
return String(str || "").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/g, "").replace(/(^|[\s([])(?:\??\d{1,4}(?:;\d{1,4})*[A-Za-z])(?=$|[\s)\]])/g, "$1").replace(/(^|[\s([])(?:\[\??\d{1,4}(?:;\d{1,4})*[A-Za-z])(?=$|[\s)\]])/g, "$1").replace(/(^|[\s([])(?:\d{1,4};\?)(?=$|[\s)\]])/g, "$1").replace(/(^|[\s([])(?:\d+\$r[0-9;\" ]*[A-Za-z]?)(?=$|[\s)\]])/g, "$1").replace(/(^|[\s([])(?:>\|[A-Za-z0-9_.:-]+(?:\([^)]*\))?)(?=$|[\s)\]])/g, "$1").replace(/(^|[\s([])(?:[A-Z]\d(?:\s+[A-Z]\d)+)(?=$|[\s)\]])/g, "$1").replace(/(^|[\s([])(?:\d+;[^\s)\]]+)(?=$|[\s)\]])/g, "$1").replace(/\r+/g, "\n").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").replace(/ {2,}/g, " ");
|
|
759
759
|
}
|
|
760
760
|
function sanitizeTerminalText(str) {
|
|
761
761
|
return stripTerminalNoise(stripAnsi(str));
|
|
@@ -798,12 +798,12 @@ function findBinary(name) {
|
|
|
798
798
|
function isScriptBinary(binaryPath) {
|
|
799
799
|
if (!path7.isAbsolute(binaryPath)) return false;
|
|
800
800
|
try {
|
|
801
|
-
const
|
|
802
|
-
const resolved =
|
|
801
|
+
const fs15 = __require("fs");
|
|
802
|
+
const resolved = fs15.realpathSync(binaryPath);
|
|
803
803
|
const head = Buffer.alloc(8);
|
|
804
|
-
const fd =
|
|
805
|
-
|
|
806
|
-
|
|
804
|
+
const fd = fs15.openSync(resolved, "r");
|
|
805
|
+
fs15.readSync(fd, head, 0, 8, 0);
|
|
806
|
+
fs15.closeSync(fd);
|
|
807
807
|
let i = 0;
|
|
808
808
|
if (head[0] === 239 && head[1] === 187 && head[2] === 191) i = 3;
|
|
809
809
|
return head[i] === 35 && head[i + 1] === 33;
|
|
@@ -814,12 +814,12 @@ function isScriptBinary(binaryPath) {
|
|
|
814
814
|
function looksLikeMachOOrElf(filePath) {
|
|
815
815
|
if (!path7.isAbsolute(filePath)) return false;
|
|
816
816
|
try {
|
|
817
|
-
const
|
|
818
|
-
const resolved =
|
|
817
|
+
const fs15 = __require("fs");
|
|
818
|
+
const resolved = fs15.realpathSync(filePath);
|
|
819
819
|
const buf = Buffer.alloc(8);
|
|
820
|
-
const fd =
|
|
821
|
-
|
|
822
|
-
|
|
820
|
+
const fd = fs15.openSync(resolved, "r");
|
|
821
|
+
fs15.readSync(fd, buf, 0, 8, 0);
|
|
822
|
+
fs15.closeSync(fd);
|
|
823
823
|
let i = 0;
|
|
824
824
|
if (buf[0] === 239 && buf[1] === 187 && buf[2] === 191) i = 3;
|
|
825
825
|
const b = buf.subarray(i);
|
|
@@ -872,6 +872,9 @@ function promptLikelyVisible(screenText, promptSnippet) {
|
|
|
872
872
|
).length;
|
|
873
873
|
return matched >= required;
|
|
874
874
|
}
|
|
875
|
+
function normalizeScreenSnapshot(text) {
|
|
876
|
+
return sanitizeTerminalText(String(text || "")).replace(/\s+/g, " ").trim();
|
|
877
|
+
}
|
|
875
878
|
function parsePatternEntry(x) {
|
|
876
879
|
if (x instanceof RegExp) return x;
|
|
877
880
|
if (x && typeof x === "object" && typeof x.source === "string") {
|
|
@@ -907,14 +910,14 @@ var init_provider_cli_adapter = __esm({
|
|
|
907
910
|
pty2 = __require("node-pty");
|
|
908
911
|
if (os8.platform() !== "win32") {
|
|
909
912
|
try {
|
|
910
|
-
const
|
|
913
|
+
const fs15 = __require("fs");
|
|
911
914
|
const ptyDir = path7.resolve(path7.dirname(__require.resolve("node-pty")), "..");
|
|
912
915
|
const platformArch = `${os8.platform()}-${os8.arch()}`;
|
|
913
916
|
const helper = path7.join(ptyDir, "prebuilds", platformArch, "spawn-helper");
|
|
914
|
-
if (
|
|
915
|
-
const stat =
|
|
917
|
+
if (fs15.existsSync(helper)) {
|
|
918
|
+
const stat = fs15.statSync(helper);
|
|
916
919
|
if (!(stat.mode & 73)) {
|
|
917
|
-
|
|
920
|
+
fs15.chmodSync(helper, stat.mode | 493);
|
|
918
921
|
LOG.info("CLI", "[node-pty] Fixed spawn-helper permissions");
|
|
919
922
|
}
|
|
920
923
|
}
|
|
@@ -948,10 +951,25 @@ var init_provider_cli_adapter = __esm({
|
|
|
948
951
|
this.sendDelayMs = typeof provider.sendDelayMs === "number" ? Math.max(0, provider.sendDelayMs) : 0;
|
|
949
952
|
this.sendKey = typeof provider.sendKey === "string" && provider.sendKey.length > 0 ? provider.sendKey : "\r";
|
|
950
953
|
this.submitStrategy = provider.submitStrategy === "immediate" ? "immediate" : "wait_for_echo";
|
|
954
|
+
this.providerResolutionMeta = {
|
|
955
|
+
type: provider.type,
|
|
956
|
+
name: provider.name,
|
|
957
|
+
resolvedVersion: provider._resolvedVersion || null,
|
|
958
|
+
resolvedOs: provider._resolvedOs || null,
|
|
959
|
+
providerDir: provider._resolvedProviderDir || null,
|
|
960
|
+
scriptDir: provider._resolvedScriptDir || null,
|
|
961
|
+
scriptsPath: provider._resolvedScriptsPath || null,
|
|
962
|
+
scriptsSource: provider._resolvedScriptsSource || null,
|
|
963
|
+
versionWarning: provider._versionWarning || null
|
|
964
|
+
};
|
|
951
965
|
this.cliScripts = provider.scripts || {};
|
|
952
966
|
const scriptNames = Object.keys(this.cliScripts).filter((k) => typeof this.cliScripts[k] === "function");
|
|
953
967
|
if (scriptNames.length > 0) {
|
|
954
968
|
LOG.info("CLI", `[${this.cliType}] CLI scripts: [${scriptNames.join(", ")}]`);
|
|
969
|
+
LOG.info(
|
|
970
|
+
"CLI",
|
|
971
|
+
`[${this.cliType}] Provider resolution: providerDir=${this.providerResolutionMeta.providerDir || "-"} scriptDir=${this.providerResolutionMeta.scriptDir || "-"} scriptsPath=${this.providerResolutionMeta.scriptsPath || "-"} source=${this.providerResolutionMeta.scriptsSource || "-"} version=${this.providerResolutionMeta.resolvedVersion || "-"}`
|
|
972
|
+
);
|
|
955
973
|
} else {
|
|
956
974
|
LOG.warn("CLI", `[${this.cliType}] \u26A0 No CLI scripts loaded! Provider needs scripts/{version}/scripts.js`);
|
|
957
975
|
}
|
|
@@ -984,6 +1002,10 @@ var init_provider_cli_adapter = __esm({
|
|
|
984
1002
|
ptyOutputBuffer = "";
|
|
985
1003
|
ptyOutputFlushTimer = null;
|
|
986
1004
|
pendingTerminalQueryTail = "";
|
|
1005
|
+
lastOutputAt = 0;
|
|
1006
|
+
lastNonEmptyOutputAt = 0;
|
|
1007
|
+
lastScreenChangeAt = 0;
|
|
1008
|
+
lastScreenSnapshot = "";
|
|
987
1009
|
// Server log forwarding
|
|
988
1010
|
serverConn = null;
|
|
989
1011
|
logBuffer = [];
|
|
@@ -1004,6 +1026,7 @@ var init_provider_cli_adapter = __esm({
|
|
|
1004
1026
|
submitRetryTimer = null;
|
|
1005
1027
|
submitRetryUsed = false;
|
|
1006
1028
|
submitRetryPromptSnippet = "";
|
|
1029
|
+
idleFinishCandidate = null;
|
|
1007
1030
|
// Resize redraw suppression
|
|
1008
1031
|
resizeSuppressUntil = 0;
|
|
1009
1032
|
// Debug: status transition history
|
|
@@ -1019,6 +1042,12 @@ var init_provider_cli_adapter = __esm({
|
|
|
1019
1042
|
/** Max accumulated buffer size (last 50KB) */
|
|
1020
1043
|
static MAX_ACCUMULATED_BUFFER = 5e4;
|
|
1021
1044
|
currentTurnScope = null;
|
|
1045
|
+
traceEntries = [];
|
|
1046
|
+
traceSeq = 0;
|
|
1047
|
+
traceSessionId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1048
|
+
static MAX_TRACE_ENTRIES = 250;
|
|
1049
|
+
providerResolutionMeta;
|
|
1050
|
+
static IDLE_FINISH_CONFIRM_MS = 900;
|
|
1022
1051
|
syncMessageViews() {
|
|
1023
1052
|
this.messages = [...this.committedMessages];
|
|
1024
1053
|
this.structuredMessages = [...this.committedMessages];
|
|
@@ -1054,8 +1083,89 @@ var init_provider_cli_adapter = __esm({
|
|
|
1054
1083
|
this.currentStatus = status;
|
|
1055
1084
|
this.statusHistory.push({ status, at: Date.now(), trigger });
|
|
1056
1085
|
if (this.statusHistory.length > 50) this.statusHistory.shift();
|
|
1086
|
+
this.recordTrace("status", {
|
|
1087
|
+
previousStatus: prev,
|
|
1088
|
+
trigger: trigger || null
|
|
1089
|
+
});
|
|
1057
1090
|
LOG.info("CLI", `[${this.cliType}] status: ${prev} \u2192 ${status}${trigger ? ` (${trigger})` : ""}`);
|
|
1058
1091
|
}
|
|
1092
|
+
clearIdleFinishCandidate(reason) {
|
|
1093
|
+
if (!this.idleFinishCandidate) return;
|
|
1094
|
+
this.recordTrace("idle_candidate_reset", {
|
|
1095
|
+
reason,
|
|
1096
|
+
candidate: this.idleFinishCandidate
|
|
1097
|
+
});
|
|
1098
|
+
this.idleFinishCandidate = null;
|
|
1099
|
+
}
|
|
1100
|
+
armIdleFinishCandidate(assistantLength) {
|
|
1101
|
+
const now = Date.now();
|
|
1102
|
+
this.idleFinishCandidate = {
|
|
1103
|
+
armedAt: now,
|
|
1104
|
+
lastOutputAt: this.lastOutputAt,
|
|
1105
|
+
lastScreenChangeAt: this.lastScreenChangeAt,
|
|
1106
|
+
responseEpoch: this.responseEpoch,
|
|
1107
|
+
assistantLength
|
|
1108
|
+
};
|
|
1109
|
+
this.recordTrace("idle_candidate_armed", {
|
|
1110
|
+
confirmMs: _ProviderCliAdapter.IDLE_FINISH_CONFIRM_MS,
|
|
1111
|
+
candidate: this.idleFinishCandidate,
|
|
1112
|
+
...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
|
|
1113
|
+
});
|
|
1114
|
+
if (this.settleTimer) clearTimeout(this.settleTimer);
|
|
1115
|
+
this.settleTimer = setTimeout(() => {
|
|
1116
|
+
this.settleTimer = null;
|
|
1117
|
+
this.settledBuffer = this.recentOutputBuffer;
|
|
1118
|
+
this.evaluateSettled();
|
|
1119
|
+
}, _ProviderCliAdapter.IDLE_FINISH_CONFIRM_MS);
|
|
1120
|
+
}
|
|
1121
|
+
summarizeTraceText(text, max = 800) {
|
|
1122
|
+
const value = sanitizeTerminalText(String(text || ""));
|
|
1123
|
+
if (value.length <= max) return value;
|
|
1124
|
+
return `\u2026${value.slice(-max)}`;
|
|
1125
|
+
}
|
|
1126
|
+
summarizeTraceMessages(messages, limit = 3) {
|
|
1127
|
+
return messages.slice(-limit).map((message) => ({
|
|
1128
|
+
role: message.role,
|
|
1129
|
+
content: this.summarizeTraceText(message.content, 240),
|
|
1130
|
+
timestamp: message.timestamp
|
|
1131
|
+
}));
|
|
1132
|
+
}
|
|
1133
|
+
buildTraceParseSnapshot(scope, partialResponse = "") {
|
|
1134
|
+
const scopedBuffer = scope ? this.sliceFromOffset(this.accumulatedBuffer, scope.bufferStart) || this.accumulatedBuffer : this.accumulatedBuffer;
|
|
1135
|
+
const scopedRawBuffer = scope ? this.sliceFromOffset(this.accumulatedRawBuffer, scope.rawBufferStart) || this.accumulatedRawBuffer : this.accumulatedRawBuffer;
|
|
1136
|
+
return {
|
|
1137
|
+
currentTurnScope: scope || null,
|
|
1138
|
+
responseBuffer: this.summarizeTraceText(this.responseBuffer, 1200),
|
|
1139
|
+
partialResponse: this.summarizeTraceText(partialResponse || this.responseBuffer, 1200),
|
|
1140
|
+
turnBuffer: this.summarizeTraceText(scopedBuffer, 1600),
|
|
1141
|
+
turnRawPreview: this.summarizeTraceText(scopedRawBuffer, 1600),
|
|
1142
|
+
turnSanitizedRawPreview: this.summarizeTraceText(sanitizeTerminalText(scopedRawBuffer), 1600)
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
recordTrace(type, payload = {}) {
|
|
1146
|
+
const entry = {
|
|
1147
|
+
id: ++this.traceSeq,
|
|
1148
|
+
at: Date.now(),
|
|
1149
|
+
type,
|
|
1150
|
+
status: this.currentStatus,
|
|
1151
|
+
isWaitingForResponse: this.isWaitingForResponse,
|
|
1152
|
+
activeModal: this.activeModal ? { message: this.activeModal.message, buttons: [...this.activeModal.buttons] } : null,
|
|
1153
|
+
payload
|
|
1154
|
+
};
|
|
1155
|
+
this.traceEntries.push(entry);
|
|
1156
|
+
if (this.traceEntries.length > _ProviderCliAdapter.MAX_TRACE_ENTRIES) {
|
|
1157
|
+
this.traceEntries.splice(0, this.traceEntries.length - _ProviderCliAdapter.MAX_TRACE_ENTRIES);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
resetTraceSession() {
|
|
1161
|
+
this.traceEntries = [];
|
|
1162
|
+
this.traceSeq = 0;
|
|
1163
|
+
this.traceSessionId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1164
|
+
this.recordTrace("session_start", {
|
|
1165
|
+
providerType: this.cliType,
|
|
1166
|
+
workingDir: this.workingDir
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1059
1169
|
// Resolved timeouts
|
|
1060
1170
|
timeouts;
|
|
1061
1171
|
// Provider approval key mapping
|
|
@@ -1101,6 +1211,7 @@ var init_provider_cli_adapter = __esm({
|
|
|
1101
1211
|
const isWin = os8.platform() === "win32";
|
|
1102
1212
|
const allArgs = [...spawnConfig.args, ...this.extraArgs];
|
|
1103
1213
|
LOG.info("CLI", `[${this.cliType}] Spawning in ${this.workingDir}`);
|
|
1214
|
+
this.resetTraceSession();
|
|
1104
1215
|
let shellCmd;
|
|
1105
1216
|
let shellArgs;
|
|
1106
1217
|
const useShellUnix = !isWin && (!!spawnConfig.shell || !path7.isAbsolute(binaryPath) || isScriptBinary(binaryPath) || !looksLikeMachOOrElf(binaryPath));
|
|
@@ -1126,6 +1237,14 @@ var init_provider_cli_adapter = __esm({
|
|
|
1126
1237
|
cwd: this.workingDir,
|
|
1127
1238
|
env: buildCliSpawnEnv(process.env, spawnConfig.env)
|
|
1128
1239
|
};
|
|
1240
|
+
this.recordTrace("spawn", {
|
|
1241
|
+
shellCommand: shellCmd,
|
|
1242
|
+
shellArgs,
|
|
1243
|
+
cwd: ptyOpts.cwd,
|
|
1244
|
+
cols: ptyOpts.cols,
|
|
1245
|
+
rows: ptyOpts.rows,
|
|
1246
|
+
providerResolution: this.providerResolutionMeta
|
|
1247
|
+
});
|
|
1129
1248
|
try {
|
|
1130
1249
|
this.ptyProcess = this.transportFactory.spawn(shellCmd, shellArgs, ptyOpts);
|
|
1131
1250
|
} catch (err) {
|
|
@@ -1168,6 +1287,7 @@ var init_provider_cli_adapter = __esm({
|
|
|
1168
1287
|
this.ptyProcess.onExit(({ exitCode }) => {
|
|
1169
1288
|
LOG.info("CLI", `[${this.cliType}] Exit code ${exitCode}`);
|
|
1170
1289
|
this.flushPendingOutputParse();
|
|
1290
|
+
this.recordTrace("exit", { exitCode });
|
|
1171
1291
|
this.ptyProcess = null;
|
|
1172
1292
|
this.setStatus("stopped", "pty_exit");
|
|
1173
1293
|
this.ready = false;
|
|
@@ -1183,6 +1303,9 @@ var init_provider_cli_adapter = __esm({
|
|
|
1183
1303
|
this.currentTurnScope = null;
|
|
1184
1304
|
this.ready = false;
|
|
1185
1305
|
await this.ptyProcess.ready;
|
|
1306
|
+
this.recordTrace("ready", {
|
|
1307
|
+
runtimeMeta: this.getRuntimeMetadata()
|
|
1308
|
+
});
|
|
1186
1309
|
this.setStatus("idle", "pty_ready");
|
|
1187
1310
|
this.onStatusChange?.();
|
|
1188
1311
|
}
|
|
@@ -1190,6 +1313,24 @@ var init_provider_cli_adapter = __esm({
|
|
|
1190
1313
|
handleOutput(rawData) {
|
|
1191
1314
|
this.terminalScreen.write(rawData);
|
|
1192
1315
|
const cleanData = sanitizeTerminalText(rawData);
|
|
1316
|
+
const now = Date.now();
|
|
1317
|
+
const normalizedScreenSnapshot = normalizeScreenSnapshot(this.terminalScreen.getText());
|
|
1318
|
+
this.lastOutputAt = now;
|
|
1319
|
+
if (cleanData.trim()) this.lastNonEmptyOutputAt = now;
|
|
1320
|
+
if (normalizedScreenSnapshot !== this.lastScreenSnapshot) {
|
|
1321
|
+
this.lastScreenSnapshot = normalizedScreenSnapshot;
|
|
1322
|
+
this.lastScreenChangeAt = now;
|
|
1323
|
+
}
|
|
1324
|
+
if (this.idleFinishCandidate && (rawData.length > 0 || cleanData.length > 0)) {
|
|
1325
|
+
this.clearIdleFinishCandidate("new_output");
|
|
1326
|
+
}
|
|
1327
|
+
this.recordTrace("output", {
|
|
1328
|
+
rawLength: rawData.length,
|
|
1329
|
+
cleanLength: cleanData.length,
|
|
1330
|
+
rawPreview: this.summarizeTraceText(rawData, 300),
|
|
1331
|
+
cleanPreview: this.summarizeTraceText(cleanData, 300),
|
|
1332
|
+
screenText: this.summarizeTraceText(this.terminalScreen.getText(), 1200)
|
|
1333
|
+
});
|
|
1193
1334
|
if (this.isWaitingForResponse && cleanData) {
|
|
1194
1335
|
this.responseBuffer = (this.responseBuffer + cleanData).slice(-8e3);
|
|
1195
1336
|
}
|
|
@@ -1207,11 +1348,17 @@ var init_provider_cli_adapter = __esm({
|
|
|
1207
1348
|
this.startupBuffer += cleanData;
|
|
1208
1349
|
const elapsed = Date.now() - this.spawnAt;
|
|
1209
1350
|
const scriptStatus = this.runDetectStatus(this.startupBuffer);
|
|
1210
|
-
const
|
|
1351
|
+
const screenText = this.terminalScreen.getText() || "";
|
|
1352
|
+
const hasInteractivePrompt = this.looksLikeVisibleIdlePrompt(screenText);
|
|
1353
|
+
const startupStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
|
|
1354
|
+
const isReady = (scriptStatus === "idle" || scriptStatus === "waiting_approval") && hasInteractivePrompt && startupStableMs >= 700 || elapsed > 8e3 || this.startupBuffer.length > 12e3;
|
|
1211
1355
|
if (isReady) {
|
|
1212
1356
|
this.startupParseGate = false;
|
|
1213
1357
|
this.ready = true;
|
|
1214
|
-
LOG.info(
|
|
1358
|
+
LOG.info(
|
|
1359
|
+
"CLI",
|
|
1360
|
+
`[${this.cliType}] Startup ready (${elapsed}ms, scriptStatus=${scriptStatus}, prompt=${hasInteractivePrompt}, stableMs=${startupStableMs}) providerDir=${this.providerResolutionMeta.providerDir || "-"} scriptDir=${this.providerResolutionMeta.scriptDir || "-"} scriptsPath=${this.providerResolutionMeta.scriptsPath || "-"}`
|
|
1361
|
+
);
|
|
1215
1362
|
this.onStatusChange?.();
|
|
1216
1363
|
}
|
|
1217
1364
|
}
|
|
@@ -1256,6 +1403,41 @@ var init_provider_cli_adapter = __esm({
|
|
|
1256
1403
|
if (!text.trim()) return false;
|
|
1257
1404
|
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);
|
|
1258
1405
|
}
|
|
1406
|
+
async waitForInteractivePrompt(maxWaitMs = 5e3) {
|
|
1407
|
+
const startedAt = Date.now();
|
|
1408
|
+
let loggedWait = false;
|
|
1409
|
+
while (Date.now() - startedAt < maxWaitMs) {
|
|
1410
|
+
const screenText = this.terminalScreen.getText() || "";
|
|
1411
|
+
const hasPrompt = this.looksLikeVisibleIdlePrompt(screenText);
|
|
1412
|
+
const stableMs = this.lastScreenChangeAt ? Date.now() - this.lastScreenChangeAt : 0;
|
|
1413
|
+
const recentlyOutput = this.lastNonEmptyOutputAt ? Date.now() - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
|
|
1414
|
+
const status = this.runDetectStatus(this.recentOutputBuffer) || this.currentStatus;
|
|
1415
|
+
const startupLikelyActive = /Welcome back|Tips for getting|Recent activity|Claude Code v\d/i.test(screenText);
|
|
1416
|
+
const interactiveReady = hasPrompt && stableMs >= 700 && recentlyOutput >= 350 && status !== "starting" && status !== "generating";
|
|
1417
|
+
if (interactiveReady) {
|
|
1418
|
+
if (loggedWait) {
|
|
1419
|
+
LOG.info(
|
|
1420
|
+
"CLI",
|
|
1421
|
+
`[${this.cliType}] Interactive prompt ready after ${Date.now() - startedAt}ms (stableMs=${stableMs}, recentOutputMs=${recentlyOutput}, startup=${startupLikelyActive})`
|
|
1422
|
+
);
|
|
1423
|
+
}
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
if (!loggedWait && Date.now() - startedAt >= 400) {
|
|
1427
|
+
loggedWait = true;
|
|
1428
|
+
LOG.info(
|
|
1429
|
+
"CLI",
|
|
1430
|
+
`[${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)}`
|
|
1431
|
+
);
|
|
1432
|
+
}
|
|
1433
|
+
await new Promise((resolve10) => setTimeout(resolve10, 50));
|
|
1434
|
+
}
|
|
1435
|
+
const finalScreenText = this.terminalScreen.getText() || "";
|
|
1436
|
+
LOG.warn(
|
|
1437
|
+
"CLI",
|
|
1438
|
+
`[${this.cliType}] Interactive prompt wait timed out after ${maxWaitMs}ms; proceeding with screen=${JSON.stringify(this.summarizeTraceText(finalScreenText, 240)).slice(0, 280)}`
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1259
1441
|
evaluateSettled() {
|
|
1260
1442
|
const now = Date.now();
|
|
1261
1443
|
if (this.submitPendingUntil > now || this.responseSettleIgnoreUntil > now) {
|
|
@@ -1273,6 +1455,30 @@ var init_provider_cli_adapter = __esm({
|
|
|
1273
1455
|
const modal = this.runParseApproval(tail);
|
|
1274
1456
|
const rawScriptStatus = this.runDetectStatus(tail);
|
|
1275
1457
|
const scriptStatus = rawScriptStatus;
|
|
1458
|
+
const parsedTranscript = this.parseCurrentTranscript(
|
|
1459
|
+
this.committedMessages,
|
|
1460
|
+
this.responseBuffer,
|
|
1461
|
+
this.currentTurnScope
|
|
1462
|
+
);
|
|
1463
|
+
const parsedMessages = Array.isArray(parsedTranscript?.messages) ? this.normalizeParsedMessages(parsedTranscript.messages) : [];
|
|
1464
|
+
const lastParsedAssistant = [...parsedMessages].reverse().find((message) => message.role === "assistant");
|
|
1465
|
+
this.recordTrace("settled", {
|
|
1466
|
+
tail: this.summarizeTraceText(tail, 500),
|
|
1467
|
+
screenText: this.summarizeTraceText(screenText, 1200),
|
|
1468
|
+
detectStatus: scriptStatus,
|
|
1469
|
+
parsedStatus: parsedTranscript?.status || null,
|
|
1470
|
+
parsedMessageCount: parsedMessages.length,
|
|
1471
|
+
parsedLastAssistant: lastParsedAssistant ? this.summarizeTraceText(lastParsedAssistant.content, 280) : "",
|
|
1472
|
+
parsedActiveModal: parsedTranscript?.activeModal ?? null,
|
|
1473
|
+
approval: modal,
|
|
1474
|
+
...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
|
|
1475
|
+
});
|
|
1476
|
+
if (this.currentTurnScope && !lastParsedAssistant) {
|
|
1477
|
+
LOG.info(
|
|
1478
|
+
"CLI",
|
|
1479
|
+
`[${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 || "-"}`
|
|
1480
|
+
);
|
|
1481
|
+
}
|
|
1276
1482
|
if (!scriptStatus) return;
|
|
1277
1483
|
const prevStatus = this.currentStatus;
|
|
1278
1484
|
const clearPendingScriptStatus = () => {
|
|
@@ -1308,6 +1514,7 @@ var init_provider_cli_adapter = __esm({
|
|
|
1308
1514
|
clearPendingScriptStatus();
|
|
1309
1515
|
}
|
|
1310
1516
|
if (scriptStatus === "waiting_approval") {
|
|
1517
|
+
this.clearIdleFinishCandidate("waiting_approval");
|
|
1311
1518
|
const inCooldown = this.lastApprovalResolvedAt && Date.now() - this.lastApprovalResolvedAt < this.timeouts.approvalCooldown;
|
|
1312
1519
|
const visibleIdlePrompt = this.looksLikeVisibleIdlePrompt(screenText);
|
|
1313
1520
|
if ((inCooldown || visibleIdlePrompt) && !modal) {
|
|
@@ -1341,6 +1548,7 @@ var init_provider_cli_adapter = __esm({
|
|
|
1341
1548
|
}
|
|
1342
1549
|
}
|
|
1343
1550
|
if (scriptStatus === "generating") {
|
|
1551
|
+
this.clearIdleFinishCandidate("generating");
|
|
1344
1552
|
const effectiveScreenText = screenText || this.accumulatedBuffer;
|
|
1345
1553
|
const noActiveTurn = !this.currentTurnScope;
|
|
1346
1554
|
const looksIdleChrome = /(^|\n)\s*[❯›>]\s*(?:\n|$)/m.test(effectiveScreenText) || /accept edits on/i.test(effectiveScreenText) && (/Update available!/i.test(screenText) || /\/effort/i.test(screenText) || /^.*➜\s+\S+/m.test(effectiveScreenText));
|
|
@@ -1377,13 +1585,58 @@ var init_provider_cli_adapter = __esm({
|
|
|
1377
1585
|
this.lastApprovalResolvedAt = Date.now();
|
|
1378
1586
|
}
|
|
1379
1587
|
if (this.isWaitingForResponse) {
|
|
1588
|
+
const visibleIdlePrompt = this.looksLikeVisibleIdlePrompt(screenText);
|
|
1589
|
+
const quietForMs = this.lastNonEmptyOutputAt ? now - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
|
|
1590
|
+
const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
|
|
1591
|
+
const hasAssistantTurn = !!lastParsedAssistant;
|
|
1592
|
+
const assistantLength = lastParsedAssistant?.content?.length || 0;
|
|
1593
|
+
const idleQuietThresholdMs = Math.max(220, this.timeouts.outputSettle);
|
|
1594
|
+
const idleStableThresholdMs = Math.max(120, Math.min(220, this.timeouts.outputSettle));
|
|
1595
|
+
const idleReady = visibleIdlePrompt && !modal && hasAssistantTurn && quietForMs >= idleQuietThresholdMs && screenStableMs >= idleStableThresholdMs;
|
|
1596
|
+
const candidate = this.idleFinishCandidate;
|
|
1597
|
+
const candidateQuiet = !!candidate && candidate.responseEpoch === this.responseEpoch && candidate.lastOutputAt === this.lastOutputAt && candidate.lastScreenChangeAt === this.lastScreenChangeAt && assistantLength >= candidate.assistantLength && now - candidate.armedAt >= _ProviderCliAdapter.IDLE_FINISH_CONFIRM_MS;
|
|
1598
|
+
const canFinishImmediately = idleReady && candidateQuiet;
|
|
1599
|
+
this.recordTrace("idle_decision", {
|
|
1600
|
+
visibleIdlePrompt,
|
|
1601
|
+
quietForMs,
|
|
1602
|
+
screenStableMs,
|
|
1603
|
+
hasAssistantTurn,
|
|
1604
|
+
assistantLength,
|
|
1605
|
+
hasModal: !!modal,
|
|
1606
|
+
idleQuietThresholdMs,
|
|
1607
|
+
idleStableThresholdMs,
|
|
1608
|
+
idleReady,
|
|
1609
|
+
idleFinishConfirmMs: _ProviderCliAdapter.IDLE_FINISH_CONFIRM_MS,
|
|
1610
|
+
idleFinishCandidate: candidate,
|
|
1611
|
+
candidateQuiet,
|
|
1612
|
+
canFinishImmediately,
|
|
1613
|
+
submitPendingUntil: this.submitPendingUntil,
|
|
1614
|
+
responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
|
|
1615
|
+
...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
|
|
1616
|
+
});
|
|
1617
|
+
if (canFinishImmediately) {
|
|
1618
|
+
this.clearIdleFinishCandidate("finish_response");
|
|
1619
|
+
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
1620
|
+
this.finishResponse();
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
if (idleReady) {
|
|
1624
|
+
if (!candidate) {
|
|
1625
|
+
this.armIdleFinishCandidate(assistantLength);
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
} else {
|
|
1629
|
+
this.clearIdleFinishCandidate("idle_not_ready");
|
|
1630
|
+
}
|
|
1380
1631
|
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
1381
1632
|
this.idleTimeout = setTimeout(() => {
|
|
1382
1633
|
if (this.isWaitingForResponse && this.currentStatus !== "waiting_approval") {
|
|
1634
|
+
this.clearIdleFinishCandidate("idle_timeout_finish");
|
|
1383
1635
|
this.finishResponse();
|
|
1384
1636
|
}
|
|
1385
1637
|
}, this.timeouts.idleFinish);
|
|
1386
1638
|
} else if (prevStatus !== "idle") {
|
|
1639
|
+
this.clearIdleFinishCandidate("idle_without_response");
|
|
1387
1640
|
this.setStatus("idle", "script_detect");
|
|
1388
1641
|
this.onStatusChange?.();
|
|
1389
1642
|
}
|
|
@@ -1392,6 +1645,10 @@ var init_provider_cli_adapter = __esm({
|
|
|
1392
1645
|
finishResponse() {
|
|
1393
1646
|
if (this.submitPendingUntil > Date.now()) return;
|
|
1394
1647
|
if (this.responseSettleIgnoreUntil > Date.now()) return;
|
|
1648
|
+
this.clearIdleFinishCandidate("finish_response_enter");
|
|
1649
|
+
this.recordTrace("finish_response", {
|
|
1650
|
+
...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
|
|
1651
|
+
});
|
|
1395
1652
|
this.commitCurrentTranscript();
|
|
1396
1653
|
if (this.responseTimeout) {
|
|
1397
1654
|
clearTimeout(this.responseTimeout);
|
|
@@ -1428,6 +1685,20 @@ var init_provider_cli_adapter = __esm({
|
|
|
1428
1685
|
if (parsed && Array.isArray(parsed.messages)) {
|
|
1429
1686
|
this.committedMessages = this.normalizeParsedMessages(parsed.messages);
|
|
1430
1687
|
this.syncMessageViews();
|
|
1688
|
+
const lastAssistant = [...this.committedMessages].reverse().find((message) => message.role === "assistant");
|
|
1689
|
+
this.recordTrace("commit_transcript", {
|
|
1690
|
+
parsedStatus: parsed.status || null,
|
|
1691
|
+
messageCount: this.committedMessages.length,
|
|
1692
|
+
lastAssistant: lastAssistant ? this.summarizeTraceText(lastAssistant.content, 320) : "",
|
|
1693
|
+
messages: this.summarizeTraceMessages(this.committedMessages),
|
|
1694
|
+
...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
|
|
1695
|
+
});
|
|
1696
|
+
if (!lastAssistant && this.currentTurnScope) {
|
|
1697
|
+
LOG.warn(
|
|
1698
|
+
"CLI",
|
|
1699
|
+
`[${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 || "-"}`
|
|
1700
|
+
);
|
|
1701
|
+
}
|
|
1431
1702
|
}
|
|
1432
1703
|
}
|
|
1433
1704
|
// ─── Script Execution ──────────────────────────
|
|
@@ -1551,16 +1822,23 @@ ${data.message || ""}`.trim();
|
|
|
1551
1822
|
}
|
|
1552
1823
|
if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
|
|
1553
1824
|
if (this.isWaitingForResponse) return;
|
|
1825
|
+
await this.waitForInteractivePrompt();
|
|
1554
1826
|
this.committedMessages.push({ role: "user", content: text, timestamp: Date.now() });
|
|
1555
1827
|
this.syncMessageViews();
|
|
1556
1828
|
this.isWaitingForResponse = true;
|
|
1557
1829
|
this.responseBuffer = "";
|
|
1830
|
+
this.clearIdleFinishCandidate("send_message");
|
|
1558
1831
|
this.currentTurnScope = {
|
|
1559
1832
|
prompt: text,
|
|
1560
1833
|
startedAt: Date.now(),
|
|
1561
1834
|
bufferStart: this.accumulatedBuffer.length,
|
|
1562
1835
|
rawBufferStart: this.accumulatedRawBuffer.length
|
|
1563
1836
|
};
|
|
1837
|
+
this.recordTrace("send_message", {
|
|
1838
|
+
text: this.summarizeTraceText(text, 500),
|
|
1839
|
+
estimatedLines: estimatePromptDisplayLines(text),
|
|
1840
|
+
turnScope: this.currentTurnScope
|
|
1841
|
+
});
|
|
1564
1842
|
LOG.info("CLI", `[${this.cliType}] sendMessage turn scope buffer=${this.currentTurnScope.bufferStart} raw=${this.currentTurnScope.rawBufferStart} prompt=${JSON.stringify(text).slice(0, 120)}`);
|
|
1565
1843
|
this.submitRetryUsed = false;
|
|
1566
1844
|
this.submitRetryPromptSnippet = extractPromptRetrySnippet(text);
|
|
@@ -1590,6 +1868,11 @@ ${data.message || ""}`.trim();
|
|
|
1590
1868
|
const submit = () => {
|
|
1591
1869
|
if (!this.ptyProcess) return;
|
|
1592
1870
|
this.submitPendingUntil = 0;
|
|
1871
|
+
this.recordTrace("submit_write", {
|
|
1872
|
+
mode: "submit_key",
|
|
1873
|
+
sendKey: this.sendKey,
|
|
1874
|
+
screenText: this.summarizeTraceText(this.terminalScreen.getText(), 500)
|
|
1875
|
+
});
|
|
1593
1876
|
this.ptyProcess.write(this.sendKey);
|
|
1594
1877
|
const retrySubmitIfStuck = (attempt) => {
|
|
1595
1878
|
this.submitRetryTimer = null;
|
|
@@ -1601,6 +1884,12 @@ ${data.message || ""}`.trim();
|
|
|
1601
1884
|
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;
|
|
1602
1885
|
this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
1603
1886
|
LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt ${attempt})`);
|
|
1887
|
+
this.recordTrace("submit_write", {
|
|
1888
|
+
mode: "submit_retry",
|
|
1889
|
+
attempt,
|
|
1890
|
+
sendKey: this.sendKey,
|
|
1891
|
+
screenText: this.summarizeTraceText(screenText, 500)
|
|
1892
|
+
});
|
|
1604
1893
|
this.ptyProcess.write(this.sendKey);
|
|
1605
1894
|
if (attempt >= 3) {
|
|
1606
1895
|
this.submitRetryUsed = true;
|
|
@@ -1613,6 +1902,12 @@ ${data.message || ""}`.trim();
|
|
|
1613
1902
|
};
|
|
1614
1903
|
if (this.submitStrategy === "immediate") {
|
|
1615
1904
|
this.submitPendingUntil = 0;
|
|
1905
|
+
this.recordTrace("submit_write", {
|
|
1906
|
+
mode: "immediate",
|
|
1907
|
+
text: this.summarizeTraceText(text, 500),
|
|
1908
|
+
sendKey: this.sendKey,
|
|
1909
|
+
screenText: this.summarizeTraceText(this.terminalScreen.getText(), 500)
|
|
1910
|
+
});
|
|
1616
1911
|
this.ptyProcess.write(text + this.sendKey);
|
|
1617
1912
|
this.submitRetryTimer = setTimeout(() => {
|
|
1618
1913
|
this.submitRetryTimer = null;
|
|
@@ -1623,6 +1918,12 @@ ${data.message || ""}`.trim();
|
|
|
1623
1918
|
if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
|
|
1624
1919
|
LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt 1)`);
|
|
1625
1920
|
this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
1921
|
+
this.recordTrace("submit_write", {
|
|
1922
|
+
mode: "immediate_retry",
|
|
1923
|
+
attempt: 1,
|
|
1924
|
+
sendKey: this.sendKey,
|
|
1925
|
+
screenText: this.summarizeTraceText(screenText, 500)
|
|
1926
|
+
});
|
|
1626
1927
|
this.ptyProcess.write(this.sendKey);
|
|
1627
1928
|
this.submitRetryUsed = true;
|
|
1628
1929
|
}, retryDelayMs);
|
|
@@ -1633,6 +1934,12 @@ ${data.message || ""}`.trim();
|
|
|
1633
1934
|
this.submitPendingUntil = Date.now() + submitDelayMs;
|
|
1634
1935
|
}
|
|
1635
1936
|
this.ptyProcess.write(text);
|
|
1937
|
+
this.recordTrace("submit_write", {
|
|
1938
|
+
mode: "type_then_submit",
|
|
1939
|
+
text: this.summarizeTraceText(text, 500),
|
|
1940
|
+
sendKey: this.sendKey,
|
|
1941
|
+
screenText: this.summarizeTraceText(this.terminalScreen.getText(), 500)
|
|
1942
|
+
});
|
|
1636
1943
|
const submitStartedAt = Date.now();
|
|
1637
1944
|
let lastNormalizedScreen = "";
|
|
1638
1945
|
let lastScreenChangeAt = submitStartedAt;
|
|
@@ -1733,6 +2040,7 @@ ${data.message || ""}`.trim();
|
|
|
1733
2040
|
});
|
|
1734
2041
|
}
|
|
1735
2042
|
shutdown() {
|
|
2043
|
+
this.clearIdleFinishCandidate("shutdown");
|
|
1736
2044
|
if (this.settleTimer) {
|
|
1737
2045
|
clearTimeout(this.settleTimer);
|
|
1738
2046
|
this.settleTimer = null;
|
|
@@ -1773,6 +2081,7 @@ ${data.message || ""}`.trim();
|
|
|
1773
2081
|
}
|
|
1774
2082
|
}
|
|
1775
2083
|
detach() {
|
|
2084
|
+
this.clearIdleFinishCandidate("detach");
|
|
1776
2085
|
if (this.settleTimer) {
|
|
1777
2086
|
clearTimeout(this.settleTimer);
|
|
1778
2087
|
this.settleTimer = null;
|
|
@@ -1813,6 +2122,7 @@ ${data.message || ""}`.trim();
|
|
|
1813
2122
|
this.onStatusChange?.();
|
|
1814
2123
|
}
|
|
1815
2124
|
clearHistory() {
|
|
2125
|
+
this.clearIdleFinishCandidate("clear_history");
|
|
1816
2126
|
this.committedMessages = [];
|
|
1817
2127
|
this.syncMessageViews();
|
|
1818
2128
|
this.accumulatedBuffer = "";
|
|
@@ -1842,10 +2152,19 @@ ${data.message || ""}`.trim();
|
|
|
1842
2152
|
return this.ready;
|
|
1843
2153
|
}
|
|
1844
2154
|
writeRaw(data) {
|
|
2155
|
+
this.recordTrace("write_raw", {
|
|
2156
|
+
keys: JSON.stringify(data),
|
|
2157
|
+
length: data.length
|
|
2158
|
+
});
|
|
1845
2159
|
this.ptyProcess?.write(data);
|
|
1846
2160
|
}
|
|
1847
2161
|
resolveModal(buttonIndex) {
|
|
1848
2162
|
if (!this.ptyProcess || this.currentStatus !== "waiting_approval" && !this.activeModal) return;
|
|
2163
|
+
this.clearIdleFinishCandidate("resolve_modal");
|
|
2164
|
+
this.recordTrace("resolve_modal", {
|
|
2165
|
+
buttonIndex,
|
|
2166
|
+
activeModal: this.activeModal
|
|
2167
|
+
});
|
|
1849
2168
|
this.activeModal = null;
|
|
1850
2169
|
this.lastApprovalResolvedAt = Date.now();
|
|
1851
2170
|
this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
@@ -1877,6 +2196,7 @@ ${data.message || ""}`.trim();
|
|
|
1877
2196
|
return {
|
|
1878
2197
|
type: this.cliType,
|
|
1879
2198
|
name: this.cliName,
|
|
2199
|
+
providerResolution: this.providerResolutionMeta,
|
|
1880
2200
|
status: this.currentStatus,
|
|
1881
2201
|
ready: this.ready,
|
|
1882
2202
|
startupParseGate: this.startupParseGate,
|
|
@@ -1896,6 +2216,10 @@ ${data.message || ""}`.trim();
|
|
|
1896
2216
|
rawBufferPreview: this.accumulatedRawBuffer.slice(-1e3),
|
|
1897
2217
|
sanitizedRawPreview: sanitizeTerminalText(this.accumulatedRawBuffer).slice(-1e3),
|
|
1898
2218
|
responseBuffer: this.responseBuffer.slice(-1e3),
|
|
2219
|
+
lastOutputAt: this.lastOutputAt,
|
|
2220
|
+
lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
|
|
2221
|
+
lastScreenChangeAt: this.lastScreenChangeAt,
|
|
2222
|
+
lastScreenSnapshot: this.lastScreenSnapshot.slice(-500),
|
|
1899
2223
|
isWaitingForResponse: this.isWaitingForResponse,
|
|
1900
2224
|
activeModal: this.activeModal,
|
|
1901
2225
|
lastApprovalResolvedAt: this.lastApprovalResolvedAt,
|
|
@@ -1907,6 +2231,8 @@ ${data.message || ""}`.trim();
|
|
|
1907
2231
|
resizeSuppressUntil: this.resizeSuppressUntil,
|
|
1908
2232
|
hasCliScripts: this.hasCliScripts(),
|
|
1909
2233
|
scriptNames: Object.keys(this.cliScripts).filter((k) => typeof this.cliScripts[k] === "function"),
|
|
2234
|
+
traceSessionId: this.traceSessionId,
|
|
2235
|
+
traceEntryCount: this.traceEntries.length,
|
|
1910
2236
|
statusHistory: this.statusHistory.slice(-30),
|
|
1911
2237
|
timeouts: this.timeouts,
|
|
1912
2238
|
pendingOutputParseBufferLength: this.pendingOutputParseBuffer.length,
|
|
@@ -1914,6 +2240,25 @@ ${data.message || ""}`.trim();
|
|
|
1914
2240
|
ptyAlive: !!this.ptyProcess
|
|
1915
2241
|
};
|
|
1916
2242
|
}
|
|
2243
|
+
getTraceState(limit = 120) {
|
|
2244
|
+
const cappedLimit = Math.max(1, Math.min(500, Number.isFinite(limit) ? Math.floor(limit) : 120));
|
|
2245
|
+
return {
|
|
2246
|
+
sessionId: this.traceSessionId,
|
|
2247
|
+
providerResolution: this.providerResolutionMeta,
|
|
2248
|
+
entryCount: this.traceEntries.length,
|
|
2249
|
+
entries: this.traceEntries.slice(-cappedLimit),
|
|
2250
|
+
screenText: this.summarizeTraceText(this.terminalScreen.getText(), 4e3),
|
|
2251
|
+
recentOutputBuffer: this.summarizeTraceText(this.recentOutputBuffer, 1e3),
|
|
2252
|
+
responseBuffer: this.summarizeTraceText(this.responseBuffer, 1200),
|
|
2253
|
+
status: this.currentStatus,
|
|
2254
|
+
activeModal: this.activeModal,
|
|
2255
|
+
currentTurnScope: this.currentTurnScope,
|
|
2256
|
+
messages: this.summarizeTraceMessages(this.committedMessages, 5)
|
|
2257
|
+
};
|
|
2258
|
+
}
|
|
2259
|
+
getProviderResolutionMeta() {
|
|
2260
|
+
return { ...this.providerResolutionMeta };
|
|
2261
|
+
}
|
|
1917
2262
|
respondToTerminalQueries(data) {
|
|
1918
2263
|
if (!this.ptyProcess || !data) return;
|
|
1919
2264
|
const combined = this.pendingTerminalQueryTail + data;
|
|
@@ -4030,6 +4375,7 @@ var ChatHistoryWriter = class {
|
|
|
4030
4375
|
role: msg.role,
|
|
4031
4376
|
content: msg.content || "",
|
|
4032
4377
|
kind: typeof msg.kind === "string" ? msg.kind : void 0,
|
|
4378
|
+
senderName: typeof msg.senderName === "string" ? msg.senderName : void 0,
|
|
4033
4379
|
agent: agentType,
|
|
4034
4380
|
instanceId,
|
|
4035
4381
|
historySessionId: effectiveHistoryKey,
|
|
@@ -4068,6 +4414,7 @@ var ChatHistoryWriter = class {
|
|
|
4068
4414
|
kind: "system",
|
|
4069
4415
|
content,
|
|
4070
4416
|
receivedAt: options.receivedAt,
|
|
4417
|
+
senderName: options.senderName,
|
|
4071
4418
|
historyDedupKey: options.dedupKey
|
|
4072
4419
|
}],
|
|
4073
4420
|
options.sessionTitle,
|
|
@@ -5414,6 +5761,12 @@ function getCurrentManagerKey(h) {
|
|
|
5414
5761
|
function getTargetedCliAdapter(h, args, providerType) {
|
|
5415
5762
|
return h.getCliAdapter(args?.targetSessionId || providerType || h.currentSession?.providerType || h.currentManagerKey);
|
|
5416
5763
|
}
|
|
5764
|
+
function getTargetInstance(h, args) {
|
|
5765
|
+
const targetSessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : "";
|
|
5766
|
+
const sessionId = targetSessionId || h.currentSession?.sessionId || "";
|
|
5767
|
+
if (!sessionId) return null;
|
|
5768
|
+
return h.ctx.instanceManager?.getInstance(sessionId);
|
|
5769
|
+
}
|
|
5417
5770
|
function getTargetTransport(h, provider) {
|
|
5418
5771
|
if (h.currentSession?.transport) return h.currentSession.transport;
|
|
5419
5772
|
switch (provider?.category) {
|
|
@@ -6156,6 +6509,7 @@ async function handleResolveAction(h, args) {
|
|
|
6156
6509
|
adapter.writeRaw?.(keys);
|
|
6157
6510
|
}
|
|
6158
6511
|
LOG.info("Command", `[resolveAction] CLI PTY \u2192 buttonIndex=${buttonIndex} "${buttons[buttonIndex] ?? "?"}"`);
|
|
6512
|
+
getTargetInstance(h, args)?.recordApprovalSelection?.(buttons[buttonIndex] ?? button);
|
|
6159
6513
|
return { success: true, buttonIndex, button: buttons[buttonIndex] ?? button };
|
|
6160
6514
|
}
|
|
6161
6515
|
if (isExtensionTransport(transport) && h.agentStream && h.getCdp() && h.currentSession?.sessionId) {
|
|
@@ -7329,6 +7683,7 @@ var CliProviderInstance = class {
|
|
|
7329
7683
|
generatingDebouncePending = null;
|
|
7330
7684
|
lastApprovalEventAt = 0;
|
|
7331
7685
|
historyWriter;
|
|
7686
|
+
runtimeMessages = [];
|
|
7332
7687
|
instanceId;
|
|
7333
7688
|
presentationMode;
|
|
7334
7689
|
providerSessionId;
|
|
@@ -7391,6 +7746,7 @@ var CliProviderInstance = class {
|
|
|
7391
7746
|
}
|
|
7392
7747
|
const runtime = this.adapter.getRuntimeMetadata();
|
|
7393
7748
|
const parsedMessages = Array.isArray(parsedStatus?.messages) ? parsedStatus.messages : [];
|
|
7749
|
+
const mergedMessages = this.mergeConversationMessages(parsedMessages);
|
|
7394
7750
|
const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
|
|
7395
7751
|
if (parsedMessages.length > 0) {
|
|
7396
7752
|
let messagesToSave = parsedMessages;
|
|
@@ -7420,7 +7776,7 @@ var CliProviderInstance = class {
|
|
|
7420
7776
|
id: `${this.type}_${this.workingDir}`,
|
|
7421
7777
|
title: parsedStatus?.title || dirName,
|
|
7422
7778
|
status: parsedStatus?.status || adapterStatus.status,
|
|
7423
|
-
messages:
|
|
7779
|
+
messages: mergedMessages,
|
|
7424
7780
|
activeModal: parsedStatus?.activeModal ?? adapterStatus.activeModal,
|
|
7425
7781
|
inputContent: ""
|
|
7426
7782
|
},
|
|
@@ -7519,6 +7875,11 @@ var CliProviderInstance = class {
|
|
|
7519
7875
|
const approvalCooldown = 5e3;
|
|
7520
7876
|
if (this.lastStatus !== "waiting_approval" && (!this.lastApprovalEventAt || now - this.lastApprovalEventAt > approvalCooldown)) {
|
|
7521
7877
|
this.lastApprovalEventAt = now;
|
|
7878
|
+
this.appendRuntimeSystemMessage(
|
|
7879
|
+
this.formatApprovalRequestMessage(modal?.message, modal?.buttons),
|
|
7880
|
+
`approval_request:${now}`,
|
|
7881
|
+
now
|
|
7882
|
+
);
|
|
7522
7883
|
this.pushEvent({
|
|
7523
7884
|
event: "agent:waiting_approval",
|
|
7524
7885
|
chatTitle,
|
|
@@ -7590,11 +7951,71 @@ var CliProviderInstance = class {
|
|
|
7590
7951
|
get cliName() {
|
|
7591
7952
|
return this.provider.name;
|
|
7592
7953
|
}
|
|
7954
|
+
recordApprovalSelection(buttonText) {
|
|
7955
|
+
const cleanButton = String(buttonText || "").trim();
|
|
7956
|
+
if (!cleanButton) return;
|
|
7957
|
+
const now = Date.now();
|
|
7958
|
+
this.appendRuntimeSystemMessage(
|
|
7959
|
+
`Approval selected: ${cleanButton}`,
|
|
7960
|
+
`approval_selection:${now}:${cleanButton}`,
|
|
7961
|
+
now
|
|
7962
|
+
);
|
|
7963
|
+
}
|
|
7593
7964
|
formatMarkerTimestamp(timestamp) {
|
|
7594
7965
|
const date = new Date(timestamp);
|
|
7595
7966
|
const pad = (value) => String(value).padStart(2, "0");
|
|
7596
7967
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
|
7597
7968
|
}
|
|
7969
|
+
appendRuntimeSystemMessage(content, dedupKey, receivedAt = Date.now()) {
|
|
7970
|
+
const normalizedContent = String(content || "").trim();
|
|
7971
|
+
if (!normalizedContent) return;
|
|
7972
|
+
if (this.runtimeMessages.some((entry) => entry.key === dedupKey)) return;
|
|
7973
|
+
this.runtimeMessages.push({
|
|
7974
|
+
key: dedupKey,
|
|
7975
|
+
message: {
|
|
7976
|
+
role: "system",
|
|
7977
|
+
senderName: "System",
|
|
7978
|
+
content: normalizedContent,
|
|
7979
|
+
receivedAt,
|
|
7980
|
+
timestamp: receivedAt
|
|
7981
|
+
}
|
|
7982
|
+
});
|
|
7983
|
+
if (this.runtimeMessages.length > 50) {
|
|
7984
|
+
this.runtimeMessages = this.runtimeMessages.slice(-50);
|
|
7985
|
+
}
|
|
7986
|
+
this.historyWriter.appendNewMessages(
|
|
7987
|
+
this.type,
|
|
7988
|
+
[{
|
|
7989
|
+
role: "system",
|
|
7990
|
+
senderName: "System",
|
|
7991
|
+
content: normalizedContent,
|
|
7992
|
+
receivedAt,
|
|
7993
|
+
historyDedupKey: dedupKey
|
|
7994
|
+
}],
|
|
7995
|
+
this.adapter.getScriptParsedStatus?.()?.title || this.workingDir.split("/").filter(Boolean).pop() || "session",
|
|
7996
|
+
this.instanceId,
|
|
7997
|
+
this.providerSessionId
|
|
7998
|
+
);
|
|
7999
|
+
}
|
|
8000
|
+
mergeConversationMessages(parsedMessages) {
|
|
8001
|
+
if (this.runtimeMessages.length === 0) return parsedMessages;
|
|
8002
|
+
return [...parsedMessages, ...this.runtimeMessages.map((entry) => entry.message)].map((message, index) => ({ message, index })).sort((a, b) => {
|
|
8003
|
+
const aTime = a.message.receivedAt || a.message.timestamp || 0;
|
|
8004
|
+
const bTime = b.message.receivedAt || b.message.timestamp || 0;
|
|
8005
|
+
if (aTime !== bTime) return aTime - bTime;
|
|
8006
|
+
return a.index - b.index;
|
|
8007
|
+
}).map((entry) => entry.message);
|
|
8008
|
+
}
|
|
8009
|
+
formatApprovalRequestMessage(modalMessage, buttons) {
|
|
8010
|
+
const lines = ["Approval requested"];
|
|
8011
|
+
const cleanMessage = String(modalMessage || "").trim();
|
|
8012
|
+
if (cleanMessage) lines.push(cleanMessage);
|
|
8013
|
+
const labels = (buttons || []).map((button) => String(button || "").trim()).filter(Boolean);
|
|
8014
|
+
if (labels.length > 0) {
|
|
8015
|
+
lines.push(labels.map((label) => `[${label}]`).join(" "));
|
|
8016
|
+
}
|
|
8017
|
+
return lines.join("\n");
|
|
8018
|
+
}
|
|
7598
8019
|
promoteProviderSessionId(sessionId) {
|
|
7599
8020
|
const nextSessionId = String(sessionId || "").trim();
|
|
7600
8021
|
if (!nextSessionId || nextSessionId === this.providerSessionId) return;
|
|
@@ -9615,6 +10036,7 @@ var ProviderLoader = class _ProviderLoader {
|
|
|
9615
10036
|
resolve(type, context) {
|
|
9616
10037
|
const base = this.providers.get(type);
|
|
9617
10038
|
if (!base) return void 0;
|
|
10039
|
+
const providerDir = this.findProviderDirInternal(type) || void 0;
|
|
9618
10040
|
const currentOs = context?.os || process.platform;
|
|
9619
10041
|
const currentVersion = context?.version ?? this.versionArchive?.getLatest(type) ?? void 0;
|
|
9620
10042
|
const resolved = JSON.parse(JSON.stringify(base));
|
|
@@ -9624,6 +10046,9 @@ var ProviderLoader = class _ProviderLoader {
|
|
|
9624
10046
|
if (base.scripts) {
|
|
9625
10047
|
resolved.scripts = { ...base.scripts };
|
|
9626
10048
|
}
|
|
10049
|
+
if (providerDir) {
|
|
10050
|
+
resolved._resolvedProviderDir = providerDir;
|
|
10051
|
+
}
|
|
9627
10052
|
if (base.os?.[currentOs]) {
|
|
9628
10053
|
const osOverride = base.os[currentOs];
|
|
9629
10054
|
if (osOverride.scripts) {
|
|
@@ -9644,6 +10069,12 @@ var ProviderLoader = class _ProviderLoader {
|
|
|
9644
10069
|
if (loaded) {
|
|
9645
10070
|
resolved.scripts = loaded;
|
|
9646
10071
|
this.log(` [compatibility] ${type} v${currentVersion} \u2192 ${entry.scriptDir}`);
|
|
10072
|
+
resolved._resolvedScriptDir = entry.scriptDir;
|
|
10073
|
+
resolved._resolvedScriptsSource = `compatibility:${entry.ideVersion}`;
|
|
10074
|
+
if (providerDir) {
|
|
10075
|
+
const fullDir = path10.join(providerDir, entry.scriptDir);
|
|
10076
|
+
resolved._resolvedScriptsPath = fs6.existsSync(path10.join(fullDir, "scripts.js")) ? path10.join(fullDir, "scripts.js") : fullDir;
|
|
10077
|
+
}
|
|
9647
10078
|
matched = true;
|
|
9648
10079
|
}
|
|
9649
10080
|
break;
|
|
@@ -9654,6 +10085,12 @@ var ProviderLoader = class _ProviderLoader {
|
|
|
9654
10085
|
if (loaded) {
|
|
9655
10086
|
resolved.scripts = loaded;
|
|
9656
10087
|
this.log(` [compatibility] ${type} v${currentVersion} \u2192 default: ${base.defaultScriptDir}`);
|
|
10088
|
+
resolved._resolvedScriptDir = base.defaultScriptDir;
|
|
10089
|
+
resolved._resolvedScriptsSource = "defaultScriptDir:version_miss";
|
|
10090
|
+
if (providerDir) {
|
|
10091
|
+
const fullDir = path10.join(providerDir, base.defaultScriptDir);
|
|
10092
|
+
resolved._resolvedScriptsPath = fs6.existsSync(path10.join(fullDir, "scripts.js")) ? path10.join(fullDir, "scripts.js") : fullDir;
|
|
10093
|
+
}
|
|
9657
10094
|
}
|
|
9658
10095
|
resolved._versionWarning = `Version ${currentVersion} not in compatibility matrix. Using default scripts.`;
|
|
9659
10096
|
}
|
|
@@ -9666,6 +10103,12 @@ var ProviderLoader = class _ProviderLoader {
|
|
|
9666
10103
|
if (loaded) {
|
|
9667
10104
|
resolved.scripts = loaded;
|
|
9668
10105
|
this.log(` [version override] ${type} ${range} \u2192 ${dirOverride}`);
|
|
10106
|
+
resolved._resolvedScriptDir = dirOverride;
|
|
10107
|
+
resolved._resolvedScriptsSource = `versions:${range}`;
|
|
10108
|
+
if (providerDir) {
|
|
10109
|
+
const fullDir = path10.join(providerDir, dirOverride);
|
|
10110
|
+
resolved._resolvedScriptsPath = fs6.existsSync(path10.join(fullDir, "scripts.js")) ? path10.join(fullDir, "scripts.js") : fullDir;
|
|
10111
|
+
}
|
|
9669
10112
|
}
|
|
9670
10113
|
} else if (override.scripts) {
|
|
9671
10114
|
resolved.scripts = { ...resolved.scripts, ...override.scripts };
|
|
@@ -9677,6 +10120,12 @@ var ProviderLoader = class _ProviderLoader {
|
|
|
9677
10120
|
if (loaded) {
|
|
9678
10121
|
resolved.scripts = loaded;
|
|
9679
10122
|
this.log(` [compatibility] ${type} no version detected \u2192 default: ${base.defaultScriptDir}`);
|
|
10123
|
+
resolved._resolvedScriptDir = base.defaultScriptDir;
|
|
10124
|
+
resolved._resolvedScriptsSource = "defaultScriptDir:no_version";
|
|
10125
|
+
if (providerDir) {
|
|
10126
|
+
const fullDir = path10.join(providerDir, base.defaultScriptDir);
|
|
10127
|
+
resolved._resolvedScriptsPath = fs6.existsSync(path10.join(fullDir, "scripts.js")) ? path10.join(fullDir, "scripts.js") : fullDir;
|
|
10128
|
+
}
|
|
9680
10129
|
}
|
|
9681
10130
|
}
|
|
9682
10131
|
if (base.overrides) {
|
|
@@ -9745,6 +10194,9 @@ var ProviderLoader = class _ProviderLoader {
|
|
|
9745
10194
|
awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }
|
|
9746
10195
|
});
|
|
9747
10196
|
const handleChange = (filePath) => {
|
|
10197
|
+
if (/[\/\\]fixtures[\/\\]/.test(filePath)) {
|
|
10198
|
+
return;
|
|
10199
|
+
}
|
|
9748
10200
|
if (filePath.endsWith(".js") || filePath.endsWith(".json")) {
|
|
9749
10201
|
this.log(`File changed: ${path10.basename(filePath)}, reloading...`);
|
|
9750
10202
|
this.reload();
|
|
@@ -10437,7 +10889,7 @@ function detectCurrentWorkspace(ideId) {
|
|
|
10437
10889
|
}
|
|
10438
10890
|
} else if (plat === "win32") {
|
|
10439
10891
|
try {
|
|
10440
|
-
const
|
|
10892
|
+
const fs15 = __require("fs");
|
|
10441
10893
|
const appNameMap = getMacAppIdentifiers();
|
|
10442
10894
|
const appName = appNameMap[ideId];
|
|
10443
10895
|
if (appName) {
|
|
@@ -10446,8 +10898,8 @@ function detectCurrentWorkspace(ideId) {
|
|
|
10446
10898
|
appName,
|
|
10447
10899
|
"storage.json"
|
|
10448
10900
|
);
|
|
10449
|
-
if (
|
|
10450
|
-
const data = JSON.parse(
|
|
10901
|
+
if (fs15.existsSync(storagePath)) {
|
|
10902
|
+
const data = JSON.parse(fs15.readFileSync(storagePath, "utf-8"));
|
|
10451
10903
|
const workspaces = data?.openedPathsList?.workspaces3 || data?.openedPathsList?.entries || [];
|
|
10452
10904
|
if (workspaces.length > 0) {
|
|
10453
10905
|
const recent = workspaces[0];
|
|
@@ -12639,8 +13091,8 @@ async function detectAllVersions(loader, archive) {
|
|
|
12639
13091
|
|
|
12640
13092
|
// src/daemon/dev-server.ts
|
|
12641
13093
|
import * as http2 from "http";
|
|
12642
|
-
import * as
|
|
12643
|
-
import * as
|
|
13094
|
+
import * as fs14 from "fs";
|
|
13095
|
+
import * as path18 from "path";
|
|
12644
13096
|
|
|
12645
13097
|
// src/daemon/scaffold-template.ts
|
|
12646
13098
|
function generateFiles(type, name, category, opts = {}) {
|
|
@@ -13983,6 +14435,162 @@ async function handleDomContext(ctx, type, req, res) {
|
|
|
13983
14435
|
}
|
|
13984
14436
|
|
|
13985
14437
|
// src/daemon/dev-cli-debug.ts
|
|
14438
|
+
import * as fs12 from "fs";
|
|
14439
|
+
import * as path16 from "path";
|
|
14440
|
+
function slugifyFixtureName(value) {
|
|
14441
|
+
const normalized = String(value || "").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
14442
|
+
return normalized || `fixture-${Date.now()}`;
|
|
14443
|
+
}
|
|
14444
|
+
function getCliFixtureDir(ctx, type) {
|
|
14445
|
+
const providerDir = ctx.providerLoader.findProviderDir(type);
|
|
14446
|
+
if (!providerDir) {
|
|
14447
|
+
throw new Error(`Provider directory not found for '${type}'`);
|
|
14448
|
+
}
|
|
14449
|
+
return path16.join(providerDir, "fixtures");
|
|
14450
|
+
}
|
|
14451
|
+
function readCliFixture(ctx, type, name) {
|
|
14452
|
+
const fixtureDir = getCliFixtureDir(ctx, type);
|
|
14453
|
+
const filePath = path16.join(fixtureDir, `${name}.json`);
|
|
14454
|
+
if (!fs12.existsSync(filePath)) {
|
|
14455
|
+
throw new Error(`Fixture not found: ${filePath}`);
|
|
14456
|
+
}
|
|
14457
|
+
return JSON.parse(fs12.readFileSync(filePath, "utf-8"));
|
|
14458
|
+
}
|
|
14459
|
+
function getExerciseTranscriptText(result) {
|
|
14460
|
+
const parts = [];
|
|
14461
|
+
const debugMessages = Array.isArray(result?.debug?.messages) ? result.debug.messages : [];
|
|
14462
|
+
const traceMessages = Array.isArray(result?.trace?.messages) ? result.trace.messages : [];
|
|
14463
|
+
for (const message of [...debugMessages, ...traceMessages]) {
|
|
14464
|
+
if (!message || typeof message.content !== "string") continue;
|
|
14465
|
+
parts.push(message.content);
|
|
14466
|
+
}
|
|
14467
|
+
if (typeof result?.debug?.partialResponse === "string") parts.push(result.debug.partialResponse);
|
|
14468
|
+
if (typeof result?.trace?.responseBuffer === "string") parts.push(result.trace.responseBuffer);
|
|
14469
|
+
return parts.join("\n");
|
|
14470
|
+
}
|
|
14471
|
+
function getExerciseLastAssistant(result) {
|
|
14472
|
+
const debugMessages = Array.isArray(result?.debug?.messages) ? result.debug.messages : [];
|
|
14473
|
+
const traceMessages = Array.isArray(result?.trace?.messages) ? result.trace.messages : [];
|
|
14474
|
+
for (const messages of [debugMessages, traceMessages]) {
|
|
14475
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
14476
|
+
const message = messages[i];
|
|
14477
|
+
if (message?.role === "assistant" && typeof message.content === "string" && message.content.trim()) {
|
|
14478
|
+
return message.content;
|
|
14479
|
+
}
|
|
14480
|
+
}
|
|
14481
|
+
}
|
|
14482
|
+
return "";
|
|
14483
|
+
}
|
|
14484
|
+
function getExerciseMessageCount(result) {
|
|
14485
|
+
const debugMessages = Array.isArray(result?.debug?.messages) ? result.debug.messages : [];
|
|
14486
|
+
const traceMessages = Array.isArray(result?.trace?.messages) ? result.trace.messages : [];
|
|
14487
|
+
return Math.max(debugMessages.length, traceMessages.length);
|
|
14488
|
+
}
|
|
14489
|
+
function compileFixtureRegex(source) {
|
|
14490
|
+
const value = String(source || "").trim();
|
|
14491
|
+
if (!value) return null;
|
|
14492
|
+
const delimited = value.match(/^\/([\s\S]+)\/([dgimsuvy]*)$/);
|
|
14493
|
+
try {
|
|
14494
|
+
if (delimited) {
|
|
14495
|
+
return new RegExp(delimited[1], delimited[2]);
|
|
14496
|
+
}
|
|
14497
|
+
return new RegExp(value, "m");
|
|
14498
|
+
} catch {
|
|
14499
|
+
return null;
|
|
14500
|
+
}
|
|
14501
|
+
}
|
|
14502
|
+
function statusesContainSequence(actual, expected) {
|
|
14503
|
+
if (!expected.length) return true;
|
|
14504
|
+
let index = 0;
|
|
14505
|
+
for (const status of actual) {
|
|
14506
|
+
if (status === expected[index]) index += 1;
|
|
14507
|
+
if (index >= expected.length) return true;
|
|
14508
|
+
}
|
|
14509
|
+
return false;
|
|
14510
|
+
}
|
|
14511
|
+
function validateCliFixtureResult(result, assertions) {
|
|
14512
|
+
const failures = [];
|
|
14513
|
+
const transcriptText = getExerciseTranscriptText(result);
|
|
14514
|
+
const lastAssistant = getExerciseLastAssistant(result);
|
|
14515
|
+
const mustContainAny = assertions.mustContainAny || [];
|
|
14516
|
+
const mustNotContainAny = assertions.mustNotContainAny || [];
|
|
14517
|
+
const mustMatchAny = assertions.mustMatchAny || [];
|
|
14518
|
+
const mustNotMatchAny = assertions.mustNotMatchAny || [];
|
|
14519
|
+
const lastAssistantMustContainAny = assertions.lastAssistantMustContainAny || [];
|
|
14520
|
+
const lastAssistantMustNotContainAny = assertions.lastAssistantMustNotContainAny || [];
|
|
14521
|
+
const lastAssistantMustMatchAny = assertions.lastAssistantMustMatchAny || [];
|
|
14522
|
+
const lastAssistantMustNotMatchAny = assertions.lastAssistantMustNotMatchAny || [];
|
|
14523
|
+
const statusesSeen = Array.isArray(result?.statusesSeen) ? result.statusesSeen.map((value) => String(value)) : [];
|
|
14524
|
+
if (assertions.requireNotTimedOut !== false && result?.timedOut) {
|
|
14525
|
+
failures.push("Exercise timed out");
|
|
14526
|
+
}
|
|
14527
|
+
const missingRequired = mustContainAny.filter((value) => !transcriptText.includes(value));
|
|
14528
|
+
if (missingRequired.length > 0) {
|
|
14529
|
+
failures.push(`Missing required substrings: ${missingRequired.join(", ")}`);
|
|
14530
|
+
}
|
|
14531
|
+
const presentBanned = mustNotContainAny.filter((value) => transcriptText.includes(value));
|
|
14532
|
+
if (presentBanned.length > 0) {
|
|
14533
|
+
failures.push(`Found banned substrings: ${presentBanned.join(", ")}`);
|
|
14534
|
+
}
|
|
14535
|
+
const missingRegex = mustMatchAny.filter((value) => {
|
|
14536
|
+
const regex = compileFixtureRegex(value);
|
|
14537
|
+
return !regex || !regex.test(transcriptText);
|
|
14538
|
+
});
|
|
14539
|
+
if (missingRegex.length > 0) {
|
|
14540
|
+
failures.push(`Missing required regex matches: ${missingRegex.join(", ")}`);
|
|
14541
|
+
}
|
|
14542
|
+
const presentBannedRegex = mustNotMatchAny.filter((value) => {
|
|
14543
|
+
const regex = compileFixtureRegex(value);
|
|
14544
|
+
return !!regex && regex.test(transcriptText);
|
|
14545
|
+
});
|
|
14546
|
+
if (presentBannedRegex.length > 0) {
|
|
14547
|
+
failures.push(`Found banned regex matches: ${presentBannedRegex.join(", ")}`);
|
|
14548
|
+
}
|
|
14549
|
+
const missingLastAssistant = lastAssistantMustContainAny.filter((value) => !lastAssistant.includes(value));
|
|
14550
|
+
if (missingLastAssistant.length > 0) {
|
|
14551
|
+
failures.push(`Missing required lastAssistant substrings: ${missingLastAssistant.join(", ")}`);
|
|
14552
|
+
}
|
|
14553
|
+
const presentBannedLastAssistant = lastAssistantMustNotContainAny.filter((value) => lastAssistant.includes(value));
|
|
14554
|
+
if (presentBannedLastAssistant.length > 0) {
|
|
14555
|
+
failures.push(`Found banned lastAssistant substrings: ${presentBannedLastAssistant.join(", ")}`);
|
|
14556
|
+
}
|
|
14557
|
+
const missingLastAssistantRegex = lastAssistantMustMatchAny.filter((value) => {
|
|
14558
|
+
const regex = compileFixtureRegex(value);
|
|
14559
|
+
return !regex || !regex.test(lastAssistant);
|
|
14560
|
+
});
|
|
14561
|
+
if (missingLastAssistantRegex.length > 0) {
|
|
14562
|
+
failures.push(`Missing required lastAssistant regex matches: ${missingLastAssistantRegex.join(", ")}`);
|
|
14563
|
+
}
|
|
14564
|
+
const presentBannedLastAssistantRegex = lastAssistantMustNotMatchAny.filter((value) => {
|
|
14565
|
+
const regex = compileFixtureRegex(value);
|
|
14566
|
+
return !!regex && regex.test(lastAssistant);
|
|
14567
|
+
});
|
|
14568
|
+
if (presentBannedLastAssistantRegex.length > 0) {
|
|
14569
|
+
failures.push(`Found banned lastAssistant regex matches: ${presentBannedLastAssistantRegex.join(", ")}`);
|
|
14570
|
+
}
|
|
14571
|
+
if (assertions.statusesSeen?.length && !statusesContainSequence(statusesSeen, assertions.statusesSeen)) {
|
|
14572
|
+
failures.push(`Expected statuses sequence not observed: ${assertions.statusesSeen.join(" -> ")}`);
|
|
14573
|
+
}
|
|
14574
|
+
if (result && typeof result === "object") {
|
|
14575
|
+
result.lastAssistant = lastAssistant;
|
|
14576
|
+
}
|
|
14577
|
+
return failures;
|
|
14578
|
+
}
|
|
14579
|
+
function getCliProviderResolutionMeta(ctx, type, adapter) {
|
|
14580
|
+
const adapterMeta = typeof adapter?.getProviderResolutionMeta === "function" ? adapter.getProviderResolutionMeta() : adapter?.getDebugState?.()?.providerResolution || null;
|
|
14581
|
+
const resolvedProvider = ctx.providerLoader.resolve(type);
|
|
14582
|
+
if (!adapterMeta && !resolvedProvider) return null;
|
|
14583
|
+
return {
|
|
14584
|
+
type,
|
|
14585
|
+
providerDir: adapterMeta?.providerDir || resolvedProvider?._resolvedProviderDir || ctx.providerLoader.findProviderDir(type),
|
|
14586
|
+
scriptDir: adapterMeta?.scriptDir || resolvedProvider?._resolvedScriptDir || null,
|
|
14587
|
+
scriptsPath: adapterMeta?.scriptsPath || resolvedProvider?._resolvedScriptsPath || null,
|
|
14588
|
+
scriptsSource: adapterMeta?.scriptsSource || resolvedProvider?._resolvedScriptsSource || null,
|
|
14589
|
+
resolvedVersion: adapterMeta?.resolvedVersion || resolvedProvider?._resolvedVersion || null,
|
|
14590
|
+
resolvedOs: adapterMeta?.resolvedOs || resolvedProvider?._resolvedOs || null,
|
|
14591
|
+
versionWarning: adapterMeta?.versionWarning || resolvedProvider?._versionWarning || null
|
|
14592
|
+
};
|
|
14593
|
+
}
|
|
13986
14594
|
function findCliTarget(ctx, type, instanceId) {
|
|
13987
14595
|
if (!ctx.instanceManager) return null;
|
|
13988
14596
|
const cliStates = ctx.instanceManager.collectAllStates().filter((s) => s.category === "cli" || s.category === "acp");
|
|
@@ -13991,6 +14599,331 @@ function findCliTarget(ctx, type, instanceId) {
|
|
|
13991
14599
|
const matches = cliStates.filter((s) => s.type === type);
|
|
13992
14600
|
return matches[matches.length - 1] || null;
|
|
13993
14601
|
}
|
|
14602
|
+
function getCliTargetBundle(ctx, type, instanceId) {
|
|
14603
|
+
if (!ctx.instanceManager) return null;
|
|
14604
|
+
const target = findCliTarget(ctx, type, instanceId);
|
|
14605
|
+
if (!target) return null;
|
|
14606
|
+
const instance = ctx.instanceManager.getInstance(target.instanceId);
|
|
14607
|
+
if (!instance) return null;
|
|
14608
|
+
const adapter = instance.getAdapter?.() || instance.adapter;
|
|
14609
|
+
if (!adapter) return null;
|
|
14610
|
+
return { target, instance, adapter };
|
|
14611
|
+
}
|
|
14612
|
+
function sleep(ms) {
|
|
14613
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
14614
|
+
}
|
|
14615
|
+
async function waitForCliReady(ctx, type, instanceId, timeoutMs) {
|
|
14616
|
+
const startedAt = Date.now();
|
|
14617
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
14618
|
+
const bundle = getCliTargetBundle(ctx, type, instanceId);
|
|
14619
|
+
if (bundle) {
|
|
14620
|
+
const debug = typeof bundle.adapter.getDebugState === "function" ? bundle.adapter.getDebugState() : null;
|
|
14621
|
+
const startupParseGate = !!debug?.startupParseGate;
|
|
14622
|
+
const adapterReady = !!debug?.ready;
|
|
14623
|
+
const visibleStatusReady = bundle.target.status === "generating" || bundle.target.status === "waiting_approval";
|
|
14624
|
+
const idleReady = bundle.target.status === "idle" && !startupParseGate;
|
|
14625
|
+
if (adapterReady || visibleStatusReady || idleReady) {
|
|
14626
|
+
return bundle;
|
|
14627
|
+
}
|
|
14628
|
+
}
|
|
14629
|
+
await sleep(100);
|
|
14630
|
+
}
|
|
14631
|
+
return getCliTargetBundle(ctx, type, instanceId);
|
|
14632
|
+
}
|
|
14633
|
+
async function runCliExerciseInternal(ctx, body) {
|
|
14634
|
+
if (!ctx.cliManager) {
|
|
14635
|
+
throw new Error("CliManager not available");
|
|
14636
|
+
}
|
|
14637
|
+
if (!ctx.instanceManager) {
|
|
14638
|
+
throw new Error("InstanceManager not available");
|
|
14639
|
+
}
|
|
14640
|
+
const {
|
|
14641
|
+
type,
|
|
14642
|
+
text,
|
|
14643
|
+
instanceId: requestedInstanceId,
|
|
14644
|
+
workingDir,
|
|
14645
|
+
args,
|
|
14646
|
+
autoLaunch = true,
|
|
14647
|
+
freshSession = true,
|
|
14648
|
+
autoResolveApprovals = true,
|
|
14649
|
+
approvalButtonIndex = 0,
|
|
14650
|
+
timeoutMs = 45e3,
|
|
14651
|
+
readyTimeoutMs = 15e3,
|
|
14652
|
+
idleSettledMs = 1200,
|
|
14653
|
+
traceLimit = 160,
|
|
14654
|
+
stopWhenDone = false
|
|
14655
|
+
} = body || {};
|
|
14656
|
+
if (!type) {
|
|
14657
|
+
throw new Error("type required (e.g. claude-cli, codex-cli)");
|
|
14658
|
+
}
|
|
14659
|
+
if (!text || typeof text !== "string") {
|
|
14660
|
+
throw new Error("text required (prompt to send to the CLI)");
|
|
14661
|
+
}
|
|
14662
|
+
let resolvedInstanceId = requestedInstanceId;
|
|
14663
|
+
if (freshSession) {
|
|
14664
|
+
const staleTargets = ctx.instanceManager.collectAllStates().filter((state) => (state.category === "cli" || state.category === "acp") && state.type === type).map((state) => state.instanceId);
|
|
14665
|
+
for (const staleId of staleTargets) {
|
|
14666
|
+
ctx.instanceManager.removeInstance(staleId);
|
|
14667
|
+
}
|
|
14668
|
+
resolvedInstanceId = void 0;
|
|
14669
|
+
}
|
|
14670
|
+
let bundle = getCliTargetBundle(ctx, type, resolvedInstanceId);
|
|
14671
|
+
if (!bundle && autoLaunch) {
|
|
14672
|
+
const launchArgs = [type, workingDir || process.cwd(), Array.isArray(args) ? args : []];
|
|
14673
|
+
let launched = null;
|
|
14674
|
+
let lastLaunchError = null;
|
|
14675
|
+
for (let attempt = 1; attempt <= 2; attempt += 1) {
|
|
14676
|
+
try {
|
|
14677
|
+
launched = await ctx.cliManager.startSession(...launchArgs);
|
|
14678
|
+
lastLaunchError = null;
|
|
14679
|
+
break;
|
|
14680
|
+
} catch (error) {
|
|
14681
|
+
lastLaunchError = error instanceof Error ? error : new Error(String(error?.message || error));
|
|
14682
|
+
const message = String(lastLaunchError.message || "");
|
|
14683
|
+
const retryable = /ECONNREFUSED|session-host|Session host/i.test(message);
|
|
14684
|
+
if (!retryable || attempt === 2) break;
|
|
14685
|
+
await sleep(1e3);
|
|
14686
|
+
}
|
|
14687
|
+
}
|
|
14688
|
+
if (!launched) {
|
|
14689
|
+
throw lastLaunchError || new Error(`Failed to start ${type}`);
|
|
14690
|
+
}
|
|
14691
|
+
resolvedInstanceId = launched.runtimeSessionId;
|
|
14692
|
+
bundle = await waitForCliReady(ctx, type, resolvedInstanceId, Math.max(1e3, readyTimeoutMs));
|
|
14693
|
+
}
|
|
14694
|
+
if (!bundle) {
|
|
14695
|
+
throw new Error(`No running instance found for: ${resolvedInstanceId || type}`);
|
|
14696
|
+
}
|
|
14697
|
+
const initialDebug = typeof bundle.adapter.getDebugState === "function" ? bundle.adapter.getDebugState() : null;
|
|
14698
|
+
const initialTrace = typeof bundle.adapter.getTraceState === "function" ? bundle.adapter.getTraceState(traceLimit) : null;
|
|
14699
|
+
const providerResolution = getCliProviderResolutionMeta(ctx, bundle.target.type, bundle.adapter);
|
|
14700
|
+
const preTraceCount = Number(initialTrace?.entryCount || 0);
|
|
14701
|
+
const startAt = Date.now();
|
|
14702
|
+
const statusesSeen = [];
|
|
14703
|
+
const approvalsResolved = [];
|
|
14704
|
+
let lastStatus = "";
|
|
14705
|
+
let lastModalKey = "";
|
|
14706
|
+
let idleSince = 0;
|
|
14707
|
+
let sawBusy = false;
|
|
14708
|
+
ctx.instanceManager.sendEvent(bundle.target.instanceId, "send_message", { text });
|
|
14709
|
+
while (Date.now() - startAt < Math.max(1e3, timeoutMs)) {
|
|
14710
|
+
await sleep(150);
|
|
14711
|
+
bundle = getCliTargetBundle(ctx, type, bundle.target.instanceId);
|
|
14712
|
+
if (!bundle) {
|
|
14713
|
+
throw new Error("CLI instance disappeared during exercise");
|
|
14714
|
+
}
|
|
14715
|
+
const debug = typeof bundle.adapter.getDebugState === "function" ? bundle.adapter.getDebugState() : null;
|
|
14716
|
+
const trace = typeof bundle.adapter.getTraceState === "function" ? bundle.adapter.getTraceState(traceLimit) : null;
|
|
14717
|
+
const status = String(debug?.status || bundle.target.status || "unknown");
|
|
14718
|
+
const traceEntries = Array.isArray(trace?.entries) ? trace.entries : [];
|
|
14719
|
+
const sawSendMessage = traceEntries.some((entry) => entry?.type === "send_message");
|
|
14720
|
+
const sawSubmitWrite = traceEntries.some((entry) => entry?.type === "submit_write");
|
|
14721
|
+
const hasTurnStarted = sawSendMessage || sawSubmitWrite || !!debug?.currentTurnScope;
|
|
14722
|
+
if (status !== lastStatus) {
|
|
14723
|
+
statusesSeen.push(status);
|
|
14724
|
+
lastStatus = status;
|
|
14725
|
+
}
|
|
14726
|
+
if (status === "generating" || status === "waiting_approval") {
|
|
14727
|
+
sawBusy = true;
|
|
14728
|
+
idleSince = 0;
|
|
14729
|
+
}
|
|
14730
|
+
const modal = debug?.activeModal || trace?.activeModal || null;
|
|
14731
|
+
if (autoResolveApprovals && status === "waiting_approval" && modal && Array.isArray(modal.buttons) && modal.buttons.length > 0) {
|
|
14732
|
+
const clampedIndex = Math.max(0, Math.min(Number(approvalButtonIndex) || 0, modal.buttons.length - 1));
|
|
14733
|
+
const modalKey = JSON.stringify({
|
|
14734
|
+
message: modal.message || "",
|
|
14735
|
+
buttons: modal.buttons,
|
|
14736
|
+
index: clampedIndex
|
|
14737
|
+
});
|
|
14738
|
+
if (modalKey !== lastModalKey && typeof bundle.adapter.resolveModal === "function") {
|
|
14739
|
+
lastModalKey = modalKey;
|
|
14740
|
+
approvalsResolved.push({
|
|
14741
|
+
at: Date.now(),
|
|
14742
|
+
buttonIndex: clampedIndex,
|
|
14743
|
+
label: modal.buttons[clampedIndex] || null
|
|
14744
|
+
});
|
|
14745
|
+
bundle.adapter.resolveModal(clampedIndex);
|
|
14746
|
+
continue;
|
|
14747
|
+
}
|
|
14748
|
+
}
|
|
14749
|
+
const traceCount = Number(trace?.entryCount || 0);
|
|
14750
|
+
const hasProgress = hasTurnStarted && (traceCount > preTraceCount || statusesSeen.length > 1 || approvalsResolved.length > 0);
|
|
14751
|
+
if (status === "idle" && hasProgress && sawBusy) {
|
|
14752
|
+
if (!idleSince) idleSince = Date.now();
|
|
14753
|
+
if (Date.now() - idleSince >= Math.max(200, idleSettledMs)) {
|
|
14754
|
+
const payload2 = {
|
|
14755
|
+
exercised: true,
|
|
14756
|
+
instanceId: bundle.target.instanceId,
|
|
14757
|
+
providerState: {
|
|
14758
|
+
type: bundle.target.type,
|
|
14759
|
+
name: bundle.target.name,
|
|
14760
|
+
status: bundle.target.status,
|
|
14761
|
+
mode: "mode" in bundle.target ? bundle.target.mode : void 0
|
|
14762
|
+
},
|
|
14763
|
+
providerResolution,
|
|
14764
|
+
initialDebug,
|
|
14765
|
+
initialTrace,
|
|
14766
|
+
debug,
|
|
14767
|
+
trace,
|
|
14768
|
+
statusesSeen,
|
|
14769
|
+
approvalsResolved,
|
|
14770
|
+
elapsedMs: Date.now() - startAt,
|
|
14771
|
+
timedOut: false
|
|
14772
|
+
};
|
|
14773
|
+
payload2.lastAssistant = getExerciseLastAssistant(payload2);
|
|
14774
|
+
payload2.messageCount = getExerciseMessageCount(payload2);
|
|
14775
|
+
if (stopWhenDone) {
|
|
14776
|
+
ctx.instanceManager.removeInstance(bundle.target.instanceId);
|
|
14777
|
+
}
|
|
14778
|
+
return payload2;
|
|
14779
|
+
}
|
|
14780
|
+
} else if (status === "idle" && hasProgress) {
|
|
14781
|
+
if (!idleSince) idleSince = Date.now();
|
|
14782
|
+
if (Date.now() - idleSince >= Math.max(500, idleSettledMs) && Date.now() - startAt >= 750) {
|
|
14783
|
+
const payload2 = {
|
|
14784
|
+
exercised: true,
|
|
14785
|
+
instanceId: bundle.target.instanceId,
|
|
14786
|
+
providerState: {
|
|
14787
|
+
type: bundle.target.type,
|
|
14788
|
+
name: bundle.target.name,
|
|
14789
|
+
status: bundle.target.status,
|
|
14790
|
+
mode: "mode" in bundle.target ? bundle.target.mode : void 0
|
|
14791
|
+
},
|
|
14792
|
+
providerResolution,
|
|
14793
|
+
initialDebug,
|
|
14794
|
+
initialTrace,
|
|
14795
|
+
debug,
|
|
14796
|
+
trace,
|
|
14797
|
+
statusesSeen,
|
|
14798
|
+
approvalsResolved,
|
|
14799
|
+
elapsedMs: Date.now() - startAt,
|
|
14800
|
+
timedOut: false
|
|
14801
|
+
};
|
|
14802
|
+
payload2.lastAssistant = getExerciseLastAssistant(payload2);
|
|
14803
|
+
payload2.messageCount = getExerciseMessageCount(payload2);
|
|
14804
|
+
if (stopWhenDone) {
|
|
14805
|
+
ctx.instanceManager.removeInstance(bundle.target.instanceId);
|
|
14806
|
+
}
|
|
14807
|
+
return payload2;
|
|
14808
|
+
}
|
|
14809
|
+
} else {
|
|
14810
|
+
idleSince = 0;
|
|
14811
|
+
}
|
|
14812
|
+
}
|
|
14813
|
+
const finalBundle = getCliTargetBundle(ctx, type, bundle.target.instanceId) || bundle;
|
|
14814
|
+
const finalDebug = typeof finalBundle.adapter.getDebugState === "function" ? finalBundle.adapter.getDebugState() : null;
|
|
14815
|
+
const finalTrace = typeof finalBundle.adapter.getTraceState === "function" ? finalBundle.adapter.getTraceState(traceLimit) : null;
|
|
14816
|
+
if (stopWhenDone) {
|
|
14817
|
+
ctx.instanceManager.removeInstance(finalBundle.target.instanceId);
|
|
14818
|
+
}
|
|
14819
|
+
const payload = {
|
|
14820
|
+
exercised: true,
|
|
14821
|
+
instanceId: finalBundle.target.instanceId,
|
|
14822
|
+
providerState: {
|
|
14823
|
+
type: finalBundle.target.type,
|
|
14824
|
+
name: finalBundle.target.name,
|
|
14825
|
+
status: finalBundle.target.status,
|
|
14826
|
+
mode: "mode" in finalBundle.target ? finalBundle.target.mode : void 0
|
|
14827
|
+
},
|
|
14828
|
+
providerResolution: getCliProviderResolutionMeta(ctx, finalBundle.target.type, finalBundle.adapter),
|
|
14829
|
+
initialDebug,
|
|
14830
|
+
initialTrace,
|
|
14831
|
+
debug: finalDebug,
|
|
14832
|
+
trace: finalTrace,
|
|
14833
|
+
statusesSeen,
|
|
14834
|
+
approvalsResolved,
|
|
14835
|
+
elapsedMs: Date.now() - startAt,
|
|
14836
|
+
timedOut: true
|
|
14837
|
+
};
|
|
14838
|
+
payload.lastAssistant = getExerciseLastAssistant(payload);
|
|
14839
|
+
payload.messageCount = getExerciseMessageCount(payload);
|
|
14840
|
+
return payload;
|
|
14841
|
+
}
|
|
14842
|
+
async function runCliAutoImplVerification(ctx, type, verification) {
|
|
14843
|
+
const assertions = {
|
|
14844
|
+
mustContainAny: verification?.mustContainAny || [],
|
|
14845
|
+
mustNotContainAny: verification?.mustNotContainAny || [],
|
|
14846
|
+
mustMatchAny: verification?.mustMatchAny || [],
|
|
14847
|
+
mustNotMatchAny: verification?.mustNotMatchAny || [],
|
|
14848
|
+
lastAssistantMustContainAny: verification?.lastAssistantMustContainAny || [],
|
|
14849
|
+
lastAssistantMustNotContainAny: verification?.lastAssistantMustNotContainAny || [],
|
|
14850
|
+
lastAssistantMustMatchAny: verification?.lastAssistantMustMatchAny || [],
|
|
14851
|
+
lastAssistantMustNotMatchAny: verification?.lastAssistantMustNotMatchAny || [],
|
|
14852
|
+
requireNotTimedOut: true
|
|
14853
|
+
};
|
|
14854
|
+
const rawFixtureNames = Array.isArray(verification?.fixtureNames) ? verification.fixtureNames.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
14855
|
+
if (rawFixtureNames.length > 0) {
|
|
14856
|
+
const results = [];
|
|
14857
|
+
for (const rawFixtureName2 of rawFixtureNames) {
|
|
14858
|
+
const name = slugifyFixtureName(rawFixtureName2);
|
|
14859
|
+
const fixture = readCliFixture(ctx, type, name);
|
|
14860
|
+
const mergedAssertions = {
|
|
14861
|
+
...fixture.assertions,
|
|
14862
|
+
...assertions
|
|
14863
|
+
};
|
|
14864
|
+
const result2 = await runCliExerciseInternal(ctx, {
|
|
14865
|
+
...fixture.request,
|
|
14866
|
+
type
|
|
14867
|
+
});
|
|
14868
|
+
const failures2 = validateCliFixtureResult(result2, mergedAssertions);
|
|
14869
|
+
results.push({
|
|
14870
|
+
fixtureName: name,
|
|
14871
|
+
pass: failures2.length === 0,
|
|
14872
|
+
failures: failures2,
|
|
14873
|
+
result: result2,
|
|
14874
|
+
assertions: mergedAssertions,
|
|
14875
|
+
fixture
|
|
14876
|
+
});
|
|
14877
|
+
}
|
|
14878
|
+
const firstFailure = results.find((item) => !item.pass) || results[results.length - 1];
|
|
14879
|
+
return {
|
|
14880
|
+
mode: "fixture_replay_suite",
|
|
14881
|
+
pass: results.every((item) => item.pass),
|
|
14882
|
+
failures: results.flatMap((item) => item.failures.map((failure) => `${item.fixtureName}: ${failure}`)),
|
|
14883
|
+
result: firstFailure.result,
|
|
14884
|
+
assertions: firstFailure.assertions,
|
|
14885
|
+
fixture: firstFailure.fixture,
|
|
14886
|
+
results
|
|
14887
|
+
};
|
|
14888
|
+
}
|
|
14889
|
+
const rawFixtureName = String(verification?.fixtureName || "").trim();
|
|
14890
|
+
if (rawFixtureName) {
|
|
14891
|
+
const name = slugifyFixtureName(rawFixtureName);
|
|
14892
|
+
try {
|
|
14893
|
+
const fixture = readCliFixture(ctx, type, name);
|
|
14894
|
+
const mergedAssertions = {
|
|
14895
|
+
...fixture.assertions,
|
|
14896
|
+
...assertions
|
|
14897
|
+
};
|
|
14898
|
+
const result2 = await runCliExerciseInternal(ctx, {
|
|
14899
|
+
...fixture.request,
|
|
14900
|
+
type
|
|
14901
|
+
});
|
|
14902
|
+
const failures2 = validateCliFixtureResult(result2, mergedAssertions);
|
|
14903
|
+
return {
|
|
14904
|
+
mode: "fixture_replay",
|
|
14905
|
+
pass: failures2.length === 0,
|
|
14906
|
+
failures: failures2,
|
|
14907
|
+
result: result2,
|
|
14908
|
+
assertions: mergedAssertions,
|
|
14909
|
+
fixture
|
|
14910
|
+
};
|
|
14911
|
+
} catch {
|
|
14912
|
+
}
|
|
14913
|
+
}
|
|
14914
|
+
const result = await runCliExerciseInternal(ctx, {
|
|
14915
|
+
...verification?.request || {},
|
|
14916
|
+
type
|
|
14917
|
+
});
|
|
14918
|
+
const failures = validateCliFixtureResult(result, assertions);
|
|
14919
|
+
return {
|
|
14920
|
+
mode: "exercise",
|
|
14921
|
+
pass: failures.length === 0,
|
|
14922
|
+
failures,
|
|
14923
|
+
result,
|
|
14924
|
+
assertions
|
|
14925
|
+
};
|
|
14926
|
+
}
|
|
13994
14927
|
async function handleCliStatus(ctx, _req, res) {
|
|
13995
14928
|
if (!ctx.instanceManager) {
|
|
13996
14929
|
ctx.json(res, 503, { error: "InstanceManager not available (daemon not fully initialized)" });
|
|
@@ -14129,12 +15062,14 @@ async function handleCliDebug(ctx, type, _req, res) {
|
|
|
14129
15062
|
status: target.status,
|
|
14130
15063
|
mode: "mode" in target ? target.mode : void 0
|
|
14131
15064
|
},
|
|
15065
|
+
providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
|
|
14132
15066
|
debug: debugState
|
|
14133
15067
|
});
|
|
14134
15068
|
} else {
|
|
14135
15069
|
ctx.json(res, 200, {
|
|
14136
15070
|
instanceId: target.instanceId,
|
|
14137
15071
|
providerState: target,
|
|
15072
|
+
providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
|
|
14138
15073
|
debug: null,
|
|
14139
15074
|
message: "No debug state available (adapter.getDebugState not found)"
|
|
14140
15075
|
});
|
|
@@ -14143,6 +15078,191 @@ async function handleCliDebug(ctx, type, _req, res) {
|
|
|
14143
15078
|
ctx.json(res, 500, { error: `Debug state failed: ${e.message}` });
|
|
14144
15079
|
}
|
|
14145
15080
|
}
|
|
15081
|
+
async function handleCliTrace(ctx, type, req, res) {
|
|
15082
|
+
if (!ctx.instanceManager) {
|
|
15083
|
+
ctx.json(res, 503, { error: "InstanceManager not available" });
|
|
15084
|
+
return;
|
|
15085
|
+
}
|
|
15086
|
+
const target = findCliTarget(ctx, type);
|
|
15087
|
+
if (!target) {
|
|
15088
|
+
const allStates = ctx.instanceManager.collectAllStates();
|
|
15089
|
+
ctx.json(res, 404, {
|
|
15090
|
+
error: `No running instance for: ${type}`,
|
|
15091
|
+
available: allStates.filter((s) => s.category === "cli" || s.category === "acp").map((s) => s.type)
|
|
15092
|
+
});
|
|
15093
|
+
return;
|
|
15094
|
+
}
|
|
15095
|
+
const instance = ctx.instanceManager.getInstance(target.instanceId);
|
|
15096
|
+
if (!instance) {
|
|
15097
|
+
ctx.json(res, 404, { error: `Instance not found: ${target.instanceId}` });
|
|
15098
|
+
return;
|
|
15099
|
+
}
|
|
15100
|
+
try {
|
|
15101
|
+
const adapter = instance.getAdapter?.() || instance.adapter;
|
|
15102
|
+
const url = new URL(req.url || "/", "http://127.0.0.1");
|
|
15103
|
+
const limit = parseInt(url.searchParams.get("limit") || "120", 10);
|
|
15104
|
+
if (adapter && typeof adapter.getTraceState === "function") {
|
|
15105
|
+
const trace = adapter.getTraceState(limit);
|
|
15106
|
+
const debug = typeof adapter.getDebugState === "function" ? adapter.getDebugState() : null;
|
|
15107
|
+
ctx.json(res, 200, {
|
|
15108
|
+
instanceId: target.instanceId,
|
|
15109
|
+
providerState: {
|
|
15110
|
+
type: target.type,
|
|
15111
|
+
name: target.name,
|
|
15112
|
+
status: target.status,
|
|
15113
|
+
mode: "mode" in target ? target.mode : void 0
|
|
15114
|
+
},
|
|
15115
|
+
providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
|
|
15116
|
+
debug,
|
|
15117
|
+
trace
|
|
15118
|
+
});
|
|
15119
|
+
} else {
|
|
15120
|
+
ctx.json(res, 200, {
|
|
15121
|
+
instanceId: target.instanceId,
|
|
15122
|
+
providerState: target,
|
|
15123
|
+
providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
|
|
15124
|
+
debug: typeof adapter?.getDebugState === "function" ? adapter.getDebugState() : null,
|
|
15125
|
+
trace: null,
|
|
15126
|
+
message: "No trace state available (adapter.getTraceState not found)"
|
|
15127
|
+
});
|
|
15128
|
+
}
|
|
15129
|
+
} catch (e) {
|
|
15130
|
+
ctx.json(res, 500, { error: `Trace state failed: ${e.message}` });
|
|
15131
|
+
}
|
|
15132
|
+
}
|
|
15133
|
+
async function handleCliExercise(ctx, req, res) {
|
|
15134
|
+
try {
|
|
15135
|
+
const body = await ctx.readBody(req);
|
|
15136
|
+
const result = await runCliExerciseInternal(ctx, body || {});
|
|
15137
|
+
ctx.json(res, 200, result);
|
|
15138
|
+
} catch (e) {
|
|
15139
|
+
ctx.json(res, 500, { error: `Exercise failed: ${e.message}` });
|
|
15140
|
+
}
|
|
15141
|
+
}
|
|
15142
|
+
async function handleCliFixtureCapture(ctx, req, res) {
|
|
15143
|
+
try {
|
|
15144
|
+
const body = await ctx.readBody(req);
|
|
15145
|
+
const type = String(body?.type || "");
|
|
15146
|
+
const request = body?.request || {};
|
|
15147
|
+
if (!type) {
|
|
15148
|
+
ctx.json(res, 400, { error: "type required" });
|
|
15149
|
+
return;
|
|
15150
|
+
}
|
|
15151
|
+
if (!request?.text) {
|
|
15152
|
+
ctx.json(res, 400, { error: "request.text required" });
|
|
15153
|
+
return;
|
|
15154
|
+
}
|
|
15155
|
+
const fixtureDir = getCliFixtureDir(ctx, type);
|
|
15156
|
+
fs12.mkdirSync(fixtureDir, { recursive: true });
|
|
15157
|
+
const name = slugifyFixtureName(String(body?.name || `${type}-${Date.now()}`));
|
|
15158
|
+
const result = await runCliExerciseInternal(ctx, { ...request, type });
|
|
15159
|
+
const fixture = {
|
|
15160
|
+
version: 1,
|
|
15161
|
+
kind: "cli-exercise-fixture",
|
|
15162
|
+
name,
|
|
15163
|
+
type,
|
|
15164
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15165
|
+
providerDir: ctx.providerLoader.findProviderDir(type),
|
|
15166
|
+
providerResolution: result?.providerResolution || null,
|
|
15167
|
+
request: { ...request, type },
|
|
15168
|
+
result,
|
|
15169
|
+
assertions: {
|
|
15170
|
+
mustContainAny: Array.isArray(body?.assertions?.mustContainAny) ? body.assertions.mustContainAny : [],
|
|
15171
|
+
mustNotContainAny: Array.isArray(body?.assertions?.mustNotContainAny) ? body.assertions.mustNotContainAny : [],
|
|
15172
|
+
mustMatchAny: Array.isArray(body?.assertions?.mustMatchAny) ? body.assertions.mustMatchAny : [],
|
|
15173
|
+
mustNotMatchAny: Array.isArray(body?.assertions?.mustNotMatchAny) ? body.assertions.mustNotMatchAny : [],
|
|
15174
|
+
lastAssistantMustContainAny: Array.isArray(body?.assertions?.lastAssistantMustContainAny) ? body.assertions.lastAssistantMustContainAny : [],
|
|
15175
|
+
lastAssistantMustNotContainAny: Array.isArray(body?.assertions?.lastAssistantMustNotContainAny) ? body.assertions.lastAssistantMustNotContainAny : [],
|
|
15176
|
+
lastAssistantMustMatchAny: Array.isArray(body?.assertions?.lastAssistantMustMatchAny) ? body.assertions.lastAssistantMustMatchAny : [],
|
|
15177
|
+
lastAssistantMustNotMatchAny: Array.isArray(body?.assertions?.lastAssistantMustNotMatchAny) ? body.assertions.lastAssistantMustNotMatchAny : [],
|
|
15178
|
+
statusesSeen: Array.isArray(body?.assertions?.statusesSeen) ? body.assertions.statusesSeen : void 0,
|
|
15179
|
+
requireNotTimedOut: body?.assertions?.requireNotTimedOut !== false
|
|
15180
|
+
},
|
|
15181
|
+
notes: typeof body?.notes === "string" ? body.notes : void 0
|
|
15182
|
+
};
|
|
15183
|
+
const filePath = path16.join(fixtureDir, `${name}.json`);
|
|
15184
|
+
fs12.writeFileSync(filePath, JSON.stringify(fixture, null, 2));
|
|
15185
|
+
ctx.json(res, 200, {
|
|
15186
|
+
saved: true,
|
|
15187
|
+
name,
|
|
15188
|
+
path: filePath,
|
|
15189
|
+
fixture,
|
|
15190
|
+
verification: {
|
|
15191
|
+
pass: validateCliFixtureResult(result, fixture.assertions).length === 0,
|
|
15192
|
+
failures: validateCliFixtureResult(result, fixture.assertions)
|
|
15193
|
+
}
|
|
15194
|
+
});
|
|
15195
|
+
} catch (e) {
|
|
15196
|
+
ctx.json(res, 500, { error: `Fixture capture failed: ${e.message}` });
|
|
15197
|
+
}
|
|
15198
|
+
}
|
|
15199
|
+
async function handleCliFixtureList(ctx, type, _req, res) {
|
|
15200
|
+
try {
|
|
15201
|
+
const fixtureDir = getCliFixtureDir(ctx, type);
|
|
15202
|
+
if (!fs12.existsSync(fixtureDir)) {
|
|
15203
|
+
ctx.json(res, 200, { fixtures: [], count: 0 });
|
|
15204
|
+
return;
|
|
15205
|
+
}
|
|
15206
|
+
const fixtures = fs12.readdirSync(fixtureDir).filter((file) => file.endsWith(".json")).sort((a, b) => b.localeCompare(a, void 0, { numeric: true, sensitivity: "base" })).map((file) => {
|
|
15207
|
+
const fullPath = path16.join(fixtureDir, file);
|
|
15208
|
+
try {
|
|
15209
|
+
const raw = JSON.parse(fs12.readFileSync(fullPath, "utf-8"));
|
|
15210
|
+
return {
|
|
15211
|
+
name: raw.name || file.replace(/\.json$/i, ""),
|
|
15212
|
+
path: fullPath,
|
|
15213
|
+
createdAt: raw.createdAt || null,
|
|
15214
|
+
notes: raw.notes || null,
|
|
15215
|
+
requestText: raw.request?.text || "",
|
|
15216
|
+
assertions: raw.assertions || {}
|
|
15217
|
+
};
|
|
15218
|
+
} catch {
|
|
15219
|
+
return {
|
|
15220
|
+
name: file.replace(/\.json$/i, ""),
|
|
15221
|
+
path: fullPath,
|
|
15222
|
+
createdAt: null,
|
|
15223
|
+
notes: "Unreadable fixture",
|
|
15224
|
+
requestText: "",
|
|
15225
|
+
assertions: {}
|
|
15226
|
+
};
|
|
15227
|
+
}
|
|
15228
|
+
});
|
|
15229
|
+
ctx.json(res, 200, { fixtures, count: fixtures.length });
|
|
15230
|
+
} catch (e) {
|
|
15231
|
+
ctx.json(res, 500, { error: `Fixture list failed: ${e.message}` });
|
|
15232
|
+
}
|
|
15233
|
+
}
|
|
15234
|
+
async function handleCliFixtureReplay(ctx, req, res) {
|
|
15235
|
+
try {
|
|
15236
|
+
const body = await ctx.readBody(req);
|
|
15237
|
+
const type = String(body?.type || "");
|
|
15238
|
+
const rawName = String(body?.name || "").trim();
|
|
15239
|
+
if (!type || !rawName) {
|
|
15240
|
+
ctx.json(res, 400, { error: "type and name required" });
|
|
15241
|
+
return;
|
|
15242
|
+
}
|
|
15243
|
+
const name = slugifyFixtureName(rawName);
|
|
15244
|
+
const fixture = readCliFixture(ctx, type, name);
|
|
15245
|
+
const result = await runCliExerciseInternal(ctx, {
|
|
15246
|
+
...fixture.request,
|
|
15247
|
+
type
|
|
15248
|
+
});
|
|
15249
|
+
const assertions = {
|
|
15250
|
+
...fixture.assertions,
|
|
15251
|
+
...body?.assertions || {}
|
|
15252
|
+
};
|
|
15253
|
+
const failures = validateCliFixtureResult(result, assertions);
|
|
15254
|
+
ctx.json(res, 200, {
|
|
15255
|
+
replayed: true,
|
|
15256
|
+
pass: failures.length === 0,
|
|
15257
|
+
failures,
|
|
15258
|
+
fixture,
|
|
15259
|
+
result,
|
|
15260
|
+
assertions
|
|
15261
|
+
});
|
|
15262
|
+
} catch (e) {
|
|
15263
|
+
ctx.json(res, 500, { error: `Fixture replay failed: ${e.message}` });
|
|
15264
|
+
}
|
|
15265
|
+
}
|
|
14146
15266
|
async function handleCliResolve(ctx, req, res) {
|
|
14147
15267
|
const body = await ctx.readBody(req);
|
|
14148
15268
|
const { type, buttonIndex, instanceId } = body;
|
|
@@ -14219,9 +15339,29 @@ async function handleCliRaw(ctx, req, res) {
|
|
|
14219
15339
|
}
|
|
14220
15340
|
|
|
14221
15341
|
// src/daemon/dev-auto-implement.ts
|
|
14222
|
-
import * as
|
|
14223
|
-
import * as
|
|
15342
|
+
import * as fs13 from "fs";
|
|
15343
|
+
import * as path17 from "path";
|
|
14224
15344
|
import * as os17 from "os";
|
|
15345
|
+
function getAutoImplPid(ctx) {
|
|
15346
|
+
const proc = ctx.autoImplProcess;
|
|
15347
|
+
return proc && typeof proc.pid === "number" && proc.pid > 0 ? proc.pid : null;
|
|
15348
|
+
}
|
|
15349
|
+
function isPidAlive(pid) {
|
|
15350
|
+
try {
|
|
15351
|
+
process.kill(pid, 0);
|
|
15352
|
+
return true;
|
|
15353
|
+
} catch (error) {
|
|
15354
|
+
return error?.code === "EPERM";
|
|
15355
|
+
}
|
|
15356
|
+
}
|
|
15357
|
+
function clearStaleAutoImplState(ctx, reason) {
|
|
15358
|
+
if (!ctx.autoImplStatus.running && !ctx.autoImplProcess) return;
|
|
15359
|
+
const pid = getAutoImplPid(ctx);
|
|
15360
|
+
if (pid && isPidAlive(pid)) return;
|
|
15361
|
+
ctx.log(`Clearing stale auto-implement state: ${reason}${pid ? ` (pid ${pid})` : ""}`);
|
|
15362
|
+
ctx.autoImplProcess = null;
|
|
15363
|
+
ctx.autoImplStatus.running = false;
|
|
15364
|
+
}
|
|
14225
15365
|
function getDefaultAutoImplReference(ctx, category, type) {
|
|
14226
15366
|
if (category === "cli") {
|
|
14227
15367
|
return type === "codex-cli" ? "claude-cli" : "codex-cli";
|
|
@@ -14237,45 +15377,45 @@ function resolveAutoImplReference(ctx, category, requestedReference, targetType)
|
|
|
14237
15377
|
return fallback?.type || null;
|
|
14238
15378
|
}
|
|
14239
15379
|
function getLatestScriptVersionDir(scriptsDir) {
|
|
14240
|
-
if (!
|
|
14241
|
-
const versions =
|
|
15380
|
+
if (!fs13.existsSync(scriptsDir)) return null;
|
|
15381
|
+
const versions = fs13.readdirSync(scriptsDir).filter((d) => {
|
|
14242
15382
|
try {
|
|
14243
|
-
return
|
|
15383
|
+
return fs13.statSync(path17.join(scriptsDir, d)).isDirectory();
|
|
14244
15384
|
} catch {
|
|
14245
15385
|
return false;
|
|
14246
15386
|
}
|
|
14247
15387
|
}).sort((a, b) => b.localeCompare(a, void 0, { numeric: true, sensitivity: "base" }));
|
|
14248
15388
|
if (versions.length === 0) return null;
|
|
14249
|
-
return
|
|
15389
|
+
return path17.join(scriptsDir, versions[0]);
|
|
14250
15390
|
}
|
|
14251
15391
|
function resolveAutoImplWritableProviderDir(ctx, category, type, requestedDir) {
|
|
14252
|
-
const canonicalUserDir =
|
|
14253
|
-
const desiredDir = requestedDir ?
|
|
14254
|
-
const upstreamRoot =
|
|
14255
|
-
if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${
|
|
15392
|
+
const canonicalUserDir = path17.resolve(ctx.providerLoader.getUserProviderDir(category, type));
|
|
15393
|
+
const desiredDir = requestedDir ? path17.resolve(requestedDir) : canonicalUserDir;
|
|
15394
|
+
const upstreamRoot = path17.resolve(ctx.providerLoader.getUpstreamDir());
|
|
15395
|
+
if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path17.sep}`)) {
|
|
14256
15396
|
return { dir: null, reason: `Refusing to write into upstream provider directory: ${desiredDir}` };
|
|
14257
15397
|
}
|
|
14258
|
-
if (
|
|
15398
|
+
if (path17.basename(desiredDir) !== type) {
|
|
14259
15399
|
return { dir: null, reason: `Requested writable provider directory must end with '${type}': ${desiredDir}` };
|
|
14260
15400
|
}
|
|
14261
15401
|
const sourceDir = ctx.findProviderDir(type);
|
|
14262
15402
|
if (!sourceDir) {
|
|
14263
15403
|
return { dir: null, reason: `Provider source directory not found for '${type}'` };
|
|
14264
15404
|
}
|
|
14265
|
-
if (!
|
|
14266
|
-
|
|
14267
|
-
|
|
15405
|
+
if (!fs13.existsSync(desiredDir)) {
|
|
15406
|
+
fs13.mkdirSync(path17.dirname(desiredDir), { recursive: true });
|
|
15407
|
+
fs13.cpSync(sourceDir, desiredDir, { recursive: true });
|
|
14268
15408
|
ctx.log(`Auto-implement writable copy created: ${desiredDir}`);
|
|
14269
15409
|
}
|
|
14270
|
-
const providerJson =
|
|
14271
|
-
if (!
|
|
15410
|
+
const providerJson = path17.join(desiredDir, "provider.json");
|
|
15411
|
+
if (!fs13.existsSync(providerJson)) {
|
|
14272
15412
|
return { dir: null, reason: `provider.json not found in writable provider directory: ${desiredDir}` };
|
|
14273
15413
|
}
|
|
14274
15414
|
try {
|
|
14275
|
-
const providerData = JSON.parse(
|
|
15415
|
+
const providerData = JSON.parse(fs13.readFileSync(providerJson, "utf-8"));
|
|
14276
15416
|
if (providerData.disableUpstream !== true) {
|
|
14277
15417
|
providerData.disableUpstream = true;
|
|
14278
|
-
|
|
15418
|
+
fs13.writeFileSync(providerJson, JSON.stringify(providerData, null, 2));
|
|
14279
15419
|
}
|
|
14280
15420
|
} catch (error) {
|
|
14281
15421
|
return {
|
|
@@ -14288,15 +15428,15 @@ function resolveAutoImplWritableProviderDir(ctx, category, type, requestedDir) {
|
|
|
14288
15428
|
function loadAutoImplReferenceScripts(ctx, referenceType) {
|
|
14289
15429
|
if (!referenceType) return {};
|
|
14290
15430
|
const refDir = ctx.findProviderDir(referenceType);
|
|
14291
|
-
if (!refDir || !
|
|
15431
|
+
if (!refDir || !fs13.existsSync(refDir)) return {};
|
|
14292
15432
|
const referenceScripts = {};
|
|
14293
|
-
const scriptsDir =
|
|
15433
|
+
const scriptsDir = path17.join(refDir, "scripts");
|
|
14294
15434
|
const latestDir = getLatestScriptVersionDir(scriptsDir);
|
|
14295
15435
|
if (!latestDir) return referenceScripts;
|
|
14296
|
-
for (const file of
|
|
15436
|
+
for (const file of fs13.readdirSync(latestDir)) {
|
|
14297
15437
|
if (!file.endsWith(".js")) continue;
|
|
14298
15438
|
try {
|
|
14299
|
-
referenceScripts[file] =
|
|
15439
|
+
referenceScripts[file] = fs13.readFileSync(path17.join(latestDir, file), "utf-8");
|
|
14300
15440
|
} catch {
|
|
14301
15441
|
}
|
|
14302
15442
|
}
|
|
@@ -14304,11 +15444,20 @@ function loadAutoImplReferenceScripts(ctx, referenceType) {
|
|
|
14304
15444
|
}
|
|
14305
15445
|
async function handleAutoImplement(ctx, type, req, res) {
|
|
14306
15446
|
const body = await ctx.readBody(req);
|
|
14307
|
-
const {
|
|
15447
|
+
const {
|
|
15448
|
+
agent = "claude-cli",
|
|
15449
|
+
functions,
|
|
15450
|
+
reference,
|
|
15451
|
+
model,
|
|
15452
|
+
comment,
|
|
15453
|
+
providerDir: requestedProviderDir,
|
|
15454
|
+
verification
|
|
15455
|
+
} = body;
|
|
14308
15456
|
if (!functions || !Array.isArray(functions) || functions.length === 0) {
|
|
14309
15457
|
ctx.json(res, 400, { error: 'functions[] is required (e.g. ["readChat", "sendMessage"])' });
|
|
14310
15458
|
return;
|
|
14311
15459
|
}
|
|
15460
|
+
clearStaleAutoImplState(ctx, "new auto-implement request");
|
|
14312
15461
|
if (ctx.autoImplStatus.running) {
|
|
14313
15462
|
ctx.json(res, 409, { error: "Auto-implement already in progress", type: ctx.autoImplStatus.type });
|
|
14314
15463
|
return;
|
|
@@ -14326,7 +15475,55 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14326
15475
|
return;
|
|
14327
15476
|
}
|
|
14328
15477
|
const providerDir = writableProvider.dir;
|
|
15478
|
+
ctx.autoImplStatus = { running: false, type, progress: [] };
|
|
15479
|
+
if (provider.category === "cli" && verification && (verification.fixtureName || verification.fixtureNames && verification.fixtureNames.length > 0)) {
|
|
15480
|
+
sendAutoImplSSE(ctx, {
|
|
15481
|
+
event: "progress",
|
|
15482
|
+
data: {
|
|
15483
|
+
function: "_preflight",
|
|
15484
|
+
status: "verifying",
|
|
15485
|
+
message: "Running preflight verification before spawning agent..."
|
|
15486
|
+
}
|
|
15487
|
+
});
|
|
15488
|
+
try {
|
|
15489
|
+
const preflight = await runCliAutoImplVerification(ctx, type, verification);
|
|
15490
|
+
sendAutoImplSSE(ctx, { event: "verification", data: preflight });
|
|
15491
|
+
if (preflight.pass) {
|
|
15492
|
+
sendAutoImplSSE(ctx, {
|
|
15493
|
+
event: "complete",
|
|
15494
|
+
data: {
|
|
15495
|
+
success: true,
|
|
15496
|
+
exitCode: 0,
|
|
15497
|
+
functions,
|
|
15498
|
+
message: `\u2705 No-op: exact ${preflight.mode} already passes`,
|
|
15499
|
+
verification: preflight,
|
|
15500
|
+
skipped: true
|
|
15501
|
+
}
|
|
15502
|
+
});
|
|
15503
|
+
ctx.json(res, 200, {
|
|
15504
|
+
started: false,
|
|
15505
|
+
skipped: true,
|
|
15506
|
+
type,
|
|
15507
|
+
functions,
|
|
15508
|
+
providerDir,
|
|
15509
|
+
verification: preflight,
|
|
15510
|
+
message: "Preflight verification already passes. No auto-implement run needed."
|
|
15511
|
+
});
|
|
15512
|
+
return;
|
|
15513
|
+
}
|
|
15514
|
+
} catch (error) {
|
|
15515
|
+
sendAutoImplSSE(ctx, {
|
|
15516
|
+
event: "progress",
|
|
15517
|
+
data: {
|
|
15518
|
+
function: "_preflight",
|
|
15519
|
+
status: "verify_failed",
|
|
15520
|
+
message: `Preflight verification errored, continuing to agent run: ${error?.message || error}`
|
|
15521
|
+
}
|
|
15522
|
+
});
|
|
15523
|
+
}
|
|
15524
|
+
}
|
|
14329
15525
|
try {
|
|
15526
|
+
ctx.autoImplStatus = { running: true, type, progress: ctx.autoImplStatus.progress };
|
|
14330
15527
|
const resolvedReference = resolveAutoImplReference(ctx, provider.category, reference, type);
|
|
14331
15528
|
sendAutoImplSSE(ctx, {
|
|
14332
15529
|
event: "progress",
|
|
@@ -14346,17 +15543,17 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14346
15543
|
}
|
|
14347
15544
|
});
|
|
14348
15545
|
const referenceScripts = loadAutoImplReferenceScripts(ctx, resolvedReference);
|
|
14349
|
-
const prompt = buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, comment, resolvedReference);
|
|
14350
|
-
const tmpDir =
|
|
14351
|
-
if (!
|
|
14352
|
-
const promptFile =
|
|
14353
|
-
|
|
15546
|
+
const prompt = buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, comment, resolvedReference, verification);
|
|
15547
|
+
const tmpDir = path17.join(os17.tmpdir(), "adhdev-autoimpl");
|
|
15548
|
+
if (!fs13.existsSync(tmpDir)) fs13.mkdirSync(tmpDir, { recursive: true });
|
|
15549
|
+
const promptFile = path17.join(tmpDir, `prompt-${type}-${Date.now()}.md`);
|
|
15550
|
+
fs13.writeFileSync(promptFile, prompt, "utf-8");
|
|
14354
15551
|
ctx.log(`Auto-implement prompt written to ${promptFile} (${prompt.length} chars)`);
|
|
14355
15552
|
const agentProvider = ctx.providerLoader.resolve(agent) || ctx.providerLoader.getMeta(agent);
|
|
14356
15553
|
const spawn4 = agentProvider?.spawn;
|
|
14357
15554
|
if (!spawn4?.command) {
|
|
14358
15555
|
try {
|
|
14359
|
-
|
|
15556
|
+
fs13.unlinkSync(promptFile);
|
|
14360
15557
|
} catch {
|
|
14361
15558
|
}
|
|
14362
15559
|
ctx.json(res, 400, { error: `Agent '${agent}' has no spawn config. Select a CLI provider with a spawn configuration.` });
|
|
@@ -14365,7 +15562,8 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14365
15562
|
const agentCategory = agentProvider?.category;
|
|
14366
15563
|
if (agentCategory === "acp") {
|
|
14367
15564
|
sendAutoImplSSE(ctx, { event: "progress", data: { function: "_init", status: "spawning", message: `Spawning ACP agent: ${spawn4.command} ${(spawn4.args || []).join(" ")}` } });
|
|
14368
|
-
ctx.autoImplStatus =
|
|
15565
|
+
ctx.autoImplStatus.running = true;
|
|
15566
|
+
ctx.autoImplStatus.type = type;
|
|
14369
15567
|
const { ClientSideConnection: ClientSideConnection2, ndJsonStream: ndJsonStream2, PROTOCOL_VERSION: PROTOCOL_VERSION2 } = await import("@agentclientprotocol/sdk");
|
|
14370
15568
|
const { Readable: Readable2, Writable: Writable2 } = await import("stream");
|
|
14371
15569
|
const { spawn: spawnFn2 } = await import("child_process");
|
|
@@ -14457,7 +15655,7 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14457
15655
|
} catch {
|
|
14458
15656
|
}
|
|
14459
15657
|
try {
|
|
14460
|
-
|
|
15658
|
+
fs13.unlinkSync(promptFile);
|
|
14461
15659
|
} catch {
|
|
14462
15660
|
}
|
|
14463
15661
|
ctx.log(`Auto-implement (ACP) ${success ? "completed" : "failed"}: ${type} (exit: ${code})`);
|
|
@@ -14535,7 +15733,8 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14535
15733
|
}
|
|
14536
15734
|
}
|
|
14537
15735
|
sendAutoImplSSE(ctx, { event: "progress", data: { function: "_init", status: "spawning", message: `Spawning agent: ${shellCmd.substring(0, 200)}... (prompt: ${prompt.length} chars)` } });
|
|
14538
|
-
ctx.autoImplStatus =
|
|
15736
|
+
ctx.autoImplStatus.running = true;
|
|
15737
|
+
ctx.autoImplStatus.type = type;
|
|
14539
15738
|
const spawnedAt = Date.now();
|
|
14540
15739
|
let child;
|
|
14541
15740
|
let isPty = false;
|
|
@@ -14578,6 +15777,7 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14578
15777
|
let approvalKeys = { 0: "y\r" };
|
|
14579
15778
|
let approvalBuffer = "";
|
|
14580
15779
|
let lastApprovalTime = 0;
|
|
15780
|
+
let completionSignalSeen = false;
|
|
14581
15781
|
try {
|
|
14582
15782
|
const { normalizeCliProviderForRuntime: normalizeCliProviderForRuntime2 } = await Promise.resolve().then(() => (init_provider_cli_adapter(), provider_cli_adapter_exports));
|
|
14583
15783
|
const normalized = normalizeCliProviderForRuntime2(agentProvider);
|
|
@@ -14591,6 +15791,7 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14591
15791
|
approvalBuffer = (approvalBuffer + cleanData).slice(-1500);
|
|
14592
15792
|
const elapsed = Date.now() - spawnedAt;
|
|
14593
15793
|
if (elapsed > 15e3 && cleanData.includes("_PIPELINE_COMPLETE_SIGNAL_")) {
|
|
15794
|
+
completionSignalSeen = true;
|
|
14594
15795
|
ctx.log(`Agent finished task after ${Math.round(elapsed / 1e3)}s. Terminating interactive CLI session to unblock pipeline.`);
|
|
14595
15796
|
sendAutoImplSSE(ctx, { event: "output", data: { chunk: `
|
|
14596
15797
|
[\u{1F916} ADHDev Pipeline] Completion token detected. Proceeding...
|
|
@@ -14614,6 +15815,55 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14614
15815
|
lastApprovalTime = Date.now();
|
|
14615
15816
|
}
|
|
14616
15817
|
};
|
|
15818
|
+
const finalizeCliAutoImpl = async (code) => {
|
|
15819
|
+
ctx.autoImplProcess = null;
|
|
15820
|
+
let success = completionSignalSeen || code === 0;
|
|
15821
|
+
let message = success ? completionSignalSeen && code !== 0 ? "\u2705 Auto-implement complete (completion signal)" : "\u2705 Auto-implement complete" : `\u274C Agent exited (code: ${code})`;
|
|
15822
|
+
let verificationSummary = null;
|
|
15823
|
+
try {
|
|
15824
|
+
ctx.providerLoader.reload();
|
|
15825
|
+
} catch {
|
|
15826
|
+
}
|
|
15827
|
+
if (provider.category === "cli" && verification) {
|
|
15828
|
+
sendAutoImplSSE(ctx, {
|
|
15829
|
+
event: "progress",
|
|
15830
|
+
data: {
|
|
15831
|
+
function: "_verify",
|
|
15832
|
+
status: "running",
|
|
15833
|
+
message: "Running exact post-patch verification..."
|
|
15834
|
+
}
|
|
15835
|
+
});
|
|
15836
|
+
try {
|
|
15837
|
+
verificationSummary = await runCliAutoImplVerification(ctx, type, verification);
|
|
15838
|
+
sendAutoImplSSE(ctx, { event: "verification", data: verificationSummary });
|
|
15839
|
+
success = verificationSummary.pass;
|
|
15840
|
+
message = verificationSummary.pass ? `\u2705 Auto-implement complete (${verificationSummary.mode})` : `\u274C Post-patch verification failed (${verificationSummary.mode}): ${verificationSummary.failures.join("; ") || "unknown failure"}`;
|
|
15841
|
+
} catch (error) {
|
|
15842
|
+
success = false;
|
|
15843
|
+
message = `\u274C Post-patch verification error: ${error?.message || error}`;
|
|
15844
|
+
sendAutoImplSSE(ctx, {
|
|
15845
|
+
event: "verification",
|
|
15846
|
+
data: { pass: false, error: error?.message || String(error) }
|
|
15847
|
+
});
|
|
15848
|
+
}
|
|
15849
|
+
}
|
|
15850
|
+
ctx.autoImplStatus.running = false;
|
|
15851
|
+
sendAutoImplSSE(ctx, {
|
|
15852
|
+
event: "complete",
|
|
15853
|
+
data: {
|
|
15854
|
+
success,
|
|
15855
|
+
exitCode: code,
|
|
15856
|
+
functions,
|
|
15857
|
+
message,
|
|
15858
|
+
verification: verificationSummary
|
|
15859
|
+
}
|
|
15860
|
+
});
|
|
15861
|
+
try {
|
|
15862
|
+
fs13.unlinkSync(promptFile);
|
|
15863
|
+
} catch {
|
|
15864
|
+
}
|
|
15865
|
+
ctx.log(`Auto-implement ${success ? "completed" : "failed"}: ${type} (exit: ${code})${verificationSummary ? ` verify=${verificationSummary.pass ? "pass" : "fail"}` : ""}`);
|
|
15866
|
+
};
|
|
14617
15867
|
if (isPty) {
|
|
14618
15868
|
child.onData((data) => {
|
|
14619
15869
|
stdout += data;
|
|
@@ -14625,21 +15875,7 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14625
15875
|
sendAutoImplSSE(ctx, { event: "output", data: { chunk: data, stream: "stdout" } });
|
|
14626
15876
|
});
|
|
14627
15877
|
child.onExit(({ exitCode: code }) => {
|
|
14628
|
-
|
|
14629
|
-
ctx.autoImplStatus.running = false;
|
|
14630
|
-
const success = code === 0;
|
|
14631
|
-
sendAutoImplSSE(ctx, {
|
|
14632
|
-
event: "complete",
|
|
14633
|
-
data: { success, exitCode: code, functions, message: success ? "\u2705 Auto-implement complete" : `\u274C Agent exited (code: ${code})` }
|
|
14634
|
-
});
|
|
14635
|
-
try {
|
|
14636
|
-
ctx.providerLoader.reload();
|
|
14637
|
-
} catch {
|
|
14638
|
-
}
|
|
14639
|
-
try {
|
|
14640
|
-
fs12.unlinkSync(promptFile);
|
|
14641
|
-
} catch {
|
|
14642
|
-
}
|
|
15878
|
+
void finalizeCliAutoImpl(code);
|
|
14643
15879
|
});
|
|
14644
15880
|
} else {
|
|
14645
15881
|
child.stdout?.on("data", (d) => {
|
|
@@ -14656,27 +15892,7 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14656
15892
|
sendAutoImplSSE(ctx, { event: "output", data: { chunk, stream: "stderr" } });
|
|
14657
15893
|
});
|
|
14658
15894
|
child.on("exit", (code) => {
|
|
14659
|
-
|
|
14660
|
-
ctx.autoImplStatus.running = false;
|
|
14661
|
-
const success = code === 0;
|
|
14662
|
-
sendAutoImplSSE(ctx, {
|
|
14663
|
-
event: "complete",
|
|
14664
|
-
data: {
|
|
14665
|
-
success,
|
|
14666
|
-
exitCode: code,
|
|
14667
|
-
functions,
|
|
14668
|
-
message: success ? "\u2705 Auto-implement complete" : `\u274C Agent exited (code: ${code})`
|
|
14669
|
-
}
|
|
14670
|
-
});
|
|
14671
|
-
try {
|
|
14672
|
-
ctx.providerLoader.reload();
|
|
14673
|
-
} catch {
|
|
14674
|
-
}
|
|
14675
|
-
try {
|
|
14676
|
-
fs12.unlinkSync(promptFile);
|
|
14677
|
-
} catch {
|
|
14678
|
-
}
|
|
14679
|
-
ctx.log(`Auto-implement ${success ? "completed" : "failed"}: ${type} (exit: ${code})`);
|
|
15895
|
+
void finalizeCliAutoImpl(code);
|
|
14680
15896
|
});
|
|
14681
15897
|
}
|
|
14682
15898
|
ctx.json(res, 202, {
|
|
@@ -14693,9 +15909,9 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14693
15909
|
ctx.json(res, 500, { error: `Auto-implement failed: ${e.message}` });
|
|
14694
15910
|
}
|
|
14695
15911
|
}
|
|
14696
|
-
function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, userComment, referenceType) {
|
|
15912
|
+
function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, userComment, referenceType, verification) {
|
|
14697
15913
|
if (provider.category === "cli") {
|
|
14698
|
-
return buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType);
|
|
15914
|
+
return buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType, verification);
|
|
14699
15915
|
}
|
|
14700
15916
|
const lines = [];
|
|
14701
15917
|
lines.push("You are implementing browser automation scripts for an IDE provider.");
|
|
@@ -14720,7 +15936,7 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
|
|
|
14720
15936
|
setMode: "set_mode.js"
|
|
14721
15937
|
};
|
|
14722
15938
|
const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
|
|
14723
|
-
const scriptsDir =
|
|
15939
|
+
const scriptsDir = path17.join(providerDir, "scripts");
|
|
14724
15940
|
const latestScriptsDir = getLatestScriptVersionDir(scriptsDir);
|
|
14725
15941
|
if (latestScriptsDir) {
|
|
14726
15942
|
lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
|
|
@@ -14728,10 +15944,10 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
|
|
|
14728
15944
|
lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
|
|
14729
15945
|
lines.push("These are the ONLY files you are allowed to modify. Replace the TODO stubs with working implementations.");
|
|
14730
15946
|
lines.push("");
|
|
14731
|
-
for (const file of
|
|
15947
|
+
for (const file of fs13.readdirSync(latestScriptsDir)) {
|
|
14732
15948
|
if (file.endsWith(".js") && targetFileNames.has(file)) {
|
|
14733
15949
|
try {
|
|
14734
|
-
const content =
|
|
15950
|
+
const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
|
|
14735
15951
|
lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
|
|
14736
15952
|
lines.push("```javascript");
|
|
14737
15953
|
lines.push(content);
|
|
@@ -14741,14 +15957,14 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
|
|
|
14741
15957
|
}
|
|
14742
15958
|
}
|
|
14743
15959
|
}
|
|
14744
|
-
const refFiles =
|
|
15960
|
+
const refFiles = fs13.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
|
|
14745
15961
|
if (refFiles.length > 0) {
|
|
14746
15962
|
lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
|
|
14747
15963
|
lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
|
|
14748
15964
|
lines.push("");
|
|
14749
15965
|
for (const file of refFiles) {
|
|
14750
15966
|
try {
|
|
14751
|
-
const content =
|
|
15967
|
+
const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
|
|
14752
15968
|
lines.push(`### \`${file}\` \u{1F512}`);
|
|
14753
15969
|
lines.push("```javascript");
|
|
14754
15970
|
lines.push(content);
|
|
@@ -14789,11 +16005,11 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
|
|
|
14789
16005
|
lines.push("");
|
|
14790
16006
|
}
|
|
14791
16007
|
}
|
|
14792
|
-
const docsDir =
|
|
16008
|
+
const docsDir = path17.join(providerDir, "../../docs");
|
|
14793
16009
|
const loadGuide = (name) => {
|
|
14794
16010
|
try {
|
|
14795
|
-
const p =
|
|
14796
|
-
if (
|
|
16011
|
+
const p = path17.join(docsDir, name);
|
|
16012
|
+
if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
|
|
14797
16013
|
} catch {
|
|
14798
16014
|
}
|
|
14799
16015
|
return null;
|
|
@@ -14951,8 +16167,69 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
|
|
|
14951
16167
|
lines.push("Start NOW. Do not ask for permission. Explore the DOM -> Code -> Test.");
|
|
14952
16168
|
return lines.join("\n");
|
|
14953
16169
|
}
|
|
14954
|
-
function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType) {
|
|
16170
|
+
function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType, verification) {
|
|
14955
16171
|
const lines = [];
|
|
16172
|
+
const defaultExercisePayload = {
|
|
16173
|
+
type,
|
|
16174
|
+
workingDir: providerDir,
|
|
16175
|
+
freshSession: true,
|
|
16176
|
+
autoLaunch: true,
|
|
16177
|
+
autoResolveApprovals: true,
|
|
16178
|
+
approvalButtonIndex: 0,
|
|
16179
|
+
timeoutMs: 45e3,
|
|
16180
|
+
traceLimit: 200,
|
|
16181
|
+
text: "Create a file at tmp/adhdev_provider_fix_test.py that prints the current working directory and the squares of 1 through 5, then run python3 tmp/adhdev_provider_fix_test.py and tell me the exact output."
|
|
16182
|
+
};
|
|
16183
|
+
const exercisePayload = {
|
|
16184
|
+
...defaultExercisePayload,
|
|
16185
|
+
...verification?.request || {},
|
|
16186
|
+
type,
|
|
16187
|
+
workingDir: providerDir
|
|
16188
|
+
};
|
|
16189
|
+
const exerciseJson = JSON.stringify(exercisePayload).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
|
|
16190
|
+
const verificationInspectFields = verification?.inspectFields?.length ? verification.inspectFields : [
|
|
16191
|
+
"debug.messages",
|
|
16192
|
+
"trace.entries[].payload.parsedLastAssistant",
|
|
16193
|
+
"trace.entries[].payload.lastAssistant"
|
|
16194
|
+
];
|
|
16195
|
+
const verificationMustContainAny = verification?.mustContainAny || [];
|
|
16196
|
+
const verificationMustNotContainAny = verification?.mustNotContainAny || [];
|
|
16197
|
+
const verificationMustMatchAny = verification?.mustMatchAny || [];
|
|
16198
|
+
const verificationMustNotMatchAny = verification?.mustNotMatchAny || [];
|
|
16199
|
+
const verificationLastAssistantMustContainAny = verification?.lastAssistantMustContainAny || [];
|
|
16200
|
+
const verificationLastAssistantMustNotContainAny = verification?.lastAssistantMustNotContainAny || [];
|
|
16201
|
+
const verificationLastAssistantMustMatchAny = verification?.lastAssistantMustMatchAny || [];
|
|
16202
|
+
const verificationLastAssistantMustNotMatchAny = verification?.lastAssistantMustNotMatchAny || [];
|
|
16203
|
+
const quotedMustContain = verificationMustContainAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16204
|
+
const quotedMustNotContain = verificationMustNotContainAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16205
|
+
const quotedMustMatch = verificationMustMatchAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16206
|
+
const quotedMustNotMatch = verificationMustNotMatchAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16207
|
+
const quotedLastAssistantMustContain = verificationLastAssistantMustContainAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16208
|
+
const quotedLastAssistantMustNotContain = verificationLastAssistantMustNotContainAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16209
|
+
const quotedLastAssistantMustMatch = verificationLastAssistantMustMatchAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16210
|
+
const quotedLastAssistantMustNotMatch = verificationLastAssistantMustNotMatchAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16211
|
+
const fixtureName = verification?.fixtureName || `${type}-provider-fix`;
|
|
16212
|
+
const fixtureNames = Array.isArray(verification?.fixtureNames) ? verification.fixtureNames.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
16213
|
+
const fixtureCaptureJson = JSON.stringify({
|
|
16214
|
+
type,
|
|
16215
|
+
name: fixtureName,
|
|
16216
|
+
request: exercisePayload,
|
|
16217
|
+
assertions: {
|
|
16218
|
+
mustContainAny: verificationMustContainAny,
|
|
16219
|
+
mustNotContainAny: verificationMustNotContainAny,
|
|
16220
|
+
mustMatchAny: verificationMustMatchAny,
|
|
16221
|
+
mustNotMatchAny: verificationMustNotMatchAny,
|
|
16222
|
+
lastAssistantMustContainAny: verificationLastAssistantMustContainAny,
|
|
16223
|
+
lastAssistantMustNotContainAny: verificationLastAssistantMustNotContainAny,
|
|
16224
|
+
lastAssistantMustMatchAny: verificationLastAssistantMustMatchAny,
|
|
16225
|
+
lastAssistantMustNotMatchAny: verificationLastAssistantMustNotMatchAny,
|
|
16226
|
+
requireNotTimedOut: true
|
|
16227
|
+
}
|
|
16228
|
+
}).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
|
|
16229
|
+
const fixtureReplayJson = JSON.stringify({
|
|
16230
|
+
type,
|
|
16231
|
+
name: fixtureName
|
|
16232
|
+
}).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
|
|
14956
16233
|
lines.push("You are implementing PTY parsing scripts for a CLI provider.");
|
|
14957
16234
|
lines.push("Be concise. Do NOT explain your reasoning. Edit files directly and verify with the local DevServer.");
|
|
14958
16235
|
lines.push("");
|
|
@@ -14966,7 +16243,7 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
14966
16243
|
parseApproval: "parse_approval.js"
|
|
14967
16244
|
};
|
|
14968
16245
|
const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
|
|
14969
|
-
const scriptsDir =
|
|
16246
|
+
const scriptsDir = path17.join(providerDir, "scripts");
|
|
14970
16247
|
const latestScriptsDir = getLatestScriptVersionDir(scriptsDir);
|
|
14971
16248
|
if (latestScriptsDir) {
|
|
14972
16249
|
lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
|
|
@@ -14974,11 +16251,11 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
14974
16251
|
lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
|
|
14975
16252
|
lines.push("These are the ONLY files you are allowed to modify. Replace TODO or heuristic-only logic with working PTY-aware implementations.");
|
|
14976
16253
|
lines.push("");
|
|
14977
|
-
for (const file of
|
|
16254
|
+
for (const file of fs13.readdirSync(latestScriptsDir)) {
|
|
14978
16255
|
if (!file.endsWith(".js")) continue;
|
|
14979
16256
|
if (!targetFileNames.has(file)) continue;
|
|
14980
16257
|
try {
|
|
14981
|
-
const content =
|
|
16258
|
+
const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
|
|
14982
16259
|
lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
|
|
14983
16260
|
lines.push("```javascript");
|
|
14984
16261
|
lines.push(content);
|
|
@@ -14987,14 +16264,14 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
14987
16264
|
} catch {
|
|
14988
16265
|
}
|
|
14989
16266
|
}
|
|
14990
|
-
const refFiles =
|
|
16267
|
+
const refFiles = fs13.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
|
|
14991
16268
|
if (refFiles.length > 0) {
|
|
14992
16269
|
lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
|
|
14993
16270
|
lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
|
|
14994
16271
|
lines.push("");
|
|
14995
16272
|
for (const file of refFiles) {
|
|
14996
16273
|
try {
|
|
14997
|
-
const content =
|
|
16274
|
+
const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
|
|
14998
16275
|
lines.push(`### \`${file}\` \u{1F512}`);
|
|
14999
16276
|
lines.push("```javascript");
|
|
15000
16277
|
lines.push(content);
|
|
@@ -15027,17 +16304,17 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15027
16304
|
lines.push("");
|
|
15028
16305
|
}
|
|
15029
16306
|
}
|
|
15030
|
-
const docsDir =
|
|
16307
|
+
const docsDir = path17.join(providerDir, "../../docs");
|
|
15031
16308
|
const loadGuide = (name) => {
|
|
15032
16309
|
try {
|
|
15033
|
-
const p =
|
|
15034
|
-
if (
|
|
16310
|
+
const p = path17.join(docsDir, name);
|
|
16311
|
+
if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
|
|
15035
16312
|
} catch {
|
|
15036
16313
|
}
|
|
15037
16314
|
return null;
|
|
15038
16315
|
};
|
|
15039
16316
|
const providerGuide = loadGuide("PROVIDER_GUIDE.md");
|
|
15040
|
-
if (providerGuide) {
|
|
16317
|
+
if (providerGuide && provider.category !== "cli") {
|
|
15041
16318
|
lines.push("## Documentation: PROVIDER_GUIDE.md");
|
|
15042
16319
|
lines.push("```markdown");
|
|
15043
16320
|
lines.push(providerGuide);
|
|
@@ -15079,6 +16356,11 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15079
16356
|
lines.push("13. After the first successful live repro, stop broad diagnosis. Edit the scripts, reload, and verify. Do not burn tokens on repeated re-inspection without code changes.");
|
|
15080
16357
|
lines.push("14. If the visible current screen is clean and sufficient, do NOT fall back to complex buffer heuristics. Simpler current-screen parsing is preferred.");
|
|
15081
16358
|
lines.push("15. Before changing parser logic, verify whether `provider.json` submit/approval behavior (`sendDelayMs`, `approvalKeys`, submit strategy) is the simpler and more correct fix.");
|
|
16359
|
+
lines.push('16. Do NOT patch transcript bugs by piling up one-off literal string exceptions (`includes("foo")`, `=== "bar"`, ad hoc allowlists/denylists) for every observed variant. Model the UI as PATTERN FAMILIES using reusable regex classifiers and normalization first.');
|
|
16360
|
+
lines.push("17. If you find yourself adding a second or third near-duplicate literal check for spinner words, tool headers, approval prompts, footer chrome, or OSC residue, STOP and replace them with a broader regex or helper classifier.");
|
|
16361
|
+
lines.push('18. Prefer a small number of named classifiers such as "status line", "tool header", "tool detail", "footer chrome", "approval cue", "prompt line", and "OSC residue" over a long chain of unrelated string checks.');
|
|
16362
|
+
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.");
|
|
16363
|
+
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.");
|
|
15082
16364
|
lines.push("");
|
|
15083
16365
|
lines.push("## Task");
|
|
15084
16366
|
lines.push(`Edit files in \`${providerDir}\` to implement: **${functions.join(", ")}**`);
|
|
@@ -15086,35 +16368,145 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15086
16368
|
lines.push("## Verification API");
|
|
15087
16369
|
lines.push("Use the DevServer CLI debug endpoints, not DOM/CDP routes.");
|
|
15088
16370
|
lines.push("");
|
|
15089
|
-
lines.push("### 1.
|
|
16371
|
+
lines.push("### 1. Preferred: run a full autonomous repro");
|
|
16372
|
+
lines.push("Use the exercise endpoint first. It launches a fresh CLI session, sends the repro prompt, auto-resolves approvals, waits for the session to settle, and returns the final debug + trace payload in one response.");
|
|
15090
16373
|
lines.push("```bash");
|
|
15091
|
-
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/
|
|
16374
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/exercise \\`);
|
|
15092
16375
|
lines.push(' -H "Content-Type: application/json" \\');
|
|
15093
|
-
lines.push(` -d '
|
|
16376
|
+
lines.push(` -d '${exerciseJson}'`);
|
|
15094
16377
|
lines.push("```");
|
|
15095
16378
|
lines.push("");
|
|
16379
|
+
if (verification?.description) {
|
|
16380
|
+
lines.push("Verification intent:");
|
|
16381
|
+
lines.push(verification.description);
|
|
16382
|
+
lines.push("");
|
|
16383
|
+
}
|
|
16384
|
+
lines.push("Read the JSON response carefully. It already includes:");
|
|
16385
|
+
lines.push("1. `instanceId`");
|
|
16386
|
+
lines.push("2. `statusesSeen` and `approvalsResolved`");
|
|
16387
|
+
lines.push("3. `debug` for the final settled state");
|
|
16388
|
+
lines.push("4. `trace.entries` for the repro turn");
|
|
16389
|
+
lines.push("");
|
|
16390
|
+
lines.push("Save the response to a temp file and inspect the exact parsed transcript fields before editing:");
|
|
16391
|
+
lines.push("```bash");
|
|
16392
|
+
lines.push(`EXERCISE_JSON=$(mktemp)`);
|
|
16393
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/exercise \\`);
|
|
16394
|
+
lines.push(' -H "Content-Type: application/json" \\');
|
|
16395
|
+
lines.push(` -d '${exerciseJson}' > "$EXERCISE_JSON"`);
|
|
16396
|
+
lines.push(`jq '{timedOut,statusesSeen,approvalsResolved,inspect:{${verificationInspectFields.map((field, index) => `f${index + 1}: .${field}`).join(", ")}}}' "$EXERCISE_JSON"`);
|
|
16397
|
+
lines.push("```");
|
|
16398
|
+
lines.push("");
|
|
16399
|
+
if (verificationMustContainAny.length > 0 || verificationMustNotContainAny.length > 0 || verificationMustMatchAny.length > 0 || verificationMustNotMatchAny.length > 0 || verificationLastAssistantMustContainAny.length > 0 || verificationLastAssistantMustNotContainAny.length > 0 || verificationLastAssistantMustMatchAny.length > 0 || verificationLastAssistantMustNotMatchAny.length > 0) {
|
|
16400
|
+
lines.push("The exact repro below is mandatory. Do NOT declare success unless these transcript assertions pass on the exercise JSON from the PATCHED provider.");
|
|
16401
|
+
lines.push("```bash");
|
|
16402
|
+
if (verificationMustContainAny.length > 0) {
|
|
16403
|
+
lines.push(`node -e 'const fs=require("fs");const text=fs.readFileSync(process.argv[1],"utf8");const required=[${quotedMustContain}];const missing=required.filter(v=>!text.includes(v));if(missing.length){console.error("Missing required substrings:\\n"+missing.join("\\n"));process.exit(1);}' "$EXERCISE_JSON"`);
|
|
16404
|
+
}
|
|
16405
|
+
if (verificationMustNotContainAny.length > 0) {
|
|
16406
|
+
lines.push(`node -e 'const fs=require("fs");const text=fs.readFileSync(process.argv[1],"utf8");const banned=[${quotedMustNotContain}];const hits=banned.filter(v=>text.includes(v));if(hits.length){console.error("Found banned substrings:\\n"+hits.join("\\n"));process.exit(1);}' "$EXERCISE_JSON"`);
|
|
16407
|
+
}
|
|
16408
|
+
if (verificationMustMatchAny.length > 0) {
|
|
16409
|
+
lines.push(`node -e 'const fs=require("fs");const text=fs.readFileSync(process.argv[1],"utf8");const required=[${quotedMustMatch}].map(v=>new RegExp(v,"m"));const missing=required.filter(v=>!v.test(text)).map(v=>String(v));if(missing.length){console.error("Missing required regex matches:\\n"+missing.join("\\n"));process.exit(1);}' "$EXERCISE_JSON"`);
|
|
16410
|
+
}
|
|
16411
|
+
if (verificationMustNotMatchAny.length > 0) {
|
|
16412
|
+
lines.push(`node -e 'const fs=require("fs");const text=fs.readFileSync(process.argv[1],"utf8");const banned=[${quotedMustNotMatch}].map(v=>new RegExp(v,"m"));const hits=banned.filter(v=>v.test(text)).map(v=>String(v));if(hits.length){console.error("Found banned regex matches:\\n"+hits.join("\\n"));process.exit(1);}' "$EXERCISE_JSON"`);
|
|
16413
|
+
}
|
|
16414
|
+
if (verificationLastAssistantMustContainAny.length > 0) {
|
|
16415
|
+
lines.push(`node -e 'const fs=require("fs");const payload=JSON.parse(fs.readFileSync(process.argv[1],"utf8"));const text=String(payload.lastAssistant||"");const required=[${quotedLastAssistantMustContain}];const missing=required.filter(v=>!text.includes(v));if(missing.length){console.error("Missing required lastAssistant substrings:\\n"+missing.join("\\n"));process.exit(1);}' "$EXERCISE_JSON"`);
|
|
16416
|
+
}
|
|
16417
|
+
if (verificationLastAssistantMustNotContainAny.length > 0) {
|
|
16418
|
+
lines.push(`node -e 'const fs=require("fs");const payload=JSON.parse(fs.readFileSync(process.argv[1],"utf8"));const text=String(payload.lastAssistant||"");const banned=[${quotedLastAssistantMustNotContain}];const hits=banned.filter(v=>text.includes(v));if(hits.length){console.error("Found banned lastAssistant substrings:\\n"+hits.join("\\n"));process.exit(1);}' "$EXERCISE_JSON"`);
|
|
16419
|
+
}
|
|
16420
|
+
if (verificationLastAssistantMustMatchAny.length > 0) {
|
|
16421
|
+
lines.push(`node -e 'const fs=require("fs");const payload=JSON.parse(fs.readFileSync(process.argv[1],"utf8"));const text=String(payload.lastAssistant||"");const required=[${quotedLastAssistantMustMatch}].map(v=>new RegExp(v,"m"));const missing=required.filter(v=>!v.test(text)).map(v=>String(v));if(missing.length){console.error("Missing required lastAssistant regex matches:\\n"+missing.join("\\n"));process.exit(1);}' "$EXERCISE_JSON"`);
|
|
16422
|
+
}
|
|
16423
|
+
if (verificationLastAssistantMustNotMatchAny.length > 0) {
|
|
16424
|
+
lines.push(`node -e 'const fs=require("fs");const payload=JSON.parse(fs.readFileSync(process.argv[1],"utf8"));const text=String(payload.lastAssistant||"");const banned=[${quotedLastAssistantMustNotMatch}].map(v=>new RegExp(v,"m"));const hits=banned.filter(v=>v.test(text)).map(v=>String(v));if(hits.length){console.error("Found banned lastAssistant regex matches:\\n"+hits.join("\\n"));process.exit(1);}' "$EXERCISE_JSON"`);
|
|
16425
|
+
}
|
|
16426
|
+
lines.push("```");
|
|
16427
|
+
lines.push("");
|
|
16428
|
+
}
|
|
16429
|
+
lines.push("If you need a manual follow-up repro after patching, use the SAME endpoint again with the SAME prompt and compare the new trace to the previous one.");
|
|
16430
|
+
lines.push("");
|
|
16431
|
+
lines.push("### 1b. Persist or replay the exact repro as a reusable fixture");
|
|
16432
|
+
if (fixtureNames.length > 0) {
|
|
16433
|
+
lines.push(`Replay this exact fixture suite before editing, and replay the SAME suite again after patching. Do not declare success unless EVERY fixture passes: ${fixtureNames.map((name) => `\`${name}\``).join(", ")}.`);
|
|
16434
|
+
for (const name of fixtureNames) {
|
|
16435
|
+
const replayJson = JSON.stringify({ type, name }).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
|
|
16436
|
+
lines.push("```bash");
|
|
16437
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/replay \\`);
|
|
16438
|
+
lines.push(' -H "Content-Type: application/json" \\');
|
|
16439
|
+
lines.push(` -d '${replayJson}'`);
|
|
16440
|
+
lines.push("```");
|
|
16441
|
+
lines.push("");
|
|
16442
|
+
}
|
|
16443
|
+
lines.push("Do not create new fixtures unless one of the listed fixtures is missing or stale.");
|
|
16444
|
+
} else if (verification?.fixtureName) {
|
|
16445
|
+
lines.push(`Replay the EXISTING saved fixture \`${fixtureName}\` before editing, and replay the SAME fixture again after patching. Do not declare success unless that exact fixture passes.`);
|
|
16446
|
+
lines.push("```bash");
|
|
16447
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/replay \\`);
|
|
16448
|
+
lines.push(' -H "Content-Type: application/json" \\');
|
|
16449
|
+
lines.push(` -d '${fixtureReplayJson}'`);
|
|
16450
|
+
lines.push("```");
|
|
16451
|
+
lines.push("");
|
|
16452
|
+
lines.push("Only if the named fixture is missing or outdated should you recapture it. Prefer replaying the existing failing fixture over creating a new one.");
|
|
16453
|
+
lines.push("```bash");
|
|
16454
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/capture \\`);
|
|
16455
|
+
lines.push(' -H "Content-Type: application/json" \\');
|
|
16456
|
+
lines.push(` -d '${fixtureCaptureJson}'`);
|
|
16457
|
+
lines.push("```");
|
|
16458
|
+
} else {
|
|
16459
|
+
lines.push("Capture the exact exercise once before editing. After patching, replay THIS fixture and do not declare success unless replay passes.");
|
|
16460
|
+
lines.push("```bash");
|
|
16461
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/capture \\`);
|
|
16462
|
+
lines.push(' -H "Content-Type: application/json" \\');
|
|
16463
|
+
lines.push(` -d '${fixtureCaptureJson}'`);
|
|
16464
|
+
lines.push("");
|
|
16465
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/replay \\`);
|
|
16466
|
+
lines.push(' -H "Content-Type: application/json" \\');
|
|
16467
|
+
lines.push(` -d '${fixtureReplayJson}'`);
|
|
16468
|
+
lines.push("```");
|
|
16469
|
+
}
|
|
16470
|
+
lines.push("");
|
|
16471
|
+
lines.push("The capture endpoint saves the exact request, initial result, and transcript assertions into the provider directory. The replay endpoint reruns the SAME exercise against your patched scripts and returns pass/fail.");
|
|
16472
|
+
lines.push("");
|
|
15096
16473
|
lines.push("### 2. Inspect parsed + raw adapter state");
|
|
15097
16474
|
lines.push("```bash");
|
|
16475
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/launch \\`);
|
|
16476
|
+
lines.push(' -H "Content-Type: application/json" \\');
|
|
16477
|
+
lines.push(` -d '{"type":"${type}","workingDir":"${providerDir.replace(/\\/g, "\\\\")}"}'`);
|
|
15098
16478
|
lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/debug/${type}`);
|
|
16479
|
+
lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/trace/${type}`);
|
|
15099
16480
|
lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/status`);
|
|
15100
16481
|
lines.push("```");
|
|
15101
16482
|
lines.push("");
|
|
16483
|
+
lines.push("The CLI trace endpoint is the primary debugging source. Read it BEFORE editing any parser code.");
|
|
16484
|
+
lines.push("Use the trace timeline to find the latest `settled` or `commit_transcript` frame for the repro turn and inspect these fields first:");
|
|
16485
|
+
lines.push("1. `payload.screenText`");
|
|
16486
|
+
lines.push("2. `payload.detectStatus` and `payload.parsedStatus`");
|
|
16487
|
+
lines.push("3. `payload.parsedLastAssistant`");
|
|
16488
|
+
lines.push("4. `payload.approval` / `payload.parsedActiveModal`");
|
|
16489
|
+
lines.push("5. `payload.rawPreview` only when control-sequence residue matters");
|
|
16490
|
+
lines.push("");
|
|
15102
16491
|
lines.push("The debug payload should be read in this priority order:");
|
|
15103
16492
|
lines.push("1. `screenText` / current visible state");
|
|
15104
16493
|
lines.push("2. parsed `status`, `messages`, `activeModal`");
|
|
15105
16494
|
lines.push("3. `rawBuffer` only for style/control-sequence cues");
|
|
15106
16495
|
lines.push("4. `buffer` only when the current screen is insufficient");
|
|
15107
16496
|
lines.push("");
|
|
15108
|
-
lines.push("
|
|
16497
|
+
lines.push("If the bug is transcript corruption, quote the exact bad `parsedLastAssistant` or bad committed assistant message from the trace and patch against that concrete failure.");
|
|
16498
|
+
lines.push("Do NOT guess based only on the final chat bubble or a truncated UI preview.");
|
|
16499
|
+
lines.push("");
|
|
16500
|
+
lines.push("Extract the current `instanceId` from the exercise, launch, or status response and keep using it below.");
|
|
15109
16501
|
lines.push("");
|
|
15110
|
-
lines.push("### 3.
|
|
16502
|
+
lines.push("### 3. Manual fallback only: send a realistic approval-triggering prompt");
|
|
15111
16503
|
lines.push("```bash");
|
|
15112
16504
|
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/send \\`);
|
|
15113
16505
|
lines.push(' -H "Content-Type: application/json" \\');
|
|
15114
16506
|
lines.push(` -d '{"type":"${type}","instanceId":"<INSTANCE_ID>","text":"Create a file at tmp/adhdev_provider_fix_test.py that prints the current working directory and the squares of 1 through 5, then run python3 tmp/adhdev_provider_fix_test.py and tell me the exact output."}'`);
|
|
15115
16507
|
lines.push("```");
|
|
15116
16508
|
lines.push("");
|
|
15117
|
-
lines.push("### 4.
|
|
16509
|
+
lines.push("### 4. Manual fallback only: if approval appears, resolve it until the CLI reaches idle");
|
|
15118
16510
|
lines.push("```bash");
|
|
15119
16511
|
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/resolve \\`);
|
|
15120
16512
|
lines.push(' -H "Content-Type: application/json" \\');
|
|
@@ -15124,10 +16516,14 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15124
16516
|
lines.push(` -d '{"type":"${type}","instanceId":"<INSTANCE_ID>","keys":"1"}'`);
|
|
15125
16517
|
lines.push("```");
|
|
15126
16518
|
lines.push("");
|
|
15127
|
-
lines.push("Use `resolve` when the parsed modal buttons are correct. Use `raw` when the CLI expects a literal keystroke like `1`, `y`, or Enter. Repeat until idle.");
|
|
16519
|
+
lines.push("Use `resolve` when the parsed modal buttons are correct. Use `raw` when the CLI expects a literal keystroke like `1`, `y`, or Enter. Repeat until idle. Prefer the exercise endpoint instead of doing this by hand.");
|
|
15128
16520
|
lines.push("");
|
|
15129
16521
|
lines.push("### Patch Discipline");
|
|
15130
16522
|
lines.push("Once the repro is confirmed, immediately edit the target files. Avoid loops where you keep re-reading long files or re-running the same debug commands without changing code.");
|
|
16523
|
+
lines.push("For CLI transcript bugs, reproduce once with the exercise endpoint, inspect the returned trace once, patch immediately, then re-run the SAME exercise and compare the new `commit_transcript` frame.");
|
|
16524
|
+
lines.push("If the patched run still fails the exact required/banned substring checks above, the task is NOT complete even if the CLI exits normally.");
|
|
16525
|
+
lines.push("When you patch, write down the pattern family you are fixing: e.g. spinner/status, tool block, approval modal, footer chrome, OSC/control residue, prompt echo, or long-output continuation. Patch that family once instead of adding case-by-case literals.");
|
|
16526
|
+
lines.push('Bad fix pattern: add another `includes("Drizzling")` or `includes("Show more (")` check. Good fix pattern: broaden the regex/helper that recognizes spinner words, collapsed tool overflow lines, or footer chrome as a family.');
|
|
15131
16527
|
lines.push("");
|
|
15132
16528
|
lines.push("### 5. Verify the side effects outside the CLI");
|
|
15133
16529
|
lines.push("```bash");
|
|
@@ -15152,6 +16548,8 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15152
16548
|
lines.push("7. Re-run the debug endpoints after edits. Do NOT finish until the parsed result looks correct.");
|
|
15153
16549
|
lines.push("8. Confirm the parser still works after a redraw or scroll change without duplicating transcript history.");
|
|
15154
16550
|
lines.push("9. Confirm the implementation prefers current-screen signals over stale history when both are present.");
|
|
16551
|
+
lines.push("10. For transcript-cleanliness bugs, confirm the latest `commit_transcript` trace frame no longer contains tool headers, approval prompts, OSC residue like `0;`, or footer chrome unless they are truly user-facing answer content.");
|
|
16552
|
+
lines.push("11. Confirm the implementation uses generalized pattern classifiers or regexes for noisy UI families instead of accumulating one-off literal string exceptions for each observed sample.");
|
|
15155
16553
|
lines.push("");
|
|
15156
16554
|
if (userComment) {
|
|
15157
16555
|
lines.push("## \u26A0\uFE0F User Instructions (HIGH PRIORITY)");
|
|
@@ -15160,10 +16558,11 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15160
16558
|
lines.push(userComment);
|
|
15161
16559
|
lines.push("");
|
|
15162
16560
|
}
|
|
15163
|
-
lines.push("Start NOW. Launch the CLI, inspect PTY state, edit the scripts, and verify via the CLI debug endpoints.");
|
|
16561
|
+
lines.push("Start NOW. Launch the CLI, inspect the trace and PTY state, edit the scripts, and verify via the CLI debug + trace endpoints.");
|
|
15164
16562
|
return lines.join("\n");
|
|
15165
16563
|
}
|
|
15166
16564
|
function handleAutoImplSSE(ctx, type, req, res) {
|
|
16565
|
+
clearStaleAutoImplState(ctx, "SSE connection opened");
|
|
15167
16566
|
res.writeHead(200, {
|
|
15168
16567
|
"Content-Type": "text/event-stream",
|
|
15169
16568
|
"Cache-Control": "no-cache",
|
|
@@ -15185,6 +16584,7 @@ data: ${JSON.stringify(p.data)}
|
|
|
15185
16584
|
});
|
|
15186
16585
|
}
|
|
15187
16586
|
function handleAutoImplCancel(ctx, _type, _req, res) {
|
|
16587
|
+
clearStaleAutoImplState(ctx, "cancel request");
|
|
15188
16588
|
if (ctx.autoImplProcess) {
|
|
15189
16589
|
ctx.autoImplProcess.kill("SIGTERM");
|
|
15190
16590
|
setTimeout(() => {
|
|
@@ -15268,11 +16668,16 @@ var DevServer = class _DevServer {
|
|
|
15268
16668
|
{ method: "GET", pattern: "/api/cli/status", handler: (q, s) => this.handleCliStatus(q, s) },
|
|
15269
16669
|
{ method: "POST", pattern: "/api/cli/launch", handler: (q, s) => this.handleCliLaunch(q, s) },
|
|
15270
16670
|
{ method: "POST", pattern: "/api/cli/send", handler: (q, s) => this.handleCliSend(q, s) },
|
|
16671
|
+
{ method: "POST", pattern: "/api/cli/exercise", handler: (q, s) => this.handleCliExercise(q, s) },
|
|
16672
|
+
{ method: "POST", pattern: "/api/cli/fixture/capture", handler: (q, s) => this.handleCliFixtureCapture(q, s) },
|
|
16673
|
+
{ method: "POST", pattern: "/api/cli/fixture/replay", handler: (q, s) => this.handleCliFixtureReplay(q, s) },
|
|
15271
16674
|
{ method: "POST", pattern: "/api/cli/resolve", handler: (q, s) => this.handleCliResolve(q, s) },
|
|
15272
16675
|
{ method: "POST", pattern: "/api/cli/raw", handler: (q, s) => this.handleCliRaw(q, s) },
|
|
15273
16676
|
{ method: "POST", pattern: "/api/cli/stop", handler: (q, s) => this.handleCliStop(q, s) },
|
|
15274
16677
|
{ method: "GET", pattern: "/api/cli/events", handler: (q, s) => this.handleCliSSE(q, s) },
|
|
15275
16678
|
{ method: "GET", pattern: /^\/api\/cli\/debug\/([^/]+)$/, handler: (q, s, p) => this.handleCliDebug(p[0], q, s) },
|
|
16679
|
+
{ method: "GET", pattern: /^\/api\/cli\/trace\/([^/]+)$/, handler: (q, s, p) => this.handleCliTrace(p[0], q, s) },
|
|
16680
|
+
{ method: "GET", pattern: /^\/api\/cli\/fixtures\/([^/]+)$/, handler: (q, s, p) => this.handleCliFixtureList(p[0], q, s) },
|
|
15276
16681
|
// Dynamic routes (provider :type param)
|
|
15277
16682
|
{ method: "POST", pattern: /^\/api\/providers\/([^/]+)\/script$/, handler: (q, s, p) => this.handleRunScript(p[0], q, s) },
|
|
15278
16683
|
{ method: "GET", pattern: /^\/api\/providers\/([^/]+)\/files$/, handler: (q, s, p) => this.handleListFiles(p[0], q, s) },
|
|
@@ -15306,8 +16711,8 @@ var DevServer = class _DevServer {
|
|
|
15306
16711
|
}
|
|
15307
16712
|
getEndpointList() {
|
|
15308
16713
|
return this.routes.map((r) => {
|
|
15309
|
-
const
|
|
15310
|
-
return `${r.method.padEnd(5)} ${
|
|
16714
|
+
const path19 = typeof r.pattern === "string" ? r.pattern : r.pattern.source.replace(/\\\//g, "/").replace(/\(\[.*?\]\+\)/g, ":type").replace(/[\^$]/g, "");
|
|
16715
|
+
return `${r.method.padEnd(5)} ${path19}`;
|
|
15311
16716
|
});
|
|
15312
16717
|
}
|
|
15313
16718
|
async start(port = DEV_SERVER_PORT) {
|
|
@@ -15589,12 +16994,12 @@ var DevServer = class _DevServer {
|
|
|
15589
16994
|
// ─── DevConsole SPA ───
|
|
15590
16995
|
getConsoleDistDir() {
|
|
15591
16996
|
const candidates = [
|
|
15592
|
-
|
|
15593
|
-
|
|
15594
|
-
|
|
16997
|
+
path18.resolve(__dirname, "../../web-devconsole/dist"),
|
|
16998
|
+
path18.resolve(__dirname, "../../../web-devconsole/dist"),
|
|
16999
|
+
path18.join(process.cwd(), "packages/web-devconsole/dist")
|
|
15595
17000
|
];
|
|
15596
17001
|
for (const dir of candidates) {
|
|
15597
|
-
if (
|
|
17002
|
+
if (fs14.existsSync(path18.join(dir, "index.html"))) return dir;
|
|
15598
17003
|
}
|
|
15599
17004
|
return null;
|
|
15600
17005
|
}
|
|
@@ -15604,9 +17009,9 @@ var DevServer = class _DevServer {
|
|
|
15604
17009
|
this.json(res, 500, { error: "DevConsole not found. Run: npm run build -w packages/web-devconsole" });
|
|
15605
17010
|
return;
|
|
15606
17011
|
}
|
|
15607
|
-
const htmlPath =
|
|
17012
|
+
const htmlPath = path18.join(distDir, "index.html");
|
|
15608
17013
|
try {
|
|
15609
|
-
const html =
|
|
17014
|
+
const html = fs14.readFileSync(htmlPath, "utf-8");
|
|
15610
17015
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
15611
17016
|
res.end(html);
|
|
15612
17017
|
} catch (e) {
|
|
@@ -15629,15 +17034,15 @@ var DevServer = class _DevServer {
|
|
|
15629
17034
|
this.json(res, 404, { error: "Not found" });
|
|
15630
17035
|
return;
|
|
15631
17036
|
}
|
|
15632
|
-
const safePath =
|
|
15633
|
-
const filePath =
|
|
17037
|
+
const safePath = path18.normalize(pathname).replace(/^\.\.\//, "");
|
|
17038
|
+
const filePath = path18.join(distDir, safePath);
|
|
15634
17039
|
if (!filePath.startsWith(distDir)) {
|
|
15635
17040
|
this.json(res, 403, { error: "Forbidden" });
|
|
15636
17041
|
return;
|
|
15637
17042
|
}
|
|
15638
17043
|
try {
|
|
15639
|
-
const content =
|
|
15640
|
-
const ext =
|
|
17044
|
+
const content = fs14.readFileSync(filePath);
|
|
17045
|
+
const ext = path18.extname(filePath);
|
|
15641
17046
|
const contentType = _DevServer.MIME_MAP[ext] || "application/octet-stream";
|
|
15642
17047
|
res.writeHead(200, { "Content-Type": contentType, "Cache-Control": "public, max-age=31536000, immutable" });
|
|
15643
17048
|
res.end(content);
|
|
@@ -15745,14 +17150,14 @@ var DevServer = class _DevServer {
|
|
|
15745
17150
|
const files = [];
|
|
15746
17151
|
const scan = (d, prefix) => {
|
|
15747
17152
|
try {
|
|
15748
|
-
for (const entry of
|
|
17153
|
+
for (const entry of fs14.readdirSync(d, { withFileTypes: true })) {
|
|
15749
17154
|
if (entry.name.startsWith(".") || entry.name.endsWith(".bak")) continue;
|
|
15750
17155
|
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
15751
17156
|
if (entry.isDirectory()) {
|
|
15752
17157
|
files.push({ path: rel, size: 0, type: "dir" });
|
|
15753
|
-
scan(
|
|
17158
|
+
scan(path18.join(d, entry.name), rel);
|
|
15754
17159
|
} else {
|
|
15755
|
-
const stat =
|
|
17160
|
+
const stat = fs14.statSync(path18.join(d, entry.name));
|
|
15756
17161
|
files.push({ path: rel, size: stat.size, type: "file" });
|
|
15757
17162
|
}
|
|
15758
17163
|
}
|
|
@@ -15775,16 +17180,16 @@ var DevServer = class _DevServer {
|
|
|
15775
17180
|
this.json(res, 404, { error: `Provider directory not found: ${type}` });
|
|
15776
17181
|
return;
|
|
15777
17182
|
}
|
|
15778
|
-
const fullPath =
|
|
17183
|
+
const fullPath = path18.resolve(dir, path18.normalize(filePath));
|
|
15779
17184
|
if (!fullPath.startsWith(dir)) {
|
|
15780
17185
|
this.json(res, 403, { error: "Forbidden" });
|
|
15781
17186
|
return;
|
|
15782
17187
|
}
|
|
15783
|
-
if (!
|
|
17188
|
+
if (!fs14.existsSync(fullPath) || fs14.statSync(fullPath).isDirectory()) {
|
|
15784
17189
|
this.json(res, 404, { error: `File not found: ${filePath}` });
|
|
15785
17190
|
return;
|
|
15786
17191
|
}
|
|
15787
|
-
const content =
|
|
17192
|
+
const content = fs14.readFileSync(fullPath, "utf-8");
|
|
15788
17193
|
this.json(res, 200, { type, path: filePath, content, lines: content.split("\n").length });
|
|
15789
17194
|
}
|
|
15790
17195
|
/** POST /api/providers/:type/file — write a file { path, content } */
|
|
@@ -15800,15 +17205,15 @@ var DevServer = class _DevServer {
|
|
|
15800
17205
|
this.json(res, 404, { error: `Provider directory not found: ${type}` });
|
|
15801
17206
|
return;
|
|
15802
17207
|
}
|
|
15803
|
-
const fullPath =
|
|
17208
|
+
const fullPath = path18.resolve(dir, path18.normalize(filePath));
|
|
15804
17209
|
if (!fullPath.startsWith(dir)) {
|
|
15805
17210
|
this.json(res, 403, { error: "Forbidden" });
|
|
15806
17211
|
return;
|
|
15807
17212
|
}
|
|
15808
17213
|
try {
|
|
15809
|
-
if (
|
|
15810
|
-
|
|
15811
|
-
|
|
17214
|
+
if (fs14.existsSync(fullPath)) fs14.copyFileSync(fullPath, fullPath + ".bak");
|
|
17215
|
+
fs14.mkdirSync(path18.dirname(fullPath), { recursive: true });
|
|
17216
|
+
fs14.writeFileSync(fullPath, content, "utf-8");
|
|
15812
17217
|
this.log(`File saved: ${fullPath} (${content.length} chars)`);
|
|
15813
17218
|
this.providerLoader.reload();
|
|
15814
17219
|
this.json(res, 200, { saved: true, path: filePath, chars: content.length });
|
|
@@ -15824,9 +17229,9 @@ var DevServer = class _DevServer {
|
|
|
15824
17229
|
return;
|
|
15825
17230
|
}
|
|
15826
17231
|
for (const name of ["scripts.js", "provider.json"]) {
|
|
15827
|
-
const p =
|
|
15828
|
-
if (
|
|
15829
|
-
const source =
|
|
17232
|
+
const p = path18.join(dir, name);
|
|
17233
|
+
if (fs14.existsSync(p)) {
|
|
17234
|
+
const source = fs14.readFileSync(p, "utf-8");
|
|
15830
17235
|
this.json(res, 200, { type, path: p, source, lines: source.split("\n").length });
|
|
15831
17236
|
return;
|
|
15832
17237
|
}
|
|
@@ -15845,11 +17250,11 @@ var DevServer = class _DevServer {
|
|
|
15845
17250
|
this.json(res, 404, { error: `Provider not found: ${type}` });
|
|
15846
17251
|
return;
|
|
15847
17252
|
}
|
|
15848
|
-
const target =
|
|
15849
|
-
const targetPath =
|
|
17253
|
+
const target = fs14.existsSync(path18.join(dir, "scripts.js")) ? "scripts.js" : "provider.json";
|
|
17254
|
+
const targetPath = path18.join(dir, target);
|
|
15850
17255
|
try {
|
|
15851
|
-
if (
|
|
15852
|
-
|
|
17256
|
+
if (fs14.existsSync(targetPath)) fs14.copyFileSync(targetPath, targetPath + ".bak");
|
|
17257
|
+
fs14.writeFileSync(targetPath, source, "utf-8");
|
|
15853
17258
|
this.log(`Saved provider: ${targetPath} (${source.length} chars)`);
|
|
15854
17259
|
this.providerLoader.reload();
|
|
15855
17260
|
this.json(res, 200, { saved: true, path: targetPath, chars: source.length });
|
|
@@ -16006,21 +17411,21 @@ var DevServer = class _DevServer {
|
|
|
16006
17411
|
}
|
|
16007
17412
|
let targetDir;
|
|
16008
17413
|
targetDir = this.providerLoader.getUserProviderDir(category, type);
|
|
16009
|
-
const jsonPath =
|
|
16010
|
-
if (
|
|
17414
|
+
const jsonPath = path18.join(targetDir, "provider.json");
|
|
17415
|
+
if (fs14.existsSync(jsonPath)) {
|
|
16011
17416
|
this.json(res, 409, { error: `Provider already exists at ${targetDir}`, path: targetDir });
|
|
16012
17417
|
return;
|
|
16013
17418
|
}
|
|
16014
17419
|
try {
|
|
16015
17420
|
const result = generateFiles(type, name, category, { cdpPorts, cli, processName, installPath, binary, extensionId, version, osPaths, processNames });
|
|
16016
|
-
|
|
16017
|
-
|
|
17421
|
+
fs14.mkdirSync(targetDir, { recursive: true });
|
|
17422
|
+
fs14.writeFileSync(jsonPath, result["provider.json"], "utf-8");
|
|
16018
17423
|
const createdFiles = ["provider.json"];
|
|
16019
17424
|
if (result.files) {
|
|
16020
17425
|
for (const [relPath, content] of Object.entries(result.files)) {
|
|
16021
|
-
const fullPath =
|
|
16022
|
-
|
|
16023
|
-
|
|
17426
|
+
const fullPath = path18.join(targetDir, relPath);
|
|
17427
|
+
fs14.mkdirSync(path18.dirname(fullPath), { recursive: true });
|
|
17428
|
+
fs14.writeFileSync(fullPath, content, "utf-8");
|
|
16024
17429
|
createdFiles.push(relPath);
|
|
16025
17430
|
}
|
|
16026
17431
|
}
|
|
@@ -16069,45 +17474,45 @@ var DevServer = class _DevServer {
|
|
|
16069
17474
|
}
|
|
16070
17475
|
// ─── Phase 2: Auto-Implement Backend ───
|
|
16071
17476
|
getLatestScriptVersionDir(scriptsDir) {
|
|
16072
|
-
if (!
|
|
16073
|
-
const versions =
|
|
17477
|
+
if (!fs14.existsSync(scriptsDir)) return null;
|
|
17478
|
+
const versions = fs14.readdirSync(scriptsDir).filter((d) => {
|
|
16074
17479
|
try {
|
|
16075
|
-
return
|
|
17480
|
+
return fs14.statSync(path18.join(scriptsDir, d)).isDirectory();
|
|
16076
17481
|
} catch {
|
|
16077
17482
|
return false;
|
|
16078
17483
|
}
|
|
16079
17484
|
}).sort((a, b) => b.localeCompare(a, void 0, { numeric: true, sensitivity: "base" }));
|
|
16080
17485
|
if (versions.length === 0) return null;
|
|
16081
|
-
return
|
|
17486
|
+
return path18.join(scriptsDir, versions[0]);
|
|
16082
17487
|
}
|
|
16083
17488
|
resolveAutoImplWritableProviderDir(category, type, requestedDir) {
|
|
16084
|
-
const canonicalUserDir =
|
|
16085
|
-
const desiredDir = requestedDir ?
|
|
16086
|
-
const upstreamRoot =
|
|
16087
|
-
if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${
|
|
17489
|
+
const canonicalUserDir = path18.resolve(this.providerLoader.getUserProviderDir(category, type));
|
|
17490
|
+
const desiredDir = requestedDir ? path18.resolve(requestedDir) : canonicalUserDir;
|
|
17491
|
+
const upstreamRoot = path18.resolve(this.providerLoader.getUpstreamDir());
|
|
17492
|
+
if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path18.sep}`)) {
|
|
16088
17493
|
return { dir: null, reason: `Refusing to write into upstream provider directory: ${desiredDir}` };
|
|
16089
17494
|
}
|
|
16090
|
-
if (
|
|
17495
|
+
if (path18.basename(desiredDir) !== type) {
|
|
16091
17496
|
return { dir: null, reason: `Requested writable provider directory must end with '${type}': ${desiredDir}` };
|
|
16092
17497
|
}
|
|
16093
17498
|
const sourceDir = this.findProviderDir(type);
|
|
16094
17499
|
if (!sourceDir) {
|
|
16095
17500
|
return { dir: null, reason: `Provider source directory not found for '${type}'` };
|
|
16096
17501
|
}
|
|
16097
|
-
if (!
|
|
16098
|
-
|
|
16099
|
-
|
|
17502
|
+
if (!fs14.existsSync(desiredDir)) {
|
|
17503
|
+
fs14.mkdirSync(path18.dirname(desiredDir), { recursive: true });
|
|
17504
|
+
fs14.cpSync(sourceDir, desiredDir, { recursive: true });
|
|
16100
17505
|
this.log(`Auto-implement writable copy created: ${desiredDir}`);
|
|
16101
17506
|
}
|
|
16102
|
-
const providerJson =
|
|
16103
|
-
if (!
|
|
17507
|
+
const providerJson = path18.join(desiredDir, "provider.json");
|
|
17508
|
+
if (!fs14.existsSync(providerJson)) {
|
|
16104
17509
|
return { dir: null, reason: `provider.json not found in writable provider directory: ${desiredDir}` };
|
|
16105
17510
|
}
|
|
16106
17511
|
try {
|
|
16107
|
-
const providerData = JSON.parse(
|
|
17512
|
+
const providerData = JSON.parse(fs14.readFileSync(providerJson, "utf-8"));
|
|
16108
17513
|
if (providerData.disableUpstream !== true) {
|
|
16109
17514
|
providerData.disableUpstream = true;
|
|
16110
|
-
|
|
17515
|
+
fs14.writeFileSync(providerJson, JSON.stringify(providerData, null, 2));
|
|
16111
17516
|
}
|
|
16112
17517
|
} catch (error) {
|
|
16113
17518
|
return {
|
|
@@ -16147,7 +17552,7 @@ var DevServer = class _DevServer {
|
|
|
16147
17552
|
setMode: "set_mode.js"
|
|
16148
17553
|
};
|
|
16149
17554
|
const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
|
|
16150
|
-
const scriptsDir =
|
|
17555
|
+
const scriptsDir = path18.join(providerDir, "scripts");
|
|
16151
17556
|
const latestScriptsDir = this.getLatestScriptVersionDir(scriptsDir);
|
|
16152
17557
|
if (latestScriptsDir) {
|
|
16153
17558
|
lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
|
|
@@ -16155,10 +17560,10 @@ var DevServer = class _DevServer {
|
|
|
16155
17560
|
lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
|
|
16156
17561
|
lines.push("These are the ONLY files you are allowed to modify. Replace the TODO stubs with working implementations.");
|
|
16157
17562
|
lines.push("");
|
|
16158
|
-
for (const file of
|
|
17563
|
+
for (const file of fs14.readdirSync(latestScriptsDir)) {
|
|
16159
17564
|
if (file.endsWith(".js") && targetFileNames.has(file)) {
|
|
16160
17565
|
try {
|
|
16161
|
-
const content =
|
|
17566
|
+
const content = fs14.readFileSync(path18.join(latestScriptsDir, file), "utf-8");
|
|
16162
17567
|
lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
|
|
16163
17568
|
lines.push("```javascript");
|
|
16164
17569
|
lines.push(content);
|
|
@@ -16168,14 +17573,14 @@ var DevServer = class _DevServer {
|
|
|
16168
17573
|
}
|
|
16169
17574
|
}
|
|
16170
17575
|
}
|
|
16171
|
-
const refFiles =
|
|
17576
|
+
const refFiles = fs14.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
|
|
16172
17577
|
if (refFiles.length > 0) {
|
|
16173
17578
|
lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
|
|
16174
17579
|
lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
|
|
16175
17580
|
lines.push("");
|
|
16176
17581
|
for (const file of refFiles) {
|
|
16177
17582
|
try {
|
|
16178
|
-
const content =
|
|
17583
|
+
const content = fs14.readFileSync(path18.join(latestScriptsDir, file), "utf-8");
|
|
16179
17584
|
lines.push(`### \`${file}\` \u{1F512}`);
|
|
16180
17585
|
lines.push("```javascript");
|
|
16181
17586
|
lines.push(content);
|
|
@@ -16216,11 +17621,11 @@ var DevServer = class _DevServer {
|
|
|
16216
17621
|
lines.push("");
|
|
16217
17622
|
}
|
|
16218
17623
|
}
|
|
16219
|
-
const docsDir =
|
|
17624
|
+
const docsDir = path18.join(providerDir, "../../docs");
|
|
16220
17625
|
const loadGuide = (name) => {
|
|
16221
17626
|
try {
|
|
16222
|
-
const p =
|
|
16223
|
-
if (
|
|
17627
|
+
const p = path18.join(docsDir, name);
|
|
17628
|
+
if (fs14.existsSync(p)) return fs14.readFileSync(p, "utf-8");
|
|
16224
17629
|
} catch {
|
|
16225
17630
|
}
|
|
16226
17631
|
return null;
|
|
@@ -16393,7 +17798,7 @@ var DevServer = class _DevServer {
|
|
|
16393
17798
|
parseApproval: "parse_approval.js"
|
|
16394
17799
|
};
|
|
16395
17800
|
const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
|
|
16396
|
-
const scriptsDir =
|
|
17801
|
+
const scriptsDir = path18.join(providerDir, "scripts");
|
|
16397
17802
|
const latestScriptsDir = this.getLatestScriptVersionDir(scriptsDir);
|
|
16398
17803
|
if (latestScriptsDir) {
|
|
16399
17804
|
lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
|
|
@@ -16401,11 +17806,11 @@ var DevServer = class _DevServer {
|
|
|
16401
17806
|
lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
|
|
16402
17807
|
lines.push("These are the ONLY files you are allowed to modify. Replace TODO or heuristic-only logic with working PTY-aware implementations.");
|
|
16403
17808
|
lines.push("");
|
|
16404
|
-
for (const file of
|
|
17809
|
+
for (const file of fs14.readdirSync(latestScriptsDir)) {
|
|
16405
17810
|
if (!file.endsWith(".js")) continue;
|
|
16406
17811
|
if (!targetFileNames.has(file)) continue;
|
|
16407
17812
|
try {
|
|
16408
|
-
const content =
|
|
17813
|
+
const content = fs14.readFileSync(path18.join(latestScriptsDir, file), "utf-8");
|
|
16409
17814
|
lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
|
|
16410
17815
|
lines.push("```javascript");
|
|
16411
17816
|
lines.push(content);
|
|
@@ -16414,14 +17819,14 @@ var DevServer = class _DevServer {
|
|
|
16414
17819
|
} catch {
|
|
16415
17820
|
}
|
|
16416
17821
|
}
|
|
16417
|
-
const refFiles =
|
|
17822
|
+
const refFiles = fs14.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
|
|
16418
17823
|
if (refFiles.length > 0) {
|
|
16419
17824
|
lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
|
|
16420
17825
|
lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
|
|
16421
17826
|
lines.push("");
|
|
16422
17827
|
for (const file of refFiles) {
|
|
16423
17828
|
try {
|
|
16424
|
-
const content =
|
|
17829
|
+
const content = fs14.readFileSync(path18.join(latestScriptsDir, file), "utf-8");
|
|
16425
17830
|
lines.push(`### \`${file}\` \u{1F512}`);
|
|
16426
17831
|
lines.push("```javascript");
|
|
16427
17832
|
lines.push(content);
|
|
@@ -16454,11 +17859,11 @@ var DevServer = class _DevServer {
|
|
|
16454
17859
|
lines.push("");
|
|
16455
17860
|
}
|
|
16456
17861
|
}
|
|
16457
|
-
const docsDir =
|
|
17862
|
+
const docsDir = path18.join(providerDir, "../../docs");
|
|
16458
17863
|
const loadGuide = (name) => {
|
|
16459
17864
|
try {
|
|
16460
|
-
const p =
|
|
16461
|
-
if (
|
|
17865
|
+
const p = path18.join(docsDir, name);
|
|
17866
|
+
if (fs14.existsSync(p)) return fs14.readFileSync(p, "utf-8");
|
|
16462
17867
|
} catch {
|
|
16463
17868
|
}
|
|
16464
17869
|
return null;
|
|
@@ -16523,6 +17928,7 @@ var DevServer = class _DevServer {
|
|
|
16523
17928
|
lines.push("### 2. Inspect parsed + raw adapter state");
|
|
16524
17929
|
lines.push("```bash");
|
|
16525
17930
|
lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/debug/${type}`);
|
|
17931
|
+
lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/trace/${type}`);
|
|
16526
17932
|
lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/status`);
|
|
16527
17933
|
lines.push("```");
|
|
16528
17934
|
lines.push("");
|
|
@@ -16657,6 +18063,16 @@ data: ${JSON.stringify(msg.data)}
|
|
|
16657
18063
|
async handleCliSend(req, res) {
|
|
16658
18064
|
return handleCliSend(this, req, res);
|
|
16659
18065
|
}
|
|
18066
|
+
/** POST /api/cli/exercise — launch/send/approve/wait helper for provider-fix loops */
|
|
18067
|
+
async handleCliExercise(req, res) {
|
|
18068
|
+
return handleCliExercise(this, req, res);
|
|
18069
|
+
}
|
|
18070
|
+
async handleCliFixtureCapture(req, res) {
|
|
18071
|
+
return handleCliFixtureCapture(this, req, res);
|
|
18072
|
+
}
|
|
18073
|
+
async handleCliFixtureReplay(req, res) {
|
|
18074
|
+
return handleCliFixtureReplay(this, req, res);
|
|
18075
|
+
}
|
|
16660
18076
|
/** POST /api/cli/stop — stop a running CLI { type } */
|
|
16661
18077
|
async handleCliStop(req, res) {
|
|
16662
18078
|
return handleCliStop(this, req, res);
|
|
@@ -16680,6 +18096,13 @@ data: ${JSON.stringify(msg.data)}
|
|
|
16680
18096
|
async handleCliDebug(type, _req, res) {
|
|
16681
18097
|
return handleCliDebug(this, type, _req, res);
|
|
16682
18098
|
}
|
|
18099
|
+
/** GET /api/cli/trace/:type — recent CLI trace timeline plus current debug snapshot */
|
|
18100
|
+
async handleCliTrace(type, _req, res) {
|
|
18101
|
+
return handleCliTrace(this, type, _req, res);
|
|
18102
|
+
}
|
|
18103
|
+
async handleCliFixtureList(type, _req, res) {
|
|
18104
|
+
return handleCliFixtureList(this, type, _req, res);
|
|
18105
|
+
}
|
|
16683
18106
|
/** POST /api/cli/resolve — resolve an approval modal { type, buttonIndex } */
|
|
16684
18107
|
async handleCliResolve(req, res) {
|
|
16685
18108
|
return handleCliResolve(this, req, res);
|
|
@@ -16852,7 +18275,18 @@ var SessionHostRuntimeTransport = class {
|
|
|
16852
18275
|
});
|
|
16853
18276
|
}
|
|
16854
18277
|
async boot() {
|
|
16855
|
-
|
|
18278
|
+
if (typeof this.options.ensureReady === "function") {
|
|
18279
|
+
await this.options.ensureReady();
|
|
18280
|
+
}
|
|
18281
|
+
try {
|
|
18282
|
+
await this.client.connect();
|
|
18283
|
+
} catch (error) {
|
|
18284
|
+
if (typeof this.options.ensureReady !== "function") {
|
|
18285
|
+
throw error;
|
|
18286
|
+
}
|
|
18287
|
+
await this.options.ensureReady();
|
|
18288
|
+
await this.client.connect();
|
|
18289
|
+
}
|
|
16856
18290
|
this.unsubscribe = this.client.onEvent((event) => this.handleEvent(event));
|
|
16857
18291
|
let record = null;
|
|
16858
18292
|
if (this.options.attachExisting) {
|
|
@@ -17234,8 +18668,8 @@ async function installExtension(ide, extension) {
|
|
|
17234
18668
|
const res = await fetch(extension.vsixUrl);
|
|
17235
18669
|
if (res.ok) {
|
|
17236
18670
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
17237
|
-
const
|
|
17238
|
-
|
|
18671
|
+
const fs15 = await import("fs");
|
|
18672
|
+
fs15.writeFileSync(vsixPath, buffer);
|
|
17239
18673
|
return new Promise((resolve10) => {
|
|
17240
18674
|
const cmd = `"${ide.cliCommand}" --install-extension "${vsixPath}" --force`;
|
|
17241
18675
|
exec2(cmd, { timeout: 6e4 }, (error, _stdout, stderr) => {
|