@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.js
CHANGED
|
@@ -754,10 +754,10 @@ __export(provider_cli_adapter_exports, {
|
|
|
754
754
|
normalizeCliProviderForRuntime: () => normalizeCliProviderForRuntime
|
|
755
755
|
});
|
|
756
756
|
function stripAnsi(str) {
|
|
757
|
-
return str.replace(/\x1B\[
|
|
757
|
+
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, " ");
|
|
758
758
|
}
|
|
759
759
|
function stripTerminalNoise(str) {
|
|
760
|
-
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, " ");
|
|
760
|
+
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, " ");
|
|
761
761
|
}
|
|
762
762
|
function sanitizeTerminalText(str) {
|
|
763
763
|
return stripTerminalNoise(stripAnsi(str));
|
|
@@ -800,12 +800,12 @@ function findBinary(name) {
|
|
|
800
800
|
function isScriptBinary(binaryPath) {
|
|
801
801
|
if (!path7.isAbsolute(binaryPath)) return false;
|
|
802
802
|
try {
|
|
803
|
-
const
|
|
804
|
-
const resolved =
|
|
803
|
+
const fs15 = require("fs");
|
|
804
|
+
const resolved = fs15.realpathSync(binaryPath);
|
|
805
805
|
const head = Buffer.alloc(8);
|
|
806
|
-
const fd =
|
|
807
|
-
|
|
808
|
-
|
|
806
|
+
const fd = fs15.openSync(resolved, "r");
|
|
807
|
+
fs15.readSync(fd, head, 0, 8, 0);
|
|
808
|
+
fs15.closeSync(fd);
|
|
809
809
|
let i = 0;
|
|
810
810
|
if (head[0] === 239 && head[1] === 187 && head[2] === 191) i = 3;
|
|
811
811
|
return head[i] === 35 && head[i + 1] === 33;
|
|
@@ -816,12 +816,12 @@ function isScriptBinary(binaryPath) {
|
|
|
816
816
|
function looksLikeMachOOrElf(filePath) {
|
|
817
817
|
if (!path7.isAbsolute(filePath)) return false;
|
|
818
818
|
try {
|
|
819
|
-
const
|
|
820
|
-
const resolved =
|
|
819
|
+
const fs15 = require("fs");
|
|
820
|
+
const resolved = fs15.realpathSync(filePath);
|
|
821
821
|
const buf = Buffer.alloc(8);
|
|
822
|
-
const fd =
|
|
823
|
-
|
|
824
|
-
|
|
822
|
+
const fd = fs15.openSync(resolved, "r");
|
|
823
|
+
fs15.readSync(fd, buf, 0, 8, 0);
|
|
824
|
+
fs15.closeSync(fd);
|
|
825
825
|
let i = 0;
|
|
826
826
|
if (buf[0] === 239 && buf[1] === 187 && buf[2] === 191) i = 3;
|
|
827
827
|
const b = buf.subarray(i);
|
|
@@ -874,6 +874,9 @@ function promptLikelyVisible(screenText, promptSnippet) {
|
|
|
874
874
|
).length;
|
|
875
875
|
return matched >= required;
|
|
876
876
|
}
|
|
877
|
+
function normalizeScreenSnapshot(text) {
|
|
878
|
+
return sanitizeTerminalText(String(text || "")).replace(/\s+/g, " ").trim();
|
|
879
|
+
}
|
|
877
880
|
function parsePatternEntry(x) {
|
|
878
881
|
if (x instanceof RegExp) return x;
|
|
879
882
|
if (x && typeof x === "object" && typeof x.source === "string") {
|
|
@@ -912,14 +915,14 @@ var init_provider_cli_adapter = __esm({
|
|
|
912
915
|
pty2 = require("node-pty");
|
|
913
916
|
if (os8.platform() !== "win32") {
|
|
914
917
|
try {
|
|
915
|
-
const
|
|
918
|
+
const fs15 = require("fs");
|
|
916
919
|
const ptyDir = path7.resolve(path7.dirname(require.resolve("node-pty")), "..");
|
|
917
920
|
const platformArch = `${os8.platform()}-${os8.arch()}`;
|
|
918
921
|
const helper = path7.join(ptyDir, "prebuilds", platformArch, "spawn-helper");
|
|
919
|
-
if (
|
|
920
|
-
const stat =
|
|
922
|
+
if (fs15.existsSync(helper)) {
|
|
923
|
+
const stat = fs15.statSync(helper);
|
|
921
924
|
if (!(stat.mode & 73)) {
|
|
922
|
-
|
|
925
|
+
fs15.chmodSync(helper, stat.mode | 493);
|
|
923
926
|
LOG.info("CLI", "[node-pty] Fixed spawn-helper permissions");
|
|
924
927
|
}
|
|
925
928
|
}
|
|
@@ -953,10 +956,25 @@ var init_provider_cli_adapter = __esm({
|
|
|
953
956
|
this.sendDelayMs = typeof provider.sendDelayMs === "number" ? Math.max(0, provider.sendDelayMs) : 0;
|
|
954
957
|
this.sendKey = typeof provider.sendKey === "string" && provider.sendKey.length > 0 ? provider.sendKey : "\r";
|
|
955
958
|
this.submitStrategy = provider.submitStrategy === "immediate" ? "immediate" : "wait_for_echo";
|
|
959
|
+
this.providerResolutionMeta = {
|
|
960
|
+
type: provider.type,
|
|
961
|
+
name: provider.name,
|
|
962
|
+
resolvedVersion: provider._resolvedVersion || null,
|
|
963
|
+
resolvedOs: provider._resolvedOs || null,
|
|
964
|
+
providerDir: provider._resolvedProviderDir || null,
|
|
965
|
+
scriptDir: provider._resolvedScriptDir || null,
|
|
966
|
+
scriptsPath: provider._resolvedScriptsPath || null,
|
|
967
|
+
scriptsSource: provider._resolvedScriptsSource || null,
|
|
968
|
+
versionWarning: provider._versionWarning || null
|
|
969
|
+
};
|
|
956
970
|
this.cliScripts = provider.scripts || {};
|
|
957
971
|
const scriptNames = Object.keys(this.cliScripts).filter((k) => typeof this.cliScripts[k] === "function");
|
|
958
972
|
if (scriptNames.length > 0) {
|
|
959
973
|
LOG.info("CLI", `[${this.cliType}] CLI scripts: [${scriptNames.join(", ")}]`);
|
|
974
|
+
LOG.info(
|
|
975
|
+
"CLI",
|
|
976
|
+
`[${this.cliType}] Provider resolution: providerDir=${this.providerResolutionMeta.providerDir || "-"} scriptDir=${this.providerResolutionMeta.scriptDir || "-"} scriptsPath=${this.providerResolutionMeta.scriptsPath || "-"} source=${this.providerResolutionMeta.scriptsSource || "-"} version=${this.providerResolutionMeta.resolvedVersion || "-"}`
|
|
977
|
+
);
|
|
960
978
|
} else {
|
|
961
979
|
LOG.warn("CLI", `[${this.cliType}] \u26A0 No CLI scripts loaded! Provider needs scripts/{version}/scripts.js`);
|
|
962
980
|
}
|
|
@@ -989,6 +1007,10 @@ var init_provider_cli_adapter = __esm({
|
|
|
989
1007
|
ptyOutputBuffer = "";
|
|
990
1008
|
ptyOutputFlushTimer = null;
|
|
991
1009
|
pendingTerminalQueryTail = "";
|
|
1010
|
+
lastOutputAt = 0;
|
|
1011
|
+
lastNonEmptyOutputAt = 0;
|
|
1012
|
+
lastScreenChangeAt = 0;
|
|
1013
|
+
lastScreenSnapshot = "";
|
|
992
1014
|
// Server log forwarding
|
|
993
1015
|
serverConn = null;
|
|
994
1016
|
logBuffer = [];
|
|
@@ -1009,6 +1031,7 @@ var init_provider_cli_adapter = __esm({
|
|
|
1009
1031
|
submitRetryTimer = null;
|
|
1010
1032
|
submitRetryUsed = false;
|
|
1011
1033
|
submitRetryPromptSnippet = "";
|
|
1034
|
+
idleFinishCandidate = null;
|
|
1012
1035
|
// Resize redraw suppression
|
|
1013
1036
|
resizeSuppressUntil = 0;
|
|
1014
1037
|
// Debug: status transition history
|
|
@@ -1024,6 +1047,12 @@ var init_provider_cli_adapter = __esm({
|
|
|
1024
1047
|
/** Max accumulated buffer size (last 50KB) */
|
|
1025
1048
|
static MAX_ACCUMULATED_BUFFER = 5e4;
|
|
1026
1049
|
currentTurnScope = null;
|
|
1050
|
+
traceEntries = [];
|
|
1051
|
+
traceSeq = 0;
|
|
1052
|
+
traceSessionId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1053
|
+
static MAX_TRACE_ENTRIES = 250;
|
|
1054
|
+
providerResolutionMeta;
|
|
1055
|
+
static IDLE_FINISH_CONFIRM_MS = 900;
|
|
1027
1056
|
syncMessageViews() {
|
|
1028
1057
|
this.messages = [...this.committedMessages];
|
|
1029
1058
|
this.structuredMessages = [...this.committedMessages];
|
|
@@ -1059,8 +1088,89 @@ var init_provider_cli_adapter = __esm({
|
|
|
1059
1088
|
this.currentStatus = status;
|
|
1060
1089
|
this.statusHistory.push({ status, at: Date.now(), trigger });
|
|
1061
1090
|
if (this.statusHistory.length > 50) this.statusHistory.shift();
|
|
1091
|
+
this.recordTrace("status", {
|
|
1092
|
+
previousStatus: prev,
|
|
1093
|
+
trigger: trigger || null
|
|
1094
|
+
});
|
|
1062
1095
|
LOG.info("CLI", `[${this.cliType}] status: ${prev} \u2192 ${status}${trigger ? ` (${trigger})` : ""}`);
|
|
1063
1096
|
}
|
|
1097
|
+
clearIdleFinishCandidate(reason) {
|
|
1098
|
+
if (!this.idleFinishCandidate) return;
|
|
1099
|
+
this.recordTrace("idle_candidate_reset", {
|
|
1100
|
+
reason,
|
|
1101
|
+
candidate: this.idleFinishCandidate
|
|
1102
|
+
});
|
|
1103
|
+
this.idleFinishCandidate = null;
|
|
1104
|
+
}
|
|
1105
|
+
armIdleFinishCandidate(assistantLength) {
|
|
1106
|
+
const now = Date.now();
|
|
1107
|
+
this.idleFinishCandidate = {
|
|
1108
|
+
armedAt: now,
|
|
1109
|
+
lastOutputAt: this.lastOutputAt,
|
|
1110
|
+
lastScreenChangeAt: this.lastScreenChangeAt,
|
|
1111
|
+
responseEpoch: this.responseEpoch,
|
|
1112
|
+
assistantLength
|
|
1113
|
+
};
|
|
1114
|
+
this.recordTrace("idle_candidate_armed", {
|
|
1115
|
+
confirmMs: _ProviderCliAdapter.IDLE_FINISH_CONFIRM_MS,
|
|
1116
|
+
candidate: this.idleFinishCandidate,
|
|
1117
|
+
...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
|
|
1118
|
+
});
|
|
1119
|
+
if (this.settleTimer) clearTimeout(this.settleTimer);
|
|
1120
|
+
this.settleTimer = setTimeout(() => {
|
|
1121
|
+
this.settleTimer = null;
|
|
1122
|
+
this.settledBuffer = this.recentOutputBuffer;
|
|
1123
|
+
this.evaluateSettled();
|
|
1124
|
+
}, _ProviderCliAdapter.IDLE_FINISH_CONFIRM_MS);
|
|
1125
|
+
}
|
|
1126
|
+
summarizeTraceText(text, max = 800) {
|
|
1127
|
+
const value = sanitizeTerminalText(String(text || ""));
|
|
1128
|
+
if (value.length <= max) return value;
|
|
1129
|
+
return `\u2026${value.slice(-max)}`;
|
|
1130
|
+
}
|
|
1131
|
+
summarizeTraceMessages(messages, limit = 3) {
|
|
1132
|
+
return messages.slice(-limit).map((message) => ({
|
|
1133
|
+
role: message.role,
|
|
1134
|
+
content: this.summarizeTraceText(message.content, 240),
|
|
1135
|
+
timestamp: message.timestamp
|
|
1136
|
+
}));
|
|
1137
|
+
}
|
|
1138
|
+
buildTraceParseSnapshot(scope, partialResponse = "") {
|
|
1139
|
+
const scopedBuffer = scope ? this.sliceFromOffset(this.accumulatedBuffer, scope.bufferStart) || this.accumulatedBuffer : this.accumulatedBuffer;
|
|
1140
|
+
const scopedRawBuffer = scope ? this.sliceFromOffset(this.accumulatedRawBuffer, scope.rawBufferStart) || this.accumulatedRawBuffer : this.accumulatedRawBuffer;
|
|
1141
|
+
return {
|
|
1142
|
+
currentTurnScope: scope || null,
|
|
1143
|
+
responseBuffer: this.summarizeTraceText(this.responseBuffer, 1200),
|
|
1144
|
+
partialResponse: this.summarizeTraceText(partialResponse || this.responseBuffer, 1200),
|
|
1145
|
+
turnBuffer: this.summarizeTraceText(scopedBuffer, 1600),
|
|
1146
|
+
turnRawPreview: this.summarizeTraceText(scopedRawBuffer, 1600),
|
|
1147
|
+
turnSanitizedRawPreview: this.summarizeTraceText(sanitizeTerminalText(scopedRawBuffer), 1600)
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
recordTrace(type, payload = {}) {
|
|
1151
|
+
const entry = {
|
|
1152
|
+
id: ++this.traceSeq,
|
|
1153
|
+
at: Date.now(),
|
|
1154
|
+
type,
|
|
1155
|
+
status: this.currentStatus,
|
|
1156
|
+
isWaitingForResponse: this.isWaitingForResponse,
|
|
1157
|
+
activeModal: this.activeModal ? { message: this.activeModal.message, buttons: [...this.activeModal.buttons] } : null,
|
|
1158
|
+
payload
|
|
1159
|
+
};
|
|
1160
|
+
this.traceEntries.push(entry);
|
|
1161
|
+
if (this.traceEntries.length > _ProviderCliAdapter.MAX_TRACE_ENTRIES) {
|
|
1162
|
+
this.traceEntries.splice(0, this.traceEntries.length - _ProviderCliAdapter.MAX_TRACE_ENTRIES);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
resetTraceSession() {
|
|
1166
|
+
this.traceEntries = [];
|
|
1167
|
+
this.traceSeq = 0;
|
|
1168
|
+
this.traceSessionId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1169
|
+
this.recordTrace("session_start", {
|
|
1170
|
+
providerType: this.cliType,
|
|
1171
|
+
workingDir: this.workingDir
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1064
1174
|
// Resolved timeouts
|
|
1065
1175
|
timeouts;
|
|
1066
1176
|
// Provider approval key mapping
|
|
@@ -1106,6 +1216,7 @@ var init_provider_cli_adapter = __esm({
|
|
|
1106
1216
|
const isWin = os8.platform() === "win32";
|
|
1107
1217
|
const allArgs = [...spawnConfig.args, ...this.extraArgs];
|
|
1108
1218
|
LOG.info("CLI", `[${this.cliType}] Spawning in ${this.workingDir}`);
|
|
1219
|
+
this.resetTraceSession();
|
|
1109
1220
|
let shellCmd;
|
|
1110
1221
|
let shellArgs;
|
|
1111
1222
|
const useShellUnix = !isWin && (!!spawnConfig.shell || !path7.isAbsolute(binaryPath) || isScriptBinary(binaryPath) || !looksLikeMachOOrElf(binaryPath));
|
|
@@ -1131,6 +1242,14 @@ var init_provider_cli_adapter = __esm({
|
|
|
1131
1242
|
cwd: this.workingDir,
|
|
1132
1243
|
env: buildCliSpawnEnv(process.env, spawnConfig.env)
|
|
1133
1244
|
};
|
|
1245
|
+
this.recordTrace("spawn", {
|
|
1246
|
+
shellCommand: shellCmd,
|
|
1247
|
+
shellArgs,
|
|
1248
|
+
cwd: ptyOpts.cwd,
|
|
1249
|
+
cols: ptyOpts.cols,
|
|
1250
|
+
rows: ptyOpts.rows,
|
|
1251
|
+
providerResolution: this.providerResolutionMeta
|
|
1252
|
+
});
|
|
1134
1253
|
try {
|
|
1135
1254
|
this.ptyProcess = this.transportFactory.spawn(shellCmd, shellArgs, ptyOpts);
|
|
1136
1255
|
} catch (err) {
|
|
@@ -1173,6 +1292,7 @@ var init_provider_cli_adapter = __esm({
|
|
|
1173
1292
|
this.ptyProcess.onExit(({ exitCode }) => {
|
|
1174
1293
|
LOG.info("CLI", `[${this.cliType}] Exit code ${exitCode}`);
|
|
1175
1294
|
this.flushPendingOutputParse();
|
|
1295
|
+
this.recordTrace("exit", { exitCode });
|
|
1176
1296
|
this.ptyProcess = null;
|
|
1177
1297
|
this.setStatus("stopped", "pty_exit");
|
|
1178
1298
|
this.ready = false;
|
|
@@ -1188,6 +1308,9 @@ var init_provider_cli_adapter = __esm({
|
|
|
1188
1308
|
this.currentTurnScope = null;
|
|
1189
1309
|
this.ready = false;
|
|
1190
1310
|
await this.ptyProcess.ready;
|
|
1311
|
+
this.recordTrace("ready", {
|
|
1312
|
+
runtimeMeta: this.getRuntimeMetadata()
|
|
1313
|
+
});
|
|
1191
1314
|
this.setStatus("idle", "pty_ready");
|
|
1192
1315
|
this.onStatusChange?.();
|
|
1193
1316
|
}
|
|
@@ -1195,6 +1318,24 @@ var init_provider_cli_adapter = __esm({
|
|
|
1195
1318
|
handleOutput(rawData) {
|
|
1196
1319
|
this.terminalScreen.write(rawData);
|
|
1197
1320
|
const cleanData = sanitizeTerminalText(rawData);
|
|
1321
|
+
const now = Date.now();
|
|
1322
|
+
const normalizedScreenSnapshot = normalizeScreenSnapshot(this.terminalScreen.getText());
|
|
1323
|
+
this.lastOutputAt = now;
|
|
1324
|
+
if (cleanData.trim()) this.lastNonEmptyOutputAt = now;
|
|
1325
|
+
if (normalizedScreenSnapshot !== this.lastScreenSnapshot) {
|
|
1326
|
+
this.lastScreenSnapshot = normalizedScreenSnapshot;
|
|
1327
|
+
this.lastScreenChangeAt = now;
|
|
1328
|
+
}
|
|
1329
|
+
if (this.idleFinishCandidate && (rawData.length > 0 || cleanData.length > 0)) {
|
|
1330
|
+
this.clearIdleFinishCandidate("new_output");
|
|
1331
|
+
}
|
|
1332
|
+
this.recordTrace("output", {
|
|
1333
|
+
rawLength: rawData.length,
|
|
1334
|
+
cleanLength: cleanData.length,
|
|
1335
|
+
rawPreview: this.summarizeTraceText(rawData, 300),
|
|
1336
|
+
cleanPreview: this.summarizeTraceText(cleanData, 300),
|
|
1337
|
+
screenText: this.summarizeTraceText(this.terminalScreen.getText(), 1200)
|
|
1338
|
+
});
|
|
1198
1339
|
if (this.isWaitingForResponse && cleanData) {
|
|
1199
1340
|
this.responseBuffer = (this.responseBuffer + cleanData).slice(-8e3);
|
|
1200
1341
|
}
|
|
@@ -1212,11 +1353,17 @@ var init_provider_cli_adapter = __esm({
|
|
|
1212
1353
|
this.startupBuffer += cleanData;
|
|
1213
1354
|
const elapsed = Date.now() - this.spawnAt;
|
|
1214
1355
|
const scriptStatus = this.runDetectStatus(this.startupBuffer);
|
|
1215
|
-
const
|
|
1356
|
+
const screenText = this.terminalScreen.getText() || "";
|
|
1357
|
+
const hasInteractivePrompt = this.looksLikeVisibleIdlePrompt(screenText);
|
|
1358
|
+
const startupStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
|
|
1359
|
+
const isReady = (scriptStatus === "idle" || scriptStatus === "waiting_approval") && hasInteractivePrompt && startupStableMs >= 700 || elapsed > 8e3 || this.startupBuffer.length > 12e3;
|
|
1216
1360
|
if (isReady) {
|
|
1217
1361
|
this.startupParseGate = false;
|
|
1218
1362
|
this.ready = true;
|
|
1219
|
-
LOG.info(
|
|
1363
|
+
LOG.info(
|
|
1364
|
+
"CLI",
|
|
1365
|
+
`[${this.cliType}] Startup ready (${elapsed}ms, scriptStatus=${scriptStatus}, prompt=${hasInteractivePrompt}, stableMs=${startupStableMs}) providerDir=${this.providerResolutionMeta.providerDir || "-"} scriptDir=${this.providerResolutionMeta.scriptDir || "-"} scriptsPath=${this.providerResolutionMeta.scriptsPath || "-"}`
|
|
1366
|
+
);
|
|
1220
1367
|
this.onStatusChange?.();
|
|
1221
1368
|
}
|
|
1222
1369
|
}
|
|
@@ -1261,6 +1408,41 @@ var init_provider_cli_adapter = __esm({
|
|
|
1261
1408
|
if (!text.trim()) return false;
|
|
1262
1409
|
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);
|
|
1263
1410
|
}
|
|
1411
|
+
async waitForInteractivePrompt(maxWaitMs = 5e3) {
|
|
1412
|
+
const startedAt = Date.now();
|
|
1413
|
+
let loggedWait = false;
|
|
1414
|
+
while (Date.now() - startedAt < maxWaitMs) {
|
|
1415
|
+
const screenText = this.terminalScreen.getText() || "";
|
|
1416
|
+
const hasPrompt = this.looksLikeVisibleIdlePrompt(screenText);
|
|
1417
|
+
const stableMs = this.lastScreenChangeAt ? Date.now() - this.lastScreenChangeAt : 0;
|
|
1418
|
+
const recentlyOutput = this.lastNonEmptyOutputAt ? Date.now() - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
|
|
1419
|
+
const status = this.runDetectStatus(this.recentOutputBuffer) || this.currentStatus;
|
|
1420
|
+
const startupLikelyActive = /Welcome back|Tips for getting|Recent activity|Claude Code v\d/i.test(screenText);
|
|
1421
|
+
const interactiveReady = hasPrompt && stableMs >= 700 && recentlyOutput >= 350 && status !== "starting" && status !== "generating";
|
|
1422
|
+
if (interactiveReady) {
|
|
1423
|
+
if (loggedWait) {
|
|
1424
|
+
LOG.info(
|
|
1425
|
+
"CLI",
|
|
1426
|
+
`[${this.cliType}] Interactive prompt ready after ${Date.now() - startedAt}ms (stableMs=${stableMs}, recentOutputMs=${recentlyOutput}, startup=${startupLikelyActive})`
|
|
1427
|
+
);
|
|
1428
|
+
}
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
if (!loggedWait && Date.now() - startedAt >= 400) {
|
|
1432
|
+
loggedWait = true;
|
|
1433
|
+
LOG.info(
|
|
1434
|
+
"CLI",
|
|
1435
|
+
`[${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)}`
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
await new Promise((resolve10) => setTimeout(resolve10, 50));
|
|
1439
|
+
}
|
|
1440
|
+
const finalScreenText = this.terminalScreen.getText() || "";
|
|
1441
|
+
LOG.warn(
|
|
1442
|
+
"CLI",
|
|
1443
|
+
`[${this.cliType}] Interactive prompt wait timed out after ${maxWaitMs}ms; proceeding with screen=${JSON.stringify(this.summarizeTraceText(finalScreenText, 240)).slice(0, 280)}`
|
|
1444
|
+
);
|
|
1445
|
+
}
|
|
1264
1446
|
evaluateSettled() {
|
|
1265
1447
|
const now = Date.now();
|
|
1266
1448
|
if (this.submitPendingUntil > now || this.responseSettleIgnoreUntil > now) {
|
|
@@ -1278,6 +1460,30 @@ var init_provider_cli_adapter = __esm({
|
|
|
1278
1460
|
const modal = this.runParseApproval(tail);
|
|
1279
1461
|
const rawScriptStatus = this.runDetectStatus(tail);
|
|
1280
1462
|
const scriptStatus = rawScriptStatus;
|
|
1463
|
+
const parsedTranscript = this.parseCurrentTranscript(
|
|
1464
|
+
this.committedMessages,
|
|
1465
|
+
this.responseBuffer,
|
|
1466
|
+
this.currentTurnScope
|
|
1467
|
+
);
|
|
1468
|
+
const parsedMessages = Array.isArray(parsedTranscript?.messages) ? this.normalizeParsedMessages(parsedTranscript.messages) : [];
|
|
1469
|
+
const lastParsedAssistant = [...parsedMessages].reverse().find((message) => message.role === "assistant");
|
|
1470
|
+
this.recordTrace("settled", {
|
|
1471
|
+
tail: this.summarizeTraceText(tail, 500),
|
|
1472
|
+
screenText: this.summarizeTraceText(screenText, 1200),
|
|
1473
|
+
detectStatus: scriptStatus,
|
|
1474
|
+
parsedStatus: parsedTranscript?.status || null,
|
|
1475
|
+
parsedMessageCount: parsedMessages.length,
|
|
1476
|
+
parsedLastAssistant: lastParsedAssistant ? this.summarizeTraceText(lastParsedAssistant.content, 280) : "",
|
|
1477
|
+
parsedActiveModal: parsedTranscript?.activeModal ?? null,
|
|
1478
|
+
approval: modal,
|
|
1479
|
+
...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
|
|
1480
|
+
});
|
|
1481
|
+
if (this.currentTurnScope && !lastParsedAssistant) {
|
|
1482
|
+
LOG.info(
|
|
1483
|
+
"CLI",
|
|
1484
|
+
`[${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 || "-"}`
|
|
1485
|
+
);
|
|
1486
|
+
}
|
|
1281
1487
|
if (!scriptStatus) return;
|
|
1282
1488
|
const prevStatus = this.currentStatus;
|
|
1283
1489
|
const clearPendingScriptStatus = () => {
|
|
@@ -1313,6 +1519,7 @@ var init_provider_cli_adapter = __esm({
|
|
|
1313
1519
|
clearPendingScriptStatus();
|
|
1314
1520
|
}
|
|
1315
1521
|
if (scriptStatus === "waiting_approval") {
|
|
1522
|
+
this.clearIdleFinishCandidate("waiting_approval");
|
|
1316
1523
|
const inCooldown = this.lastApprovalResolvedAt && Date.now() - this.lastApprovalResolvedAt < this.timeouts.approvalCooldown;
|
|
1317
1524
|
const visibleIdlePrompt = this.looksLikeVisibleIdlePrompt(screenText);
|
|
1318
1525
|
if ((inCooldown || visibleIdlePrompt) && !modal) {
|
|
@@ -1346,6 +1553,7 @@ var init_provider_cli_adapter = __esm({
|
|
|
1346
1553
|
}
|
|
1347
1554
|
}
|
|
1348
1555
|
if (scriptStatus === "generating") {
|
|
1556
|
+
this.clearIdleFinishCandidate("generating");
|
|
1349
1557
|
const effectiveScreenText = screenText || this.accumulatedBuffer;
|
|
1350
1558
|
const noActiveTurn = !this.currentTurnScope;
|
|
1351
1559
|
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));
|
|
@@ -1382,13 +1590,58 @@ var init_provider_cli_adapter = __esm({
|
|
|
1382
1590
|
this.lastApprovalResolvedAt = Date.now();
|
|
1383
1591
|
}
|
|
1384
1592
|
if (this.isWaitingForResponse) {
|
|
1593
|
+
const visibleIdlePrompt = this.looksLikeVisibleIdlePrompt(screenText);
|
|
1594
|
+
const quietForMs = this.lastNonEmptyOutputAt ? now - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
|
|
1595
|
+
const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
|
|
1596
|
+
const hasAssistantTurn = !!lastParsedAssistant;
|
|
1597
|
+
const assistantLength = lastParsedAssistant?.content?.length || 0;
|
|
1598
|
+
const idleQuietThresholdMs = Math.max(220, this.timeouts.outputSettle);
|
|
1599
|
+
const idleStableThresholdMs = Math.max(120, Math.min(220, this.timeouts.outputSettle));
|
|
1600
|
+
const idleReady = visibleIdlePrompt && !modal && hasAssistantTurn && quietForMs >= idleQuietThresholdMs && screenStableMs >= idleStableThresholdMs;
|
|
1601
|
+
const candidate = this.idleFinishCandidate;
|
|
1602
|
+
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;
|
|
1603
|
+
const canFinishImmediately = idleReady && candidateQuiet;
|
|
1604
|
+
this.recordTrace("idle_decision", {
|
|
1605
|
+
visibleIdlePrompt,
|
|
1606
|
+
quietForMs,
|
|
1607
|
+
screenStableMs,
|
|
1608
|
+
hasAssistantTurn,
|
|
1609
|
+
assistantLength,
|
|
1610
|
+
hasModal: !!modal,
|
|
1611
|
+
idleQuietThresholdMs,
|
|
1612
|
+
idleStableThresholdMs,
|
|
1613
|
+
idleReady,
|
|
1614
|
+
idleFinishConfirmMs: _ProviderCliAdapter.IDLE_FINISH_CONFIRM_MS,
|
|
1615
|
+
idleFinishCandidate: candidate,
|
|
1616
|
+
candidateQuiet,
|
|
1617
|
+
canFinishImmediately,
|
|
1618
|
+
submitPendingUntil: this.submitPendingUntil,
|
|
1619
|
+
responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
|
|
1620
|
+
...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
|
|
1621
|
+
});
|
|
1622
|
+
if (canFinishImmediately) {
|
|
1623
|
+
this.clearIdleFinishCandidate("finish_response");
|
|
1624
|
+
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
1625
|
+
this.finishResponse();
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
if (idleReady) {
|
|
1629
|
+
if (!candidate) {
|
|
1630
|
+
this.armIdleFinishCandidate(assistantLength);
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
} else {
|
|
1634
|
+
this.clearIdleFinishCandidate("idle_not_ready");
|
|
1635
|
+
}
|
|
1385
1636
|
if (this.idleTimeout) clearTimeout(this.idleTimeout);
|
|
1386
1637
|
this.idleTimeout = setTimeout(() => {
|
|
1387
1638
|
if (this.isWaitingForResponse && this.currentStatus !== "waiting_approval") {
|
|
1639
|
+
this.clearIdleFinishCandidate("idle_timeout_finish");
|
|
1388
1640
|
this.finishResponse();
|
|
1389
1641
|
}
|
|
1390
1642
|
}, this.timeouts.idleFinish);
|
|
1391
1643
|
} else if (prevStatus !== "idle") {
|
|
1644
|
+
this.clearIdleFinishCandidate("idle_without_response");
|
|
1392
1645
|
this.setStatus("idle", "script_detect");
|
|
1393
1646
|
this.onStatusChange?.();
|
|
1394
1647
|
}
|
|
@@ -1397,6 +1650,10 @@ var init_provider_cli_adapter = __esm({
|
|
|
1397
1650
|
finishResponse() {
|
|
1398
1651
|
if (this.submitPendingUntil > Date.now()) return;
|
|
1399
1652
|
if (this.responseSettleIgnoreUntil > Date.now()) return;
|
|
1653
|
+
this.clearIdleFinishCandidate("finish_response_enter");
|
|
1654
|
+
this.recordTrace("finish_response", {
|
|
1655
|
+
...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
|
|
1656
|
+
});
|
|
1400
1657
|
this.commitCurrentTranscript();
|
|
1401
1658
|
if (this.responseTimeout) {
|
|
1402
1659
|
clearTimeout(this.responseTimeout);
|
|
@@ -1433,6 +1690,20 @@ var init_provider_cli_adapter = __esm({
|
|
|
1433
1690
|
if (parsed && Array.isArray(parsed.messages)) {
|
|
1434
1691
|
this.committedMessages = this.normalizeParsedMessages(parsed.messages);
|
|
1435
1692
|
this.syncMessageViews();
|
|
1693
|
+
const lastAssistant = [...this.committedMessages].reverse().find((message) => message.role === "assistant");
|
|
1694
|
+
this.recordTrace("commit_transcript", {
|
|
1695
|
+
parsedStatus: parsed.status || null,
|
|
1696
|
+
messageCount: this.committedMessages.length,
|
|
1697
|
+
lastAssistant: lastAssistant ? this.summarizeTraceText(lastAssistant.content, 320) : "",
|
|
1698
|
+
messages: this.summarizeTraceMessages(this.committedMessages),
|
|
1699
|
+
...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
|
|
1700
|
+
});
|
|
1701
|
+
if (!lastAssistant && this.currentTurnScope) {
|
|
1702
|
+
LOG.warn(
|
|
1703
|
+
"CLI",
|
|
1704
|
+
`[${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 || "-"}`
|
|
1705
|
+
);
|
|
1706
|
+
}
|
|
1436
1707
|
}
|
|
1437
1708
|
}
|
|
1438
1709
|
// ─── Script Execution ──────────────────────────
|
|
@@ -1556,16 +1827,23 @@ ${data.message || ""}`.trim();
|
|
|
1556
1827
|
}
|
|
1557
1828
|
if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
|
|
1558
1829
|
if (this.isWaitingForResponse) return;
|
|
1830
|
+
await this.waitForInteractivePrompt();
|
|
1559
1831
|
this.committedMessages.push({ role: "user", content: text, timestamp: Date.now() });
|
|
1560
1832
|
this.syncMessageViews();
|
|
1561
1833
|
this.isWaitingForResponse = true;
|
|
1562
1834
|
this.responseBuffer = "";
|
|
1835
|
+
this.clearIdleFinishCandidate("send_message");
|
|
1563
1836
|
this.currentTurnScope = {
|
|
1564
1837
|
prompt: text,
|
|
1565
1838
|
startedAt: Date.now(),
|
|
1566
1839
|
bufferStart: this.accumulatedBuffer.length,
|
|
1567
1840
|
rawBufferStart: this.accumulatedRawBuffer.length
|
|
1568
1841
|
};
|
|
1842
|
+
this.recordTrace("send_message", {
|
|
1843
|
+
text: this.summarizeTraceText(text, 500),
|
|
1844
|
+
estimatedLines: estimatePromptDisplayLines(text),
|
|
1845
|
+
turnScope: this.currentTurnScope
|
|
1846
|
+
});
|
|
1569
1847
|
LOG.info("CLI", `[${this.cliType}] sendMessage turn scope buffer=${this.currentTurnScope.bufferStart} raw=${this.currentTurnScope.rawBufferStart} prompt=${JSON.stringify(text).slice(0, 120)}`);
|
|
1570
1848
|
this.submitRetryUsed = false;
|
|
1571
1849
|
this.submitRetryPromptSnippet = extractPromptRetrySnippet(text);
|
|
@@ -1595,6 +1873,11 @@ ${data.message || ""}`.trim();
|
|
|
1595
1873
|
const submit = () => {
|
|
1596
1874
|
if (!this.ptyProcess) return;
|
|
1597
1875
|
this.submitPendingUntil = 0;
|
|
1876
|
+
this.recordTrace("submit_write", {
|
|
1877
|
+
mode: "submit_key",
|
|
1878
|
+
sendKey: this.sendKey,
|
|
1879
|
+
screenText: this.summarizeTraceText(this.terminalScreen.getText(), 500)
|
|
1880
|
+
});
|
|
1598
1881
|
this.ptyProcess.write(this.sendKey);
|
|
1599
1882
|
const retrySubmitIfStuck = (attempt) => {
|
|
1600
1883
|
this.submitRetryTimer = null;
|
|
@@ -1606,6 +1889,12 @@ ${data.message || ""}`.trim();
|
|
|
1606
1889
|
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;
|
|
1607
1890
|
this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
1608
1891
|
LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt ${attempt})`);
|
|
1892
|
+
this.recordTrace("submit_write", {
|
|
1893
|
+
mode: "submit_retry",
|
|
1894
|
+
attempt,
|
|
1895
|
+
sendKey: this.sendKey,
|
|
1896
|
+
screenText: this.summarizeTraceText(screenText, 500)
|
|
1897
|
+
});
|
|
1609
1898
|
this.ptyProcess.write(this.sendKey);
|
|
1610
1899
|
if (attempt >= 3) {
|
|
1611
1900
|
this.submitRetryUsed = true;
|
|
@@ -1618,6 +1907,12 @@ ${data.message || ""}`.trim();
|
|
|
1618
1907
|
};
|
|
1619
1908
|
if (this.submitStrategy === "immediate") {
|
|
1620
1909
|
this.submitPendingUntil = 0;
|
|
1910
|
+
this.recordTrace("submit_write", {
|
|
1911
|
+
mode: "immediate",
|
|
1912
|
+
text: this.summarizeTraceText(text, 500),
|
|
1913
|
+
sendKey: this.sendKey,
|
|
1914
|
+
screenText: this.summarizeTraceText(this.terminalScreen.getText(), 500)
|
|
1915
|
+
});
|
|
1621
1916
|
this.ptyProcess.write(text + this.sendKey);
|
|
1622
1917
|
this.submitRetryTimer = setTimeout(() => {
|
|
1623
1918
|
this.submitRetryTimer = null;
|
|
@@ -1628,6 +1923,12 @@ ${data.message || ""}`.trim();
|
|
|
1628
1923
|
if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
|
|
1629
1924
|
LOG.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt 1)`);
|
|
1630
1925
|
this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
1926
|
+
this.recordTrace("submit_write", {
|
|
1927
|
+
mode: "immediate_retry",
|
|
1928
|
+
attempt: 1,
|
|
1929
|
+
sendKey: this.sendKey,
|
|
1930
|
+
screenText: this.summarizeTraceText(screenText, 500)
|
|
1931
|
+
});
|
|
1631
1932
|
this.ptyProcess.write(this.sendKey);
|
|
1632
1933
|
this.submitRetryUsed = true;
|
|
1633
1934
|
}, retryDelayMs);
|
|
@@ -1638,6 +1939,12 @@ ${data.message || ""}`.trim();
|
|
|
1638
1939
|
this.submitPendingUntil = Date.now() + submitDelayMs;
|
|
1639
1940
|
}
|
|
1640
1941
|
this.ptyProcess.write(text);
|
|
1942
|
+
this.recordTrace("submit_write", {
|
|
1943
|
+
mode: "type_then_submit",
|
|
1944
|
+
text: this.summarizeTraceText(text, 500),
|
|
1945
|
+
sendKey: this.sendKey,
|
|
1946
|
+
screenText: this.summarizeTraceText(this.terminalScreen.getText(), 500)
|
|
1947
|
+
});
|
|
1641
1948
|
const submitStartedAt = Date.now();
|
|
1642
1949
|
let lastNormalizedScreen = "";
|
|
1643
1950
|
let lastScreenChangeAt = submitStartedAt;
|
|
@@ -1738,6 +2045,7 @@ ${data.message || ""}`.trim();
|
|
|
1738
2045
|
});
|
|
1739
2046
|
}
|
|
1740
2047
|
shutdown() {
|
|
2048
|
+
this.clearIdleFinishCandidate("shutdown");
|
|
1741
2049
|
if (this.settleTimer) {
|
|
1742
2050
|
clearTimeout(this.settleTimer);
|
|
1743
2051
|
this.settleTimer = null;
|
|
@@ -1778,6 +2086,7 @@ ${data.message || ""}`.trim();
|
|
|
1778
2086
|
}
|
|
1779
2087
|
}
|
|
1780
2088
|
detach() {
|
|
2089
|
+
this.clearIdleFinishCandidate("detach");
|
|
1781
2090
|
if (this.settleTimer) {
|
|
1782
2091
|
clearTimeout(this.settleTimer);
|
|
1783
2092
|
this.settleTimer = null;
|
|
@@ -1818,6 +2127,7 @@ ${data.message || ""}`.trim();
|
|
|
1818
2127
|
this.onStatusChange?.();
|
|
1819
2128
|
}
|
|
1820
2129
|
clearHistory() {
|
|
2130
|
+
this.clearIdleFinishCandidate("clear_history");
|
|
1821
2131
|
this.committedMessages = [];
|
|
1822
2132
|
this.syncMessageViews();
|
|
1823
2133
|
this.accumulatedBuffer = "";
|
|
@@ -1847,10 +2157,19 @@ ${data.message || ""}`.trim();
|
|
|
1847
2157
|
return this.ready;
|
|
1848
2158
|
}
|
|
1849
2159
|
writeRaw(data) {
|
|
2160
|
+
this.recordTrace("write_raw", {
|
|
2161
|
+
keys: JSON.stringify(data),
|
|
2162
|
+
length: data.length
|
|
2163
|
+
});
|
|
1850
2164
|
this.ptyProcess?.write(data);
|
|
1851
2165
|
}
|
|
1852
2166
|
resolveModal(buttonIndex) {
|
|
1853
2167
|
if (!this.ptyProcess || this.currentStatus !== "waiting_approval" && !this.activeModal) return;
|
|
2168
|
+
this.clearIdleFinishCandidate("resolve_modal");
|
|
2169
|
+
this.recordTrace("resolve_modal", {
|
|
2170
|
+
buttonIndex,
|
|
2171
|
+
activeModal: this.activeModal
|
|
2172
|
+
});
|
|
1854
2173
|
this.activeModal = null;
|
|
1855
2174
|
this.lastApprovalResolvedAt = Date.now();
|
|
1856
2175
|
this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
|
|
@@ -1882,6 +2201,7 @@ ${data.message || ""}`.trim();
|
|
|
1882
2201
|
return {
|
|
1883
2202
|
type: this.cliType,
|
|
1884
2203
|
name: this.cliName,
|
|
2204
|
+
providerResolution: this.providerResolutionMeta,
|
|
1885
2205
|
status: this.currentStatus,
|
|
1886
2206
|
ready: this.ready,
|
|
1887
2207
|
startupParseGate: this.startupParseGate,
|
|
@@ -1901,6 +2221,10 @@ ${data.message || ""}`.trim();
|
|
|
1901
2221
|
rawBufferPreview: this.accumulatedRawBuffer.slice(-1e3),
|
|
1902
2222
|
sanitizedRawPreview: sanitizeTerminalText(this.accumulatedRawBuffer).slice(-1e3),
|
|
1903
2223
|
responseBuffer: this.responseBuffer.slice(-1e3),
|
|
2224
|
+
lastOutputAt: this.lastOutputAt,
|
|
2225
|
+
lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
|
|
2226
|
+
lastScreenChangeAt: this.lastScreenChangeAt,
|
|
2227
|
+
lastScreenSnapshot: this.lastScreenSnapshot.slice(-500),
|
|
1904
2228
|
isWaitingForResponse: this.isWaitingForResponse,
|
|
1905
2229
|
activeModal: this.activeModal,
|
|
1906
2230
|
lastApprovalResolvedAt: this.lastApprovalResolvedAt,
|
|
@@ -1912,6 +2236,8 @@ ${data.message || ""}`.trim();
|
|
|
1912
2236
|
resizeSuppressUntil: this.resizeSuppressUntil,
|
|
1913
2237
|
hasCliScripts: this.hasCliScripts(),
|
|
1914
2238
|
scriptNames: Object.keys(this.cliScripts).filter((k) => typeof this.cliScripts[k] === "function"),
|
|
2239
|
+
traceSessionId: this.traceSessionId,
|
|
2240
|
+
traceEntryCount: this.traceEntries.length,
|
|
1915
2241
|
statusHistory: this.statusHistory.slice(-30),
|
|
1916
2242
|
timeouts: this.timeouts,
|
|
1917
2243
|
pendingOutputParseBufferLength: this.pendingOutputParseBuffer.length,
|
|
@@ -1919,6 +2245,25 @@ ${data.message || ""}`.trim();
|
|
|
1919
2245
|
ptyAlive: !!this.ptyProcess
|
|
1920
2246
|
};
|
|
1921
2247
|
}
|
|
2248
|
+
getTraceState(limit = 120) {
|
|
2249
|
+
const cappedLimit = Math.max(1, Math.min(500, Number.isFinite(limit) ? Math.floor(limit) : 120));
|
|
2250
|
+
return {
|
|
2251
|
+
sessionId: this.traceSessionId,
|
|
2252
|
+
providerResolution: this.providerResolutionMeta,
|
|
2253
|
+
entryCount: this.traceEntries.length,
|
|
2254
|
+
entries: this.traceEntries.slice(-cappedLimit),
|
|
2255
|
+
screenText: this.summarizeTraceText(this.terminalScreen.getText(), 4e3),
|
|
2256
|
+
recentOutputBuffer: this.summarizeTraceText(this.recentOutputBuffer, 1e3),
|
|
2257
|
+
responseBuffer: this.summarizeTraceText(this.responseBuffer, 1200),
|
|
2258
|
+
status: this.currentStatus,
|
|
2259
|
+
activeModal: this.activeModal,
|
|
2260
|
+
currentTurnScope: this.currentTurnScope,
|
|
2261
|
+
messages: this.summarizeTraceMessages(this.committedMessages, 5)
|
|
2262
|
+
};
|
|
2263
|
+
}
|
|
2264
|
+
getProviderResolutionMeta() {
|
|
2265
|
+
return { ...this.providerResolutionMeta };
|
|
2266
|
+
}
|
|
1922
2267
|
respondToTerminalQueries(data) {
|
|
1923
2268
|
if (!this.ptyProcess || !data) return;
|
|
1924
2269
|
const combined = this.pendingTerminalQueryTail + data;
|
|
@@ -4112,6 +4457,7 @@ var ChatHistoryWriter = class {
|
|
|
4112
4457
|
role: msg.role,
|
|
4113
4458
|
content: msg.content || "",
|
|
4114
4459
|
kind: typeof msg.kind === "string" ? msg.kind : void 0,
|
|
4460
|
+
senderName: typeof msg.senderName === "string" ? msg.senderName : void 0,
|
|
4115
4461
|
agent: agentType,
|
|
4116
4462
|
instanceId,
|
|
4117
4463
|
historySessionId: effectiveHistoryKey,
|
|
@@ -4150,6 +4496,7 @@ var ChatHistoryWriter = class {
|
|
|
4150
4496
|
kind: "system",
|
|
4151
4497
|
content,
|
|
4152
4498
|
receivedAt: options.receivedAt,
|
|
4499
|
+
senderName: options.senderName,
|
|
4153
4500
|
historyDedupKey: options.dedupKey
|
|
4154
4501
|
}],
|
|
4155
4502
|
options.sessionTitle,
|
|
@@ -5496,6 +5843,12 @@ function getCurrentManagerKey(h) {
|
|
|
5496
5843
|
function getTargetedCliAdapter(h, args, providerType) {
|
|
5497
5844
|
return h.getCliAdapter(args?.targetSessionId || providerType || h.currentSession?.providerType || h.currentManagerKey);
|
|
5498
5845
|
}
|
|
5846
|
+
function getTargetInstance(h, args) {
|
|
5847
|
+
const targetSessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : "";
|
|
5848
|
+
const sessionId = targetSessionId || h.currentSession?.sessionId || "";
|
|
5849
|
+
if (!sessionId) return null;
|
|
5850
|
+
return h.ctx.instanceManager?.getInstance(sessionId);
|
|
5851
|
+
}
|
|
5499
5852
|
function getTargetTransport(h, provider) {
|
|
5500
5853
|
if (h.currentSession?.transport) return h.currentSession.transport;
|
|
5501
5854
|
switch (provider?.category) {
|
|
@@ -6238,6 +6591,7 @@ async function handleResolveAction(h, args) {
|
|
|
6238
6591
|
adapter.writeRaw?.(keys);
|
|
6239
6592
|
}
|
|
6240
6593
|
LOG.info("Command", `[resolveAction] CLI PTY \u2192 buttonIndex=${buttonIndex} "${buttons[buttonIndex] ?? "?"}"`);
|
|
6594
|
+
getTargetInstance(h, args)?.recordApprovalSelection?.(buttons[buttonIndex] ?? button);
|
|
6241
6595
|
return { success: true, buttonIndex, button: buttons[buttonIndex] ?? button };
|
|
6242
6596
|
}
|
|
6243
6597
|
if (isExtensionTransport(transport) && h.agentStream && h.getCdp() && h.currentSession?.sessionId) {
|
|
@@ -7411,6 +7765,7 @@ var CliProviderInstance = class {
|
|
|
7411
7765
|
generatingDebouncePending = null;
|
|
7412
7766
|
lastApprovalEventAt = 0;
|
|
7413
7767
|
historyWriter;
|
|
7768
|
+
runtimeMessages = [];
|
|
7414
7769
|
instanceId;
|
|
7415
7770
|
presentationMode;
|
|
7416
7771
|
providerSessionId;
|
|
@@ -7473,6 +7828,7 @@ var CliProviderInstance = class {
|
|
|
7473
7828
|
}
|
|
7474
7829
|
const runtime = this.adapter.getRuntimeMetadata();
|
|
7475
7830
|
const parsedMessages = Array.isArray(parsedStatus?.messages) ? parsedStatus.messages : [];
|
|
7831
|
+
const mergedMessages = this.mergeConversationMessages(parsedMessages);
|
|
7476
7832
|
const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
|
|
7477
7833
|
if (parsedMessages.length > 0) {
|
|
7478
7834
|
let messagesToSave = parsedMessages;
|
|
@@ -7502,7 +7858,7 @@ var CliProviderInstance = class {
|
|
|
7502
7858
|
id: `${this.type}_${this.workingDir}`,
|
|
7503
7859
|
title: parsedStatus?.title || dirName,
|
|
7504
7860
|
status: parsedStatus?.status || adapterStatus.status,
|
|
7505
|
-
messages:
|
|
7861
|
+
messages: mergedMessages,
|
|
7506
7862
|
activeModal: parsedStatus?.activeModal ?? adapterStatus.activeModal,
|
|
7507
7863
|
inputContent: ""
|
|
7508
7864
|
},
|
|
@@ -7601,6 +7957,11 @@ var CliProviderInstance = class {
|
|
|
7601
7957
|
const approvalCooldown = 5e3;
|
|
7602
7958
|
if (this.lastStatus !== "waiting_approval" && (!this.lastApprovalEventAt || now - this.lastApprovalEventAt > approvalCooldown)) {
|
|
7603
7959
|
this.lastApprovalEventAt = now;
|
|
7960
|
+
this.appendRuntimeSystemMessage(
|
|
7961
|
+
this.formatApprovalRequestMessage(modal?.message, modal?.buttons),
|
|
7962
|
+
`approval_request:${now}`,
|
|
7963
|
+
now
|
|
7964
|
+
);
|
|
7604
7965
|
this.pushEvent({
|
|
7605
7966
|
event: "agent:waiting_approval",
|
|
7606
7967
|
chatTitle,
|
|
@@ -7672,11 +8033,71 @@ var CliProviderInstance = class {
|
|
|
7672
8033
|
get cliName() {
|
|
7673
8034
|
return this.provider.name;
|
|
7674
8035
|
}
|
|
8036
|
+
recordApprovalSelection(buttonText) {
|
|
8037
|
+
const cleanButton = String(buttonText || "").trim();
|
|
8038
|
+
if (!cleanButton) return;
|
|
8039
|
+
const now = Date.now();
|
|
8040
|
+
this.appendRuntimeSystemMessage(
|
|
8041
|
+
`Approval selected: ${cleanButton}`,
|
|
8042
|
+
`approval_selection:${now}:${cleanButton}`,
|
|
8043
|
+
now
|
|
8044
|
+
);
|
|
8045
|
+
}
|
|
7675
8046
|
formatMarkerTimestamp(timestamp) {
|
|
7676
8047
|
const date = new Date(timestamp);
|
|
7677
8048
|
const pad = (value) => String(value).padStart(2, "0");
|
|
7678
8049
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
|
7679
8050
|
}
|
|
8051
|
+
appendRuntimeSystemMessage(content, dedupKey, receivedAt = Date.now()) {
|
|
8052
|
+
const normalizedContent = String(content || "").trim();
|
|
8053
|
+
if (!normalizedContent) return;
|
|
8054
|
+
if (this.runtimeMessages.some((entry) => entry.key === dedupKey)) return;
|
|
8055
|
+
this.runtimeMessages.push({
|
|
8056
|
+
key: dedupKey,
|
|
8057
|
+
message: {
|
|
8058
|
+
role: "system",
|
|
8059
|
+
senderName: "System",
|
|
8060
|
+
content: normalizedContent,
|
|
8061
|
+
receivedAt,
|
|
8062
|
+
timestamp: receivedAt
|
|
8063
|
+
}
|
|
8064
|
+
});
|
|
8065
|
+
if (this.runtimeMessages.length > 50) {
|
|
8066
|
+
this.runtimeMessages = this.runtimeMessages.slice(-50);
|
|
8067
|
+
}
|
|
8068
|
+
this.historyWriter.appendNewMessages(
|
|
8069
|
+
this.type,
|
|
8070
|
+
[{
|
|
8071
|
+
role: "system",
|
|
8072
|
+
senderName: "System",
|
|
8073
|
+
content: normalizedContent,
|
|
8074
|
+
receivedAt,
|
|
8075
|
+
historyDedupKey: dedupKey
|
|
8076
|
+
}],
|
|
8077
|
+
this.adapter.getScriptParsedStatus?.()?.title || this.workingDir.split("/").filter(Boolean).pop() || "session",
|
|
8078
|
+
this.instanceId,
|
|
8079
|
+
this.providerSessionId
|
|
8080
|
+
);
|
|
8081
|
+
}
|
|
8082
|
+
mergeConversationMessages(parsedMessages) {
|
|
8083
|
+
if (this.runtimeMessages.length === 0) return parsedMessages;
|
|
8084
|
+
return [...parsedMessages, ...this.runtimeMessages.map((entry) => entry.message)].map((message, index) => ({ message, index })).sort((a, b) => {
|
|
8085
|
+
const aTime = a.message.receivedAt || a.message.timestamp || 0;
|
|
8086
|
+
const bTime = b.message.receivedAt || b.message.timestamp || 0;
|
|
8087
|
+
if (aTime !== bTime) return aTime - bTime;
|
|
8088
|
+
return a.index - b.index;
|
|
8089
|
+
}).map((entry) => entry.message);
|
|
8090
|
+
}
|
|
8091
|
+
formatApprovalRequestMessage(modalMessage, buttons) {
|
|
8092
|
+
const lines = ["Approval requested"];
|
|
8093
|
+
const cleanMessage = String(modalMessage || "").trim();
|
|
8094
|
+
if (cleanMessage) lines.push(cleanMessage);
|
|
8095
|
+
const labels = (buttons || []).map((button) => String(button || "").trim()).filter(Boolean);
|
|
8096
|
+
if (labels.length > 0) {
|
|
8097
|
+
lines.push(labels.map((label) => `[${label}]`).join(" "));
|
|
8098
|
+
}
|
|
8099
|
+
return lines.join("\n");
|
|
8100
|
+
}
|
|
7680
8101
|
promoteProviderSessionId(sessionId) {
|
|
7681
8102
|
const nextSessionId = String(sessionId || "").trim();
|
|
7682
8103
|
if (!nextSessionId || nextSessionId === this.providerSessionId) return;
|
|
@@ -9692,6 +10113,7 @@ var ProviderLoader = class _ProviderLoader {
|
|
|
9692
10113
|
resolve(type, context) {
|
|
9693
10114
|
const base = this.providers.get(type);
|
|
9694
10115
|
if (!base) return void 0;
|
|
10116
|
+
const providerDir = this.findProviderDirInternal(type) || void 0;
|
|
9695
10117
|
const currentOs = context?.os || process.platform;
|
|
9696
10118
|
const currentVersion = context?.version ?? this.versionArchive?.getLatest(type) ?? void 0;
|
|
9697
10119
|
const resolved = JSON.parse(JSON.stringify(base));
|
|
@@ -9701,6 +10123,9 @@ var ProviderLoader = class _ProviderLoader {
|
|
|
9701
10123
|
if (base.scripts) {
|
|
9702
10124
|
resolved.scripts = { ...base.scripts };
|
|
9703
10125
|
}
|
|
10126
|
+
if (providerDir) {
|
|
10127
|
+
resolved._resolvedProviderDir = providerDir;
|
|
10128
|
+
}
|
|
9704
10129
|
if (base.os?.[currentOs]) {
|
|
9705
10130
|
const osOverride = base.os[currentOs];
|
|
9706
10131
|
if (osOverride.scripts) {
|
|
@@ -9721,6 +10146,12 @@ var ProviderLoader = class _ProviderLoader {
|
|
|
9721
10146
|
if (loaded) {
|
|
9722
10147
|
resolved.scripts = loaded;
|
|
9723
10148
|
this.log(` [compatibility] ${type} v${currentVersion} \u2192 ${entry.scriptDir}`);
|
|
10149
|
+
resolved._resolvedScriptDir = entry.scriptDir;
|
|
10150
|
+
resolved._resolvedScriptsSource = `compatibility:${entry.ideVersion}`;
|
|
10151
|
+
if (providerDir) {
|
|
10152
|
+
const fullDir = path10.join(providerDir, entry.scriptDir);
|
|
10153
|
+
resolved._resolvedScriptsPath = fs6.existsSync(path10.join(fullDir, "scripts.js")) ? path10.join(fullDir, "scripts.js") : fullDir;
|
|
10154
|
+
}
|
|
9724
10155
|
matched = true;
|
|
9725
10156
|
}
|
|
9726
10157
|
break;
|
|
@@ -9731,6 +10162,12 @@ var ProviderLoader = class _ProviderLoader {
|
|
|
9731
10162
|
if (loaded) {
|
|
9732
10163
|
resolved.scripts = loaded;
|
|
9733
10164
|
this.log(` [compatibility] ${type} v${currentVersion} \u2192 default: ${base.defaultScriptDir}`);
|
|
10165
|
+
resolved._resolvedScriptDir = base.defaultScriptDir;
|
|
10166
|
+
resolved._resolvedScriptsSource = "defaultScriptDir:version_miss";
|
|
10167
|
+
if (providerDir) {
|
|
10168
|
+
const fullDir = path10.join(providerDir, base.defaultScriptDir);
|
|
10169
|
+
resolved._resolvedScriptsPath = fs6.existsSync(path10.join(fullDir, "scripts.js")) ? path10.join(fullDir, "scripts.js") : fullDir;
|
|
10170
|
+
}
|
|
9734
10171
|
}
|
|
9735
10172
|
resolved._versionWarning = `Version ${currentVersion} not in compatibility matrix. Using default scripts.`;
|
|
9736
10173
|
}
|
|
@@ -9743,6 +10180,12 @@ var ProviderLoader = class _ProviderLoader {
|
|
|
9743
10180
|
if (loaded) {
|
|
9744
10181
|
resolved.scripts = loaded;
|
|
9745
10182
|
this.log(` [version override] ${type} ${range} \u2192 ${dirOverride}`);
|
|
10183
|
+
resolved._resolvedScriptDir = dirOverride;
|
|
10184
|
+
resolved._resolvedScriptsSource = `versions:${range}`;
|
|
10185
|
+
if (providerDir) {
|
|
10186
|
+
const fullDir = path10.join(providerDir, dirOverride);
|
|
10187
|
+
resolved._resolvedScriptsPath = fs6.existsSync(path10.join(fullDir, "scripts.js")) ? path10.join(fullDir, "scripts.js") : fullDir;
|
|
10188
|
+
}
|
|
9746
10189
|
}
|
|
9747
10190
|
} else if (override.scripts) {
|
|
9748
10191
|
resolved.scripts = { ...resolved.scripts, ...override.scripts };
|
|
@@ -9754,6 +10197,12 @@ var ProviderLoader = class _ProviderLoader {
|
|
|
9754
10197
|
if (loaded) {
|
|
9755
10198
|
resolved.scripts = loaded;
|
|
9756
10199
|
this.log(` [compatibility] ${type} no version detected \u2192 default: ${base.defaultScriptDir}`);
|
|
10200
|
+
resolved._resolvedScriptDir = base.defaultScriptDir;
|
|
10201
|
+
resolved._resolvedScriptsSource = "defaultScriptDir:no_version";
|
|
10202
|
+
if (providerDir) {
|
|
10203
|
+
const fullDir = path10.join(providerDir, base.defaultScriptDir);
|
|
10204
|
+
resolved._resolvedScriptsPath = fs6.existsSync(path10.join(fullDir, "scripts.js")) ? path10.join(fullDir, "scripts.js") : fullDir;
|
|
10205
|
+
}
|
|
9757
10206
|
}
|
|
9758
10207
|
}
|
|
9759
10208
|
if (base.overrides) {
|
|
@@ -9822,6 +10271,9 @@ var ProviderLoader = class _ProviderLoader {
|
|
|
9822
10271
|
awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }
|
|
9823
10272
|
});
|
|
9824
10273
|
const handleChange = (filePath) => {
|
|
10274
|
+
if (/[\/\\]fixtures[\/\\]/.test(filePath)) {
|
|
10275
|
+
return;
|
|
10276
|
+
}
|
|
9825
10277
|
if (filePath.endsWith(".js") || filePath.endsWith(".json")) {
|
|
9826
10278
|
this.log(`File changed: ${path10.basename(filePath)}, reloading...`);
|
|
9827
10279
|
this.reload();
|
|
@@ -10514,7 +10966,7 @@ function detectCurrentWorkspace(ideId) {
|
|
|
10514
10966
|
}
|
|
10515
10967
|
} else if (plat === "win32") {
|
|
10516
10968
|
try {
|
|
10517
|
-
const
|
|
10969
|
+
const fs15 = require("fs");
|
|
10518
10970
|
const appNameMap = getMacAppIdentifiers();
|
|
10519
10971
|
const appName = appNameMap[ideId];
|
|
10520
10972
|
if (appName) {
|
|
@@ -10523,8 +10975,8 @@ function detectCurrentWorkspace(ideId) {
|
|
|
10523
10975
|
appName,
|
|
10524
10976
|
"storage.json"
|
|
10525
10977
|
);
|
|
10526
|
-
if (
|
|
10527
|
-
const data = JSON.parse(
|
|
10978
|
+
if (fs15.existsSync(storagePath)) {
|
|
10979
|
+
const data = JSON.parse(fs15.readFileSync(storagePath, "utf-8"));
|
|
10528
10980
|
const workspaces = data?.openedPathsList?.workspaces3 || data?.openedPathsList?.entries || [];
|
|
10529
10981
|
if (workspaces.length > 0) {
|
|
10530
10982
|
const recent = workspaces[0];
|
|
@@ -12716,8 +13168,8 @@ async function detectAllVersions(loader, archive) {
|
|
|
12716
13168
|
|
|
12717
13169
|
// src/daemon/dev-server.ts
|
|
12718
13170
|
var http2 = __toESM(require("http"));
|
|
12719
|
-
var
|
|
12720
|
-
var
|
|
13171
|
+
var fs14 = __toESM(require("fs"));
|
|
13172
|
+
var path18 = __toESM(require("path"));
|
|
12721
13173
|
|
|
12722
13174
|
// src/daemon/scaffold-template.ts
|
|
12723
13175
|
function generateFiles(type, name, category, opts = {}) {
|
|
@@ -14060,6 +14512,162 @@ async function handleDomContext(ctx, type, req, res) {
|
|
|
14060
14512
|
}
|
|
14061
14513
|
|
|
14062
14514
|
// src/daemon/dev-cli-debug.ts
|
|
14515
|
+
var fs12 = __toESM(require("fs"));
|
|
14516
|
+
var path16 = __toESM(require("path"));
|
|
14517
|
+
function slugifyFixtureName(value) {
|
|
14518
|
+
const normalized = String(value || "").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
14519
|
+
return normalized || `fixture-${Date.now()}`;
|
|
14520
|
+
}
|
|
14521
|
+
function getCliFixtureDir(ctx, type) {
|
|
14522
|
+
const providerDir = ctx.providerLoader.findProviderDir(type);
|
|
14523
|
+
if (!providerDir) {
|
|
14524
|
+
throw new Error(`Provider directory not found for '${type}'`);
|
|
14525
|
+
}
|
|
14526
|
+
return path16.join(providerDir, "fixtures");
|
|
14527
|
+
}
|
|
14528
|
+
function readCliFixture(ctx, type, name) {
|
|
14529
|
+
const fixtureDir = getCliFixtureDir(ctx, type);
|
|
14530
|
+
const filePath = path16.join(fixtureDir, `${name}.json`);
|
|
14531
|
+
if (!fs12.existsSync(filePath)) {
|
|
14532
|
+
throw new Error(`Fixture not found: ${filePath}`);
|
|
14533
|
+
}
|
|
14534
|
+
return JSON.parse(fs12.readFileSync(filePath, "utf-8"));
|
|
14535
|
+
}
|
|
14536
|
+
function getExerciseTranscriptText(result) {
|
|
14537
|
+
const parts = [];
|
|
14538
|
+
const debugMessages = Array.isArray(result?.debug?.messages) ? result.debug.messages : [];
|
|
14539
|
+
const traceMessages = Array.isArray(result?.trace?.messages) ? result.trace.messages : [];
|
|
14540
|
+
for (const message of [...debugMessages, ...traceMessages]) {
|
|
14541
|
+
if (!message || typeof message.content !== "string") continue;
|
|
14542
|
+
parts.push(message.content);
|
|
14543
|
+
}
|
|
14544
|
+
if (typeof result?.debug?.partialResponse === "string") parts.push(result.debug.partialResponse);
|
|
14545
|
+
if (typeof result?.trace?.responseBuffer === "string") parts.push(result.trace.responseBuffer);
|
|
14546
|
+
return parts.join("\n");
|
|
14547
|
+
}
|
|
14548
|
+
function getExerciseLastAssistant(result) {
|
|
14549
|
+
const debugMessages = Array.isArray(result?.debug?.messages) ? result.debug.messages : [];
|
|
14550
|
+
const traceMessages = Array.isArray(result?.trace?.messages) ? result.trace.messages : [];
|
|
14551
|
+
for (const messages of [debugMessages, traceMessages]) {
|
|
14552
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
14553
|
+
const message = messages[i];
|
|
14554
|
+
if (message?.role === "assistant" && typeof message.content === "string" && message.content.trim()) {
|
|
14555
|
+
return message.content;
|
|
14556
|
+
}
|
|
14557
|
+
}
|
|
14558
|
+
}
|
|
14559
|
+
return "";
|
|
14560
|
+
}
|
|
14561
|
+
function getExerciseMessageCount(result) {
|
|
14562
|
+
const debugMessages = Array.isArray(result?.debug?.messages) ? result.debug.messages : [];
|
|
14563
|
+
const traceMessages = Array.isArray(result?.trace?.messages) ? result.trace.messages : [];
|
|
14564
|
+
return Math.max(debugMessages.length, traceMessages.length);
|
|
14565
|
+
}
|
|
14566
|
+
function compileFixtureRegex(source) {
|
|
14567
|
+
const value = String(source || "").trim();
|
|
14568
|
+
if (!value) return null;
|
|
14569
|
+
const delimited = value.match(/^\/([\s\S]+)\/([dgimsuvy]*)$/);
|
|
14570
|
+
try {
|
|
14571
|
+
if (delimited) {
|
|
14572
|
+
return new RegExp(delimited[1], delimited[2]);
|
|
14573
|
+
}
|
|
14574
|
+
return new RegExp(value, "m");
|
|
14575
|
+
} catch {
|
|
14576
|
+
return null;
|
|
14577
|
+
}
|
|
14578
|
+
}
|
|
14579
|
+
function statusesContainSequence(actual, expected) {
|
|
14580
|
+
if (!expected.length) return true;
|
|
14581
|
+
let index = 0;
|
|
14582
|
+
for (const status of actual) {
|
|
14583
|
+
if (status === expected[index]) index += 1;
|
|
14584
|
+
if (index >= expected.length) return true;
|
|
14585
|
+
}
|
|
14586
|
+
return false;
|
|
14587
|
+
}
|
|
14588
|
+
function validateCliFixtureResult(result, assertions) {
|
|
14589
|
+
const failures = [];
|
|
14590
|
+
const transcriptText = getExerciseTranscriptText(result);
|
|
14591
|
+
const lastAssistant = getExerciseLastAssistant(result);
|
|
14592
|
+
const mustContainAny = assertions.mustContainAny || [];
|
|
14593
|
+
const mustNotContainAny = assertions.mustNotContainAny || [];
|
|
14594
|
+
const mustMatchAny = assertions.mustMatchAny || [];
|
|
14595
|
+
const mustNotMatchAny = assertions.mustNotMatchAny || [];
|
|
14596
|
+
const lastAssistantMustContainAny = assertions.lastAssistantMustContainAny || [];
|
|
14597
|
+
const lastAssistantMustNotContainAny = assertions.lastAssistantMustNotContainAny || [];
|
|
14598
|
+
const lastAssistantMustMatchAny = assertions.lastAssistantMustMatchAny || [];
|
|
14599
|
+
const lastAssistantMustNotMatchAny = assertions.lastAssistantMustNotMatchAny || [];
|
|
14600
|
+
const statusesSeen = Array.isArray(result?.statusesSeen) ? result.statusesSeen.map((value) => String(value)) : [];
|
|
14601
|
+
if (assertions.requireNotTimedOut !== false && result?.timedOut) {
|
|
14602
|
+
failures.push("Exercise timed out");
|
|
14603
|
+
}
|
|
14604
|
+
const missingRequired = mustContainAny.filter((value) => !transcriptText.includes(value));
|
|
14605
|
+
if (missingRequired.length > 0) {
|
|
14606
|
+
failures.push(`Missing required substrings: ${missingRequired.join(", ")}`);
|
|
14607
|
+
}
|
|
14608
|
+
const presentBanned = mustNotContainAny.filter((value) => transcriptText.includes(value));
|
|
14609
|
+
if (presentBanned.length > 0) {
|
|
14610
|
+
failures.push(`Found banned substrings: ${presentBanned.join(", ")}`);
|
|
14611
|
+
}
|
|
14612
|
+
const missingRegex = mustMatchAny.filter((value) => {
|
|
14613
|
+
const regex = compileFixtureRegex(value);
|
|
14614
|
+
return !regex || !regex.test(transcriptText);
|
|
14615
|
+
});
|
|
14616
|
+
if (missingRegex.length > 0) {
|
|
14617
|
+
failures.push(`Missing required regex matches: ${missingRegex.join(", ")}`);
|
|
14618
|
+
}
|
|
14619
|
+
const presentBannedRegex = mustNotMatchAny.filter((value) => {
|
|
14620
|
+
const regex = compileFixtureRegex(value);
|
|
14621
|
+
return !!regex && regex.test(transcriptText);
|
|
14622
|
+
});
|
|
14623
|
+
if (presentBannedRegex.length > 0) {
|
|
14624
|
+
failures.push(`Found banned regex matches: ${presentBannedRegex.join(", ")}`);
|
|
14625
|
+
}
|
|
14626
|
+
const missingLastAssistant = lastAssistantMustContainAny.filter((value) => !lastAssistant.includes(value));
|
|
14627
|
+
if (missingLastAssistant.length > 0) {
|
|
14628
|
+
failures.push(`Missing required lastAssistant substrings: ${missingLastAssistant.join(", ")}`);
|
|
14629
|
+
}
|
|
14630
|
+
const presentBannedLastAssistant = lastAssistantMustNotContainAny.filter((value) => lastAssistant.includes(value));
|
|
14631
|
+
if (presentBannedLastAssistant.length > 0) {
|
|
14632
|
+
failures.push(`Found banned lastAssistant substrings: ${presentBannedLastAssistant.join(", ")}`);
|
|
14633
|
+
}
|
|
14634
|
+
const missingLastAssistantRegex = lastAssistantMustMatchAny.filter((value) => {
|
|
14635
|
+
const regex = compileFixtureRegex(value);
|
|
14636
|
+
return !regex || !regex.test(lastAssistant);
|
|
14637
|
+
});
|
|
14638
|
+
if (missingLastAssistantRegex.length > 0) {
|
|
14639
|
+
failures.push(`Missing required lastAssistant regex matches: ${missingLastAssistantRegex.join(", ")}`);
|
|
14640
|
+
}
|
|
14641
|
+
const presentBannedLastAssistantRegex = lastAssistantMustNotMatchAny.filter((value) => {
|
|
14642
|
+
const regex = compileFixtureRegex(value);
|
|
14643
|
+
return !!regex && regex.test(lastAssistant);
|
|
14644
|
+
});
|
|
14645
|
+
if (presentBannedLastAssistantRegex.length > 0) {
|
|
14646
|
+
failures.push(`Found banned lastAssistant regex matches: ${presentBannedLastAssistantRegex.join(", ")}`);
|
|
14647
|
+
}
|
|
14648
|
+
if (assertions.statusesSeen?.length && !statusesContainSequence(statusesSeen, assertions.statusesSeen)) {
|
|
14649
|
+
failures.push(`Expected statuses sequence not observed: ${assertions.statusesSeen.join(" -> ")}`);
|
|
14650
|
+
}
|
|
14651
|
+
if (result && typeof result === "object") {
|
|
14652
|
+
result.lastAssistant = lastAssistant;
|
|
14653
|
+
}
|
|
14654
|
+
return failures;
|
|
14655
|
+
}
|
|
14656
|
+
function getCliProviderResolutionMeta(ctx, type, adapter) {
|
|
14657
|
+
const adapterMeta = typeof adapter?.getProviderResolutionMeta === "function" ? adapter.getProviderResolutionMeta() : adapter?.getDebugState?.()?.providerResolution || null;
|
|
14658
|
+
const resolvedProvider = ctx.providerLoader.resolve(type);
|
|
14659
|
+
if (!adapterMeta && !resolvedProvider) return null;
|
|
14660
|
+
return {
|
|
14661
|
+
type,
|
|
14662
|
+
providerDir: adapterMeta?.providerDir || resolvedProvider?._resolvedProviderDir || ctx.providerLoader.findProviderDir(type),
|
|
14663
|
+
scriptDir: adapterMeta?.scriptDir || resolvedProvider?._resolvedScriptDir || null,
|
|
14664
|
+
scriptsPath: adapterMeta?.scriptsPath || resolvedProvider?._resolvedScriptsPath || null,
|
|
14665
|
+
scriptsSource: adapterMeta?.scriptsSource || resolvedProvider?._resolvedScriptsSource || null,
|
|
14666
|
+
resolvedVersion: adapterMeta?.resolvedVersion || resolvedProvider?._resolvedVersion || null,
|
|
14667
|
+
resolvedOs: adapterMeta?.resolvedOs || resolvedProvider?._resolvedOs || null,
|
|
14668
|
+
versionWarning: adapterMeta?.versionWarning || resolvedProvider?._versionWarning || null
|
|
14669
|
+
};
|
|
14670
|
+
}
|
|
14063
14671
|
function findCliTarget(ctx, type, instanceId) {
|
|
14064
14672
|
if (!ctx.instanceManager) return null;
|
|
14065
14673
|
const cliStates = ctx.instanceManager.collectAllStates().filter((s) => s.category === "cli" || s.category === "acp");
|
|
@@ -14068,6 +14676,331 @@ function findCliTarget(ctx, type, instanceId) {
|
|
|
14068
14676
|
const matches = cliStates.filter((s) => s.type === type);
|
|
14069
14677
|
return matches[matches.length - 1] || null;
|
|
14070
14678
|
}
|
|
14679
|
+
function getCliTargetBundle(ctx, type, instanceId) {
|
|
14680
|
+
if (!ctx.instanceManager) return null;
|
|
14681
|
+
const target = findCliTarget(ctx, type, instanceId);
|
|
14682
|
+
if (!target) return null;
|
|
14683
|
+
const instance = ctx.instanceManager.getInstance(target.instanceId);
|
|
14684
|
+
if (!instance) return null;
|
|
14685
|
+
const adapter = instance.getAdapter?.() || instance.adapter;
|
|
14686
|
+
if (!adapter) return null;
|
|
14687
|
+
return { target, instance, adapter };
|
|
14688
|
+
}
|
|
14689
|
+
function sleep(ms) {
|
|
14690
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
14691
|
+
}
|
|
14692
|
+
async function waitForCliReady(ctx, type, instanceId, timeoutMs) {
|
|
14693
|
+
const startedAt = Date.now();
|
|
14694
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
14695
|
+
const bundle = getCliTargetBundle(ctx, type, instanceId);
|
|
14696
|
+
if (bundle) {
|
|
14697
|
+
const debug = typeof bundle.adapter.getDebugState === "function" ? bundle.adapter.getDebugState() : null;
|
|
14698
|
+
const startupParseGate = !!debug?.startupParseGate;
|
|
14699
|
+
const adapterReady = !!debug?.ready;
|
|
14700
|
+
const visibleStatusReady = bundle.target.status === "generating" || bundle.target.status === "waiting_approval";
|
|
14701
|
+
const idleReady = bundle.target.status === "idle" && !startupParseGate;
|
|
14702
|
+
if (adapterReady || visibleStatusReady || idleReady) {
|
|
14703
|
+
return bundle;
|
|
14704
|
+
}
|
|
14705
|
+
}
|
|
14706
|
+
await sleep(100);
|
|
14707
|
+
}
|
|
14708
|
+
return getCliTargetBundle(ctx, type, instanceId);
|
|
14709
|
+
}
|
|
14710
|
+
async function runCliExerciseInternal(ctx, body) {
|
|
14711
|
+
if (!ctx.cliManager) {
|
|
14712
|
+
throw new Error("CliManager not available");
|
|
14713
|
+
}
|
|
14714
|
+
if (!ctx.instanceManager) {
|
|
14715
|
+
throw new Error("InstanceManager not available");
|
|
14716
|
+
}
|
|
14717
|
+
const {
|
|
14718
|
+
type,
|
|
14719
|
+
text,
|
|
14720
|
+
instanceId: requestedInstanceId,
|
|
14721
|
+
workingDir,
|
|
14722
|
+
args,
|
|
14723
|
+
autoLaunch = true,
|
|
14724
|
+
freshSession = true,
|
|
14725
|
+
autoResolveApprovals = true,
|
|
14726
|
+
approvalButtonIndex = 0,
|
|
14727
|
+
timeoutMs = 45e3,
|
|
14728
|
+
readyTimeoutMs = 15e3,
|
|
14729
|
+
idleSettledMs = 1200,
|
|
14730
|
+
traceLimit = 160,
|
|
14731
|
+
stopWhenDone = false
|
|
14732
|
+
} = body || {};
|
|
14733
|
+
if (!type) {
|
|
14734
|
+
throw new Error("type required (e.g. claude-cli, codex-cli)");
|
|
14735
|
+
}
|
|
14736
|
+
if (!text || typeof text !== "string") {
|
|
14737
|
+
throw new Error("text required (prompt to send to the CLI)");
|
|
14738
|
+
}
|
|
14739
|
+
let resolvedInstanceId = requestedInstanceId;
|
|
14740
|
+
if (freshSession) {
|
|
14741
|
+
const staleTargets = ctx.instanceManager.collectAllStates().filter((state) => (state.category === "cli" || state.category === "acp") && state.type === type).map((state) => state.instanceId);
|
|
14742
|
+
for (const staleId of staleTargets) {
|
|
14743
|
+
ctx.instanceManager.removeInstance(staleId);
|
|
14744
|
+
}
|
|
14745
|
+
resolvedInstanceId = void 0;
|
|
14746
|
+
}
|
|
14747
|
+
let bundle = getCliTargetBundle(ctx, type, resolvedInstanceId);
|
|
14748
|
+
if (!bundle && autoLaunch) {
|
|
14749
|
+
const launchArgs = [type, workingDir || process.cwd(), Array.isArray(args) ? args : []];
|
|
14750
|
+
let launched = null;
|
|
14751
|
+
let lastLaunchError = null;
|
|
14752
|
+
for (let attempt = 1; attempt <= 2; attempt += 1) {
|
|
14753
|
+
try {
|
|
14754
|
+
launched = await ctx.cliManager.startSession(...launchArgs);
|
|
14755
|
+
lastLaunchError = null;
|
|
14756
|
+
break;
|
|
14757
|
+
} catch (error) {
|
|
14758
|
+
lastLaunchError = error instanceof Error ? error : new Error(String(error?.message || error));
|
|
14759
|
+
const message = String(lastLaunchError.message || "");
|
|
14760
|
+
const retryable = /ECONNREFUSED|session-host|Session host/i.test(message);
|
|
14761
|
+
if (!retryable || attempt === 2) break;
|
|
14762
|
+
await sleep(1e3);
|
|
14763
|
+
}
|
|
14764
|
+
}
|
|
14765
|
+
if (!launched) {
|
|
14766
|
+
throw lastLaunchError || new Error(`Failed to start ${type}`);
|
|
14767
|
+
}
|
|
14768
|
+
resolvedInstanceId = launched.runtimeSessionId;
|
|
14769
|
+
bundle = await waitForCliReady(ctx, type, resolvedInstanceId, Math.max(1e3, readyTimeoutMs));
|
|
14770
|
+
}
|
|
14771
|
+
if (!bundle) {
|
|
14772
|
+
throw new Error(`No running instance found for: ${resolvedInstanceId || type}`);
|
|
14773
|
+
}
|
|
14774
|
+
const initialDebug = typeof bundle.adapter.getDebugState === "function" ? bundle.adapter.getDebugState() : null;
|
|
14775
|
+
const initialTrace = typeof bundle.adapter.getTraceState === "function" ? bundle.adapter.getTraceState(traceLimit) : null;
|
|
14776
|
+
const providerResolution = getCliProviderResolutionMeta(ctx, bundle.target.type, bundle.adapter);
|
|
14777
|
+
const preTraceCount = Number(initialTrace?.entryCount || 0);
|
|
14778
|
+
const startAt = Date.now();
|
|
14779
|
+
const statusesSeen = [];
|
|
14780
|
+
const approvalsResolved = [];
|
|
14781
|
+
let lastStatus = "";
|
|
14782
|
+
let lastModalKey = "";
|
|
14783
|
+
let idleSince = 0;
|
|
14784
|
+
let sawBusy = false;
|
|
14785
|
+
ctx.instanceManager.sendEvent(bundle.target.instanceId, "send_message", { text });
|
|
14786
|
+
while (Date.now() - startAt < Math.max(1e3, timeoutMs)) {
|
|
14787
|
+
await sleep(150);
|
|
14788
|
+
bundle = getCliTargetBundle(ctx, type, bundle.target.instanceId);
|
|
14789
|
+
if (!bundle) {
|
|
14790
|
+
throw new Error("CLI instance disappeared during exercise");
|
|
14791
|
+
}
|
|
14792
|
+
const debug = typeof bundle.adapter.getDebugState === "function" ? bundle.adapter.getDebugState() : null;
|
|
14793
|
+
const trace = typeof bundle.adapter.getTraceState === "function" ? bundle.adapter.getTraceState(traceLimit) : null;
|
|
14794
|
+
const status = String(debug?.status || bundle.target.status || "unknown");
|
|
14795
|
+
const traceEntries = Array.isArray(trace?.entries) ? trace.entries : [];
|
|
14796
|
+
const sawSendMessage = traceEntries.some((entry) => entry?.type === "send_message");
|
|
14797
|
+
const sawSubmitWrite = traceEntries.some((entry) => entry?.type === "submit_write");
|
|
14798
|
+
const hasTurnStarted = sawSendMessage || sawSubmitWrite || !!debug?.currentTurnScope;
|
|
14799
|
+
if (status !== lastStatus) {
|
|
14800
|
+
statusesSeen.push(status);
|
|
14801
|
+
lastStatus = status;
|
|
14802
|
+
}
|
|
14803
|
+
if (status === "generating" || status === "waiting_approval") {
|
|
14804
|
+
sawBusy = true;
|
|
14805
|
+
idleSince = 0;
|
|
14806
|
+
}
|
|
14807
|
+
const modal = debug?.activeModal || trace?.activeModal || null;
|
|
14808
|
+
if (autoResolveApprovals && status === "waiting_approval" && modal && Array.isArray(modal.buttons) && modal.buttons.length > 0) {
|
|
14809
|
+
const clampedIndex = Math.max(0, Math.min(Number(approvalButtonIndex) || 0, modal.buttons.length - 1));
|
|
14810
|
+
const modalKey = JSON.stringify({
|
|
14811
|
+
message: modal.message || "",
|
|
14812
|
+
buttons: modal.buttons,
|
|
14813
|
+
index: clampedIndex
|
|
14814
|
+
});
|
|
14815
|
+
if (modalKey !== lastModalKey && typeof bundle.adapter.resolveModal === "function") {
|
|
14816
|
+
lastModalKey = modalKey;
|
|
14817
|
+
approvalsResolved.push({
|
|
14818
|
+
at: Date.now(),
|
|
14819
|
+
buttonIndex: clampedIndex,
|
|
14820
|
+
label: modal.buttons[clampedIndex] || null
|
|
14821
|
+
});
|
|
14822
|
+
bundle.adapter.resolveModal(clampedIndex);
|
|
14823
|
+
continue;
|
|
14824
|
+
}
|
|
14825
|
+
}
|
|
14826
|
+
const traceCount = Number(trace?.entryCount || 0);
|
|
14827
|
+
const hasProgress = hasTurnStarted && (traceCount > preTraceCount || statusesSeen.length > 1 || approvalsResolved.length > 0);
|
|
14828
|
+
if (status === "idle" && hasProgress && sawBusy) {
|
|
14829
|
+
if (!idleSince) idleSince = Date.now();
|
|
14830
|
+
if (Date.now() - idleSince >= Math.max(200, idleSettledMs)) {
|
|
14831
|
+
const payload2 = {
|
|
14832
|
+
exercised: true,
|
|
14833
|
+
instanceId: bundle.target.instanceId,
|
|
14834
|
+
providerState: {
|
|
14835
|
+
type: bundle.target.type,
|
|
14836
|
+
name: bundle.target.name,
|
|
14837
|
+
status: bundle.target.status,
|
|
14838
|
+
mode: "mode" in bundle.target ? bundle.target.mode : void 0
|
|
14839
|
+
},
|
|
14840
|
+
providerResolution,
|
|
14841
|
+
initialDebug,
|
|
14842
|
+
initialTrace,
|
|
14843
|
+
debug,
|
|
14844
|
+
trace,
|
|
14845
|
+
statusesSeen,
|
|
14846
|
+
approvalsResolved,
|
|
14847
|
+
elapsedMs: Date.now() - startAt,
|
|
14848
|
+
timedOut: false
|
|
14849
|
+
};
|
|
14850
|
+
payload2.lastAssistant = getExerciseLastAssistant(payload2);
|
|
14851
|
+
payload2.messageCount = getExerciseMessageCount(payload2);
|
|
14852
|
+
if (stopWhenDone) {
|
|
14853
|
+
ctx.instanceManager.removeInstance(bundle.target.instanceId);
|
|
14854
|
+
}
|
|
14855
|
+
return payload2;
|
|
14856
|
+
}
|
|
14857
|
+
} else if (status === "idle" && hasProgress) {
|
|
14858
|
+
if (!idleSince) idleSince = Date.now();
|
|
14859
|
+
if (Date.now() - idleSince >= Math.max(500, idleSettledMs) && Date.now() - startAt >= 750) {
|
|
14860
|
+
const payload2 = {
|
|
14861
|
+
exercised: true,
|
|
14862
|
+
instanceId: bundle.target.instanceId,
|
|
14863
|
+
providerState: {
|
|
14864
|
+
type: bundle.target.type,
|
|
14865
|
+
name: bundle.target.name,
|
|
14866
|
+
status: bundle.target.status,
|
|
14867
|
+
mode: "mode" in bundle.target ? bundle.target.mode : void 0
|
|
14868
|
+
},
|
|
14869
|
+
providerResolution,
|
|
14870
|
+
initialDebug,
|
|
14871
|
+
initialTrace,
|
|
14872
|
+
debug,
|
|
14873
|
+
trace,
|
|
14874
|
+
statusesSeen,
|
|
14875
|
+
approvalsResolved,
|
|
14876
|
+
elapsedMs: Date.now() - startAt,
|
|
14877
|
+
timedOut: false
|
|
14878
|
+
};
|
|
14879
|
+
payload2.lastAssistant = getExerciseLastAssistant(payload2);
|
|
14880
|
+
payload2.messageCount = getExerciseMessageCount(payload2);
|
|
14881
|
+
if (stopWhenDone) {
|
|
14882
|
+
ctx.instanceManager.removeInstance(bundle.target.instanceId);
|
|
14883
|
+
}
|
|
14884
|
+
return payload2;
|
|
14885
|
+
}
|
|
14886
|
+
} else {
|
|
14887
|
+
idleSince = 0;
|
|
14888
|
+
}
|
|
14889
|
+
}
|
|
14890
|
+
const finalBundle = getCliTargetBundle(ctx, type, bundle.target.instanceId) || bundle;
|
|
14891
|
+
const finalDebug = typeof finalBundle.adapter.getDebugState === "function" ? finalBundle.adapter.getDebugState() : null;
|
|
14892
|
+
const finalTrace = typeof finalBundle.adapter.getTraceState === "function" ? finalBundle.adapter.getTraceState(traceLimit) : null;
|
|
14893
|
+
if (stopWhenDone) {
|
|
14894
|
+
ctx.instanceManager.removeInstance(finalBundle.target.instanceId);
|
|
14895
|
+
}
|
|
14896
|
+
const payload = {
|
|
14897
|
+
exercised: true,
|
|
14898
|
+
instanceId: finalBundle.target.instanceId,
|
|
14899
|
+
providerState: {
|
|
14900
|
+
type: finalBundle.target.type,
|
|
14901
|
+
name: finalBundle.target.name,
|
|
14902
|
+
status: finalBundle.target.status,
|
|
14903
|
+
mode: "mode" in finalBundle.target ? finalBundle.target.mode : void 0
|
|
14904
|
+
},
|
|
14905
|
+
providerResolution: getCliProviderResolutionMeta(ctx, finalBundle.target.type, finalBundle.adapter),
|
|
14906
|
+
initialDebug,
|
|
14907
|
+
initialTrace,
|
|
14908
|
+
debug: finalDebug,
|
|
14909
|
+
trace: finalTrace,
|
|
14910
|
+
statusesSeen,
|
|
14911
|
+
approvalsResolved,
|
|
14912
|
+
elapsedMs: Date.now() - startAt,
|
|
14913
|
+
timedOut: true
|
|
14914
|
+
};
|
|
14915
|
+
payload.lastAssistant = getExerciseLastAssistant(payload);
|
|
14916
|
+
payload.messageCount = getExerciseMessageCount(payload);
|
|
14917
|
+
return payload;
|
|
14918
|
+
}
|
|
14919
|
+
async function runCliAutoImplVerification(ctx, type, verification) {
|
|
14920
|
+
const assertions = {
|
|
14921
|
+
mustContainAny: verification?.mustContainAny || [],
|
|
14922
|
+
mustNotContainAny: verification?.mustNotContainAny || [],
|
|
14923
|
+
mustMatchAny: verification?.mustMatchAny || [],
|
|
14924
|
+
mustNotMatchAny: verification?.mustNotMatchAny || [],
|
|
14925
|
+
lastAssistantMustContainAny: verification?.lastAssistantMustContainAny || [],
|
|
14926
|
+
lastAssistantMustNotContainAny: verification?.lastAssistantMustNotContainAny || [],
|
|
14927
|
+
lastAssistantMustMatchAny: verification?.lastAssistantMustMatchAny || [],
|
|
14928
|
+
lastAssistantMustNotMatchAny: verification?.lastAssistantMustNotMatchAny || [],
|
|
14929
|
+
requireNotTimedOut: true
|
|
14930
|
+
};
|
|
14931
|
+
const rawFixtureNames = Array.isArray(verification?.fixtureNames) ? verification.fixtureNames.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
14932
|
+
if (rawFixtureNames.length > 0) {
|
|
14933
|
+
const results = [];
|
|
14934
|
+
for (const rawFixtureName2 of rawFixtureNames) {
|
|
14935
|
+
const name = slugifyFixtureName(rawFixtureName2);
|
|
14936
|
+
const fixture = readCliFixture(ctx, type, name);
|
|
14937
|
+
const mergedAssertions = {
|
|
14938
|
+
...fixture.assertions,
|
|
14939
|
+
...assertions
|
|
14940
|
+
};
|
|
14941
|
+
const result2 = await runCliExerciseInternal(ctx, {
|
|
14942
|
+
...fixture.request,
|
|
14943
|
+
type
|
|
14944
|
+
});
|
|
14945
|
+
const failures2 = validateCliFixtureResult(result2, mergedAssertions);
|
|
14946
|
+
results.push({
|
|
14947
|
+
fixtureName: name,
|
|
14948
|
+
pass: failures2.length === 0,
|
|
14949
|
+
failures: failures2,
|
|
14950
|
+
result: result2,
|
|
14951
|
+
assertions: mergedAssertions,
|
|
14952
|
+
fixture
|
|
14953
|
+
});
|
|
14954
|
+
}
|
|
14955
|
+
const firstFailure = results.find((item) => !item.pass) || results[results.length - 1];
|
|
14956
|
+
return {
|
|
14957
|
+
mode: "fixture_replay_suite",
|
|
14958
|
+
pass: results.every((item) => item.pass),
|
|
14959
|
+
failures: results.flatMap((item) => item.failures.map((failure) => `${item.fixtureName}: ${failure}`)),
|
|
14960
|
+
result: firstFailure.result,
|
|
14961
|
+
assertions: firstFailure.assertions,
|
|
14962
|
+
fixture: firstFailure.fixture,
|
|
14963
|
+
results
|
|
14964
|
+
};
|
|
14965
|
+
}
|
|
14966
|
+
const rawFixtureName = String(verification?.fixtureName || "").trim();
|
|
14967
|
+
if (rawFixtureName) {
|
|
14968
|
+
const name = slugifyFixtureName(rawFixtureName);
|
|
14969
|
+
try {
|
|
14970
|
+
const fixture = readCliFixture(ctx, type, name);
|
|
14971
|
+
const mergedAssertions = {
|
|
14972
|
+
...fixture.assertions,
|
|
14973
|
+
...assertions
|
|
14974
|
+
};
|
|
14975
|
+
const result2 = await runCliExerciseInternal(ctx, {
|
|
14976
|
+
...fixture.request,
|
|
14977
|
+
type
|
|
14978
|
+
});
|
|
14979
|
+
const failures2 = validateCliFixtureResult(result2, mergedAssertions);
|
|
14980
|
+
return {
|
|
14981
|
+
mode: "fixture_replay",
|
|
14982
|
+
pass: failures2.length === 0,
|
|
14983
|
+
failures: failures2,
|
|
14984
|
+
result: result2,
|
|
14985
|
+
assertions: mergedAssertions,
|
|
14986
|
+
fixture
|
|
14987
|
+
};
|
|
14988
|
+
} catch {
|
|
14989
|
+
}
|
|
14990
|
+
}
|
|
14991
|
+
const result = await runCliExerciseInternal(ctx, {
|
|
14992
|
+
...verification?.request || {},
|
|
14993
|
+
type
|
|
14994
|
+
});
|
|
14995
|
+
const failures = validateCliFixtureResult(result, assertions);
|
|
14996
|
+
return {
|
|
14997
|
+
mode: "exercise",
|
|
14998
|
+
pass: failures.length === 0,
|
|
14999
|
+
failures,
|
|
15000
|
+
result,
|
|
15001
|
+
assertions
|
|
15002
|
+
};
|
|
15003
|
+
}
|
|
14071
15004
|
async function handleCliStatus(ctx, _req, res) {
|
|
14072
15005
|
if (!ctx.instanceManager) {
|
|
14073
15006
|
ctx.json(res, 503, { error: "InstanceManager not available (daemon not fully initialized)" });
|
|
@@ -14206,12 +15139,14 @@ async function handleCliDebug(ctx, type, _req, res) {
|
|
|
14206
15139
|
status: target.status,
|
|
14207
15140
|
mode: "mode" in target ? target.mode : void 0
|
|
14208
15141
|
},
|
|
15142
|
+
providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
|
|
14209
15143
|
debug: debugState
|
|
14210
15144
|
});
|
|
14211
15145
|
} else {
|
|
14212
15146
|
ctx.json(res, 200, {
|
|
14213
15147
|
instanceId: target.instanceId,
|
|
14214
15148
|
providerState: target,
|
|
15149
|
+
providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
|
|
14215
15150
|
debug: null,
|
|
14216
15151
|
message: "No debug state available (adapter.getDebugState not found)"
|
|
14217
15152
|
});
|
|
@@ -14220,6 +15155,191 @@ async function handleCliDebug(ctx, type, _req, res) {
|
|
|
14220
15155
|
ctx.json(res, 500, { error: `Debug state failed: ${e.message}` });
|
|
14221
15156
|
}
|
|
14222
15157
|
}
|
|
15158
|
+
async function handleCliTrace(ctx, type, req, res) {
|
|
15159
|
+
if (!ctx.instanceManager) {
|
|
15160
|
+
ctx.json(res, 503, { error: "InstanceManager not available" });
|
|
15161
|
+
return;
|
|
15162
|
+
}
|
|
15163
|
+
const target = findCliTarget(ctx, type);
|
|
15164
|
+
if (!target) {
|
|
15165
|
+
const allStates = ctx.instanceManager.collectAllStates();
|
|
15166
|
+
ctx.json(res, 404, {
|
|
15167
|
+
error: `No running instance for: ${type}`,
|
|
15168
|
+
available: allStates.filter((s) => s.category === "cli" || s.category === "acp").map((s) => s.type)
|
|
15169
|
+
});
|
|
15170
|
+
return;
|
|
15171
|
+
}
|
|
15172
|
+
const instance = ctx.instanceManager.getInstance(target.instanceId);
|
|
15173
|
+
if (!instance) {
|
|
15174
|
+
ctx.json(res, 404, { error: `Instance not found: ${target.instanceId}` });
|
|
15175
|
+
return;
|
|
15176
|
+
}
|
|
15177
|
+
try {
|
|
15178
|
+
const adapter = instance.getAdapter?.() || instance.adapter;
|
|
15179
|
+
const url = new URL(req.url || "/", "http://127.0.0.1");
|
|
15180
|
+
const limit = parseInt(url.searchParams.get("limit") || "120", 10);
|
|
15181
|
+
if (adapter && typeof adapter.getTraceState === "function") {
|
|
15182
|
+
const trace = adapter.getTraceState(limit);
|
|
15183
|
+
const debug = typeof adapter.getDebugState === "function" ? adapter.getDebugState() : null;
|
|
15184
|
+
ctx.json(res, 200, {
|
|
15185
|
+
instanceId: target.instanceId,
|
|
15186
|
+
providerState: {
|
|
15187
|
+
type: target.type,
|
|
15188
|
+
name: target.name,
|
|
15189
|
+
status: target.status,
|
|
15190
|
+
mode: "mode" in target ? target.mode : void 0
|
|
15191
|
+
},
|
|
15192
|
+
providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
|
|
15193
|
+
debug,
|
|
15194
|
+
trace
|
|
15195
|
+
});
|
|
15196
|
+
} else {
|
|
15197
|
+
ctx.json(res, 200, {
|
|
15198
|
+
instanceId: target.instanceId,
|
|
15199
|
+
providerState: target,
|
|
15200
|
+
providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
|
|
15201
|
+
debug: typeof adapter?.getDebugState === "function" ? adapter.getDebugState() : null,
|
|
15202
|
+
trace: null,
|
|
15203
|
+
message: "No trace state available (adapter.getTraceState not found)"
|
|
15204
|
+
});
|
|
15205
|
+
}
|
|
15206
|
+
} catch (e) {
|
|
15207
|
+
ctx.json(res, 500, { error: `Trace state failed: ${e.message}` });
|
|
15208
|
+
}
|
|
15209
|
+
}
|
|
15210
|
+
async function handleCliExercise(ctx, req, res) {
|
|
15211
|
+
try {
|
|
15212
|
+
const body = await ctx.readBody(req);
|
|
15213
|
+
const result = await runCliExerciseInternal(ctx, body || {});
|
|
15214
|
+
ctx.json(res, 200, result);
|
|
15215
|
+
} catch (e) {
|
|
15216
|
+
ctx.json(res, 500, { error: `Exercise failed: ${e.message}` });
|
|
15217
|
+
}
|
|
15218
|
+
}
|
|
15219
|
+
async function handleCliFixtureCapture(ctx, req, res) {
|
|
15220
|
+
try {
|
|
15221
|
+
const body = await ctx.readBody(req);
|
|
15222
|
+
const type = String(body?.type || "");
|
|
15223
|
+
const request = body?.request || {};
|
|
15224
|
+
if (!type) {
|
|
15225
|
+
ctx.json(res, 400, { error: "type required" });
|
|
15226
|
+
return;
|
|
15227
|
+
}
|
|
15228
|
+
if (!request?.text) {
|
|
15229
|
+
ctx.json(res, 400, { error: "request.text required" });
|
|
15230
|
+
return;
|
|
15231
|
+
}
|
|
15232
|
+
const fixtureDir = getCliFixtureDir(ctx, type);
|
|
15233
|
+
fs12.mkdirSync(fixtureDir, { recursive: true });
|
|
15234
|
+
const name = slugifyFixtureName(String(body?.name || `${type}-${Date.now()}`));
|
|
15235
|
+
const result = await runCliExerciseInternal(ctx, { ...request, type });
|
|
15236
|
+
const fixture = {
|
|
15237
|
+
version: 1,
|
|
15238
|
+
kind: "cli-exercise-fixture",
|
|
15239
|
+
name,
|
|
15240
|
+
type,
|
|
15241
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15242
|
+
providerDir: ctx.providerLoader.findProviderDir(type),
|
|
15243
|
+
providerResolution: result?.providerResolution || null,
|
|
15244
|
+
request: { ...request, type },
|
|
15245
|
+
result,
|
|
15246
|
+
assertions: {
|
|
15247
|
+
mustContainAny: Array.isArray(body?.assertions?.mustContainAny) ? body.assertions.mustContainAny : [],
|
|
15248
|
+
mustNotContainAny: Array.isArray(body?.assertions?.mustNotContainAny) ? body.assertions.mustNotContainAny : [],
|
|
15249
|
+
mustMatchAny: Array.isArray(body?.assertions?.mustMatchAny) ? body.assertions.mustMatchAny : [],
|
|
15250
|
+
mustNotMatchAny: Array.isArray(body?.assertions?.mustNotMatchAny) ? body.assertions.mustNotMatchAny : [],
|
|
15251
|
+
lastAssistantMustContainAny: Array.isArray(body?.assertions?.lastAssistantMustContainAny) ? body.assertions.lastAssistantMustContainAny : [],
|
|
15252
|
+
lastAssistantMustNotContainAny: Array.isArray(body?.assertions?.lastAssistantMustNotContainAny) ? body.assertions.lastAssistantMustNotContainAny : [],
|
|
15253
|
+
lastAssistantMustMatchAny: Array.isArray(body?.assertions?.lastAssistantMustMatchAny) ? body.assertions.lastAssistantMustMatchAny : [],
|
|
15254
|
+
lastAssistantMustNotMatchAny: Array.isArray(body?.assertions?.lastAssistantMustNotMatchAny) ? body.assertions.lastAssistantMustNotMatchAny : [],
|
|
15255
|
+
statusesSeen: Array.isArray(body?.assertions?.statusesSeen) ? body.assertions.statusesSeen : void 0,
|
|
15256
|
+
requireNotTimedOut: body?.assertions?.requireNotTimedOut !== false
|
|
15257
|
+
},
|
|
15258
|
+
notes: typeof body?.notes === "string" ? body.notes : void 0
|
|
15259
|
+
};
|
|
15260
|
+
const filePath = path16.join(fixtureDir, `${name}.json`);
|
|
15261
|
+
fs12.writeFileSync(filePath, JSON.stringify(fixture, null, 2));
|
|
15262
|
+
ctx.json(res, 200, {
|
|
15263
|
+
saved: true,
|
|
15264
|
+
name,
|
|
15265
|
+
path: filePath,
|
|
15266
|
+
fixture,
|
|
15267
|
+
verification: {
|
|
15268
|
+
pass: validateCliFixtureResult(result, fixture.assertions).length === 0,
|
|
15269
|
+
failures: validateCliFixtureResult(result, fixture.assertions)
|
|
15270
|
+
}
|
|
15271
|
+
});
|
|
15272
|
+
} catch (e) {
|
|
15273
|
+
ctx.json(res, 500, { error: `Fixture capture failed: ${e.message}` });
|
|
15274
|
+
}
|
|
15275
|
+
}
|
|
15276
|
+
async function handleCliFixtureList(ctx, type, _req, res) {
|
|
15277
|
+
try {
|
|
15278
|
+
const fixtureDir = getCliFixtureDir(ctx, type);
|
|
15279
|
+
if (!fs12.existsSync(fixtureDir)) {
|
|
15280
|
+
ctx.json(res, 200, { fixtures: [], count: 0 });
|
|
15281
|
+
return;
|
|
15282
|
+
}
|
|
15283
|
+
const fixtures = fs12.readdirSync(fixtureDir).filter((file) => file.endsWith(".json")).sort((a, b) => b.localeCompare(a, void 0, { numeric: true, sensitivity: "base" })).map((file) => {
|
|
15284
|
+
const fullPath = path16.join(fixtureDir, file);
|
|
15285
|
+
try {
|
|
15286
|
+
const raw = JSON.parse(fs12.readFileSync(fullPath, "utf-8"));
|
|
15287
|
+
return {
|
|
15288
|
+
name: raw.name || file.replace(/\.json$/i, ""),
|
|
15289
|
+
path: fullPath,
|
|
15290
|
+
createdAt: raw.createdAt || null,
|
|
15291
|
+
notes: raw.notes || null,
|
|
15292
|
+
requestText: raw.request?.text || "",
|
|
15293
|
+
assertions: raw.assertions || {}
|
|
15294
|
+
};
|
|
15295
|
+
} catch {
|
|
15296
|
+
return {
|
|
15297
|
+
name: file.replace(/\.json$/i, ""),
|
|
15298
|
+
path: fullPath,
|
|
15299
|
+
createdAt: null,
|
|
15300
|
+
notes: "Unreadable fixture",
|
|
15301
|
+
requestText: "",
|
|
15302
|
+
assertions: {}
|
|
15303
|
+
};
|
|
15304
|
+
}
|
|
15305
|
+
});
|
|
15306
|
+
ctx.json(res, 200, { fixtures, count: fixtures.length });
|
|
15307
|
+
} catch (e) {
|
|
15308
|
+
ctx.json(res, 500, { error: `Fixture list failed: ${e.message}` });
|
|
15309
|
+
}
|
|
15310
|
+
}
|
|
15311
|
+
async function handleCliFixtureReplay(ctx, req, res) {
|
|
15312
|
+
try {
|
|
15313
|
+
const body = await ctx.readBody(req);
|
|
15314
|
+
const type = String(body?.type || "");
|
|
15315
|
+
const rawName = String(body?.name || "").trim();
|
|
15316
|
+
if (!type || !rawName) {
|
|
15317
|
+
ctx.json(res, 400, { error: "type and name required" });
|
|
15318
|
+
return;
|
|
15319
|
+
}
|
|
15320
|
+
const name = slugifyFixtureName(rawName);
|
|
15321
|
+
const fixture = readCliFixture(ctx, type, name);
|
|
15322
|
+
const result = await runCliExerciseInternal(ctx, {
|
|
15323
|
+
...fixture.request,
|
|
15324
|
+
type
|
|
15325
|
+
});
|
|
15326
|
+
const assertions = {
|
|
15327
|
+
...fixture.assertions,
|
|
15328
|
+
...body?.assertions || {}
|
|
15329
|
+
};
|
|
15330
|
+
const failures = validateCliFixtureResult(result, assertions);
|
|
15331
|
+
ctx.json(res, 200, {
|
|
15332
|
+
replayed: true,
|
|
15333
|
+
pass: failures.length === 0,
|
|
15334
|
+
failures,
|
|
15335
|
+
fixture,
|
|
15336
|
+
result,
|
|
15337
|
+
assertions
|
|
15338
|
+
});
|
|
15339
|
+
} catch (e) {
|
|
15340
|
+
ctx.json(res, 500, { error: `Fixture replay failed: ${e.message}` });
|
|
15341
|
+
}
|
|
15342
|
+
}
|
|
14223
15343
|
async function handleCliResolve(ctx, req, res) {
|
|
14224
15344
|
const body = await ctx.readBody(req);
|
|
14225
15345
|
const { type, buttonIndex, instanceId } = body;
|
|
@@ -14296,9 +15416,29 @@ async function handleCliRaw(ctx, req, res) {
|
|
|
14296
15416
|
}
|
|
14297
15417
|
|
|
14298
15418
|
// src/daemon/dev-auto-implement.ts
|
|
14299
|
-
var
|
|
14300
|
-
var
|
|
15419
|
+
var fs13 = __toESM(require("fs"));
|
|
15420
|
+
var path17 = __toESM(require("path"));
|
|
14301
15421
|
var os17 = __toESM(require("os"));
|
|
15422
|
+
function getAutoImplPid(ctx) {
|
|
15423
|
+
const proc = ctx.autoImplProcess;
|
|
15424
|
+
return proc && typeof proc.pid === "number" && proc.pid > 0 ? proc.pid : null;
|
|
15425
|
+
}
|
|
15426
|
+
function isPidAlive(pid) {
|
|
15427
|
+
try {
|
|
15428
|
+
process.kill(pid, 0);
|
|
15429
|
+
return true;
|
|
15430
|
+
} catch (error) {
|
|
15431
|
+
return error?.code === "EPERM";
|
|
15432
|
+
}
|
|
15433
|
+
}
|
|
15434
|
+
function clearStaleAutoImplState(ctx, reason) {
|
|
15435
|
+
if (!ctx.autoImplStatus.running && !ctx.autoImplProcess) return;
|
|
15436
|
+
const pid = getAutoImplPid(ctx);
|
|
15437
|
+
if (pid && isPidAlive(pid)) return;
|
|
15438
|
+
ctx.log(`Clearing stale auto-implement state: ${reason}${pid ? ` (pid ${pid})` : ""}`);
|
|
15439
|
+
ctx.autoImplProcess = null;
|
|
15440
|
+
ctx.autoImplStatus.running = false;
|
|
15441
|
+
}
|
|
14302
15442
|
function getDefaultAutoImplReference(ctx, category, type) {
|
|
14303
15443
|
if (category === "cli") {
|
|
14304
15444
|
return type === "codex-cli" ? "claude-cli" : "codex-cli";
|
|
@@ -14314,45 +15454,45 @@ function resolveAutoImplReference(ctx, category, requestedReference, targetType)
|
|
|
14314
15454
|
return fallback?.type || null;
|
|
14315
15455
|
}
|
|
14316
15456
|
function getLatestScriptVersionDir(scriptsDir) {
|
|
14317
|
-
if (!
|
|
14318
|
-
const versions =
|
|
15457
|
+
if (!fs13.existsSync(scriptsDir)) return null;
|
|
15458
|
+
const versions = fs13.readdirSync(scriptsDir).filter((d) => {
|
|
14319
15459
|
try {
|
|
14320
|
-
return
|
|
15460
|
+
return fs13.statSync(path17.join(scriptsDir, d)).isDirectory();
|
|
14321
15461
|
} catch {
|
|
14322
15462
|
return false;
|
|
14323
15463
|
}
|
|
14324
15464
|
}).sort((a, b) => b.localeCompare(a, void 0, { numeric: true, sensitivity: "base" }));
|
|
14325
15465
|
if (versions.length === 0) return null;
|
|
14326
|
-
return
|
|
15466
|
+
return path17.join(scriptsDir, versions[0]);
|
|
14327
15467
|
}
|
|
14328
15468
|
function resolveAutoImplWritableProviderDir(ctx, category, type, requestedDir) {
|
|
14329
|
-
const canonicalUserDir =
|
|
14330
|
-
const desiredDir = requestedDir ?
|
|
14331
|
-
const upstreamRoot =
|
|
14332
|
-
if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${
|
|
15469
|
+
const canonicalUserDir = path17.resolve(ctx.providerLoader.getUserProviderDir(category, type));
|
|
15470
|
+
const desiredDir = requestedDir ? path17.resolve(requestedDir) : canonicalUserDir;
|
|
15471
|
+
const upstreamRoot = path17.resolve(ctx.providerLoader.getUpstreamDir());
|
|
15472
|
+
if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path17.sep}`)) {
|
|
14333
15473
|
return { dir: null, reason: `Refusing to write into upstream provider directory: ${desiredDir}` };
|
|
14334
15474
|
}
|
|
14335
|
-
if (
|
|
15475
|
+
if (path17.basename(desiredDir) !== type) {
|
|
14336
15476
|
return { dir: null, reason: `Requested writable provider directory must end with '${type}': ${desiredDir}` };
|
|
14337
15477
|
}
|
|
14338
15478
|
const sourceDir = ctx.findProviderDir(type);
|
|
14339
15479
|
if (!sourceDir) {
|
|
14340
15480
|
return { dir: null, reason: `Provider source directory not found for '${type}'` };
|
|
14341
15481
|
}
|
|
14342
|
-
if (!
|
|
14343
|
-
|
|
14344
|
-
|
|
15482
|
+
if (!fs13.existsSync(desiredDir)) {
|
|
15483
|
+
fs13.mkdirSync(path17.dirname(desiredDir), { recursive: true });
|
|
15484
|
+
fs13.cpSync(sourceDir, desiredDir, { recursive: true });
|
|
14345
15485
|
ctx.log(`Auto-implement writable copy created: ${desiredDir}`);
|
|
14346
15486
|
}
|
|
14347
|
-
const providerJson =
|
|
14348
|
-
if (!
|
|
15487
|
+
const providerJson = path17.join(desiredDir, "provider.json");
|
|
15488
|
+
if (!fs13.existsSync(providerJson)) {
|
|
14349
15489
|
return { dir: null, reason: `provider.json not found in writable provider directory: ${desiredDir}` };
|
|
14350
15490
|
}
|
|
14351
15491
|
try {
|
|
14352
|
-
const providerData = JSON.parse(
|
|
15492
|
+
const providerData = JSON.parse(fs13.readFileSync(providerJson, "utf-8"));
|
|
14353
15493
|
if (providerData.disableUpstream !== true) {
|
|
14354
15494
|
providerData.disableUpstream = true;
|
|
14355
|
-
|
|
15495
|
+
fs13.writeFileSync(providerJson, JSON.stringify(providerData, null, 2));
|
|
14356
15496
|
}
|
|
14357
15497
|
} catch (error) {
|
|
14358
15498
|
return {
|
|
@@ -14365,15 +15505,15 @@ function resolveAutoImplWritableProviderDir(ctx, category, type, requestedDir) {
|
|
|
14365
15505
|
function loadAutoImplReferenceScripts(ctx, referenceType) {
|
|
14366
15506
|
if (!referenceType) return {};
|
|
14367
15507
|
const refDir = ctx.findProviderDir(referenceType);
|
|
14368
|
-
if (!refDir || !
|
|
15508
|
+
if (!refDir || !fs13.existsSync(refDir)) return {};
|
|
14369
15509
|
const referenceScripts = {};
|
|
14370
|
-
const scriptsDir =
|
|
15510
|
+
const scriptsDir = path17.join(refDir, "scripts");
|
|
14371
15511
|
const latestDir = getLatestScriptVersionDir(scriptsDir);
|
|
14372
15512
|
if (!latestDir) return referenceScripts;
|
|
14373
|
-
for (const file of
|
|
15513
|
+
for (const file of fs13.readdirSync(latestDir)) {
|
|
14374
15514
|
if (!file.endsWith(".js")) continue;
|
|
14375
15515
|
try {
|
|
14376
|
-
referenceScripts[file] =
|
|
15516
|
+
referenceScripts[file] = fs13.readFileSync(path17.join(latestDir, file), "utf-8");
|
|
14377
15517
|
} catch {
|
|
14378
15518
|
}
|
|
14379
15519
|
}
|
|
@@ -14381,11 +15521,20 @@ function loadAutoImplReferenceScripts(ctx, referenceType) {
|
|
|
14381
15521
|
}
|
|
14382
15522
|
async function handleAutoImplement(ctx, type, req, res) {
|
|
14383
15523
|
const body = await ctx.readBody(req);
|
|
14384
|
-
const {
|
|
15524
|
+
const {
|
|
15525
|
+
agent = "claude-cli",
|
|
15526
|
+
functions,
|
|
15527
|
+
reference,
|
|
15528
|
+
model,
|
|
15529
|
+
comment,
|
|
15530
|
+
providerDir: requestedProviderDir,
|
|
15531
|
+
verification
|
|
15532
|
+
} = body;
|
|
14385
15533
|
if (!functions || !Array.isArray(functions) || functions.length === 0) {
|
|
14386
15534
|
ctx.json(res, 400, { error: 'functions[] is required (e.g. ["readChat", "sendMessage"])' });
|
|
14387
15535
|
return;
|
|
14388
15536
|
}
|
|
15537
|
+
clearStaleAutoImplState(ctx, "new auto-implement request");
|
|
14389
15538
|
if (ctx.autoImplStatus.running) {
|
|
14390
15539
|
ctx.json(res, 409, { error: "Auto-implement already in progress", type: ctx.autoImplStatus.type });
|
|
14391
15540
|
return;
|
|
@@ -14403,7 +15552,55 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14403
15552
|
return;
|
|
14404
15553
|
}
|
|
14405
15554
|
const providerDir = writableProvider.dir;
|
|
15555
|
+
ctx.autoImplStatus = { running: false, type, progress: [] };
|
|
15556
|
+
if (provider.category === "cli" && verification && (verification.fixtureName || verification.fixtureNames && verification.fixtureNames.length > 0)) {
|
|
15557
|
+
sendAutoImplSSE(ctx, {
|
|
15558
|
+
event: "progress",
|
|
15559
|
+
data: {
|
|
15560
|
+
function: "_preflight",
|
|
15561
|
+
status: "verifying",
|
|
15562
|
+
message: "Running preflight verification before spawning agent..."
|
|
15563
|
+
}
|
|
15564
|
+
});
|
|
15565
|
+
try {
|
|
15566
|
+
const preflight = await runCliAutoImplVerification(ctx, type, verification);
|
|
15567
|
+
sendAutoImplSSE(ctx, { event: "verification", data: preflight });
|
|
15568
|
+
if (preflight.pass) {
|
|
15569
|
+
sendAutoImplSSE(ctx, {
|
|
15570
|
+
event: "complete",
|
|
15571
|
+
data: {
|
|
15572
|
+
success: true,
|
|
15573
|
+
exitCode: 0,
|
|
15574
|
+
functions,
|
|
15575
|
+
message: `\u2705 No-op: exact ${preflight.mode} already passes`,
|
|
15576
|
+
verification: preflight,
|
|
15577
|
+
skipped: true
|
|
15578
|
+
}
|
|
15579
|
+
});
|
|
15580
|
+
ctx.json(res, 200, {
|
|
15581
|
+
started: false,
|
|
15582
|
+
skipped: true,
|
|
15583
|
+
type,
|
|
15584
|
+
functions,
|
|
15585
|
+
providerDir,
|
|
15586
|
+
verification: preflight,
|
|
15587
|
+
message: "Preflight verification already passes. No auto-implement run needed."
|
|
15588
|
+
});
|
|
15589
|
+
return;
|
|
15590
|
+
}
|
|
15591
|
+
} catch (error) {
|
|
15592
|
+
sendAutoImplSSE(ctx, {
|
|
15593
|
+
event: "progress",
|
|
15594
|
+
data: {
|
|
15595
|
+
function: "_preflight",
|
|
15596
|
+
status: "verify_failed",
|
|
15597
|
+
message: `Preflight verification errored, continuing to agent run: ${error?.message || error}`
|
|
15598
|
+
}
|
|
15599
|
+
});
|
|
15600
|
+
}
|
|
15601
|
+
}
|
|
14406
15602
|
try {
|
|
15603
|
+
ctx.autoImplStatus = { running: true, type, progress: ctx.autoImplStatus.progress };
|
|
14407
15604
|
const resolvedReference = resolveAutoImplReference(ctx, provider.category, reference, type);
|
|
14408
15605
|
sendAutoImplSSE(ctx, {
|
|
14409
15606
|
event: "progress",
|
|
@@ -14423,17 +15620,17 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14423
15620
|
}
|
|
14424
15621
|
});
|
|
14425
15622
|
const referenceScripts = loadAutoImplReferenceScripts(ctx, resolvedReference);
|
|
14426
|
-
const prompt = buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, comment, resolvedReference);
|
|
14427
|
-
const tmpDir =
|
|
14428
|
-
if (!
|
|
14429
|
-
const promptFile =
|
|
14430
|
-
|
|
15623
|
+
const prompt = buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, comment, resolvedReference, verification);
|
|
15624
|
+
const tmpDir = path17.join(os17.tmpdir(), "adhdev-autoimpl");
|
|
15625
|
+
if (!fs13.existsSync(tmpDir)) fs13.mkdirSync(tmpDir, { recursive: true });
|
|
15626
|
+
const promptFile = path17.join(tmpDir, `prompt-${type}-${Date.now()}.md`);
|
|
15627
|
+
fs13.writeFileSync(promptFile, prompt, "utf-8");
|
|
14431
15628
|
ctx.log(`Auto-implement prompt written to ${promptFile} (${prompt.length} chars)`);
|
|
14432
15629
|
const agentProvider = ctx.providerLoader.resolve(agent) || ctx.providerLoader.getMeta(agent);
|
|
14433
15630
|
const spawn4 = agentProvider?.spawn;
|
|
14434
15631
|
if (!spawn4?.command) {
|
|
14435
15632
|
try {
|
|
14436
|
-
|
|
15633
|
+
fs13.unlinkSync(promptFile);
|
|
14437
15634
|
} catch {
|
|
14438
15635
|
}
|
|
14439
15636
|
ctx.json(res, 400, { error: `Agent '${agent}' has no spawn config. Select a CLI provider with a spawn configuration.` });
|
|
@@ -14442,7 +15639,8 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14442
15639
|
const agentCategory = agentProvider?.category;
|
|
14443
15640
|
if (agentCategory === "acp") {
|
|
14444
15641
|
sendAutoImplSSE(ctx, { event: "progress", data: { function: "_init", status: "spawning", message: `Spawning ACP agent: ${spawn4.command} ${(spawn4.args || []).join(" ")}` } });
|
|
14445
|
-
ctx.autoImplStatus =
|
|
15642
|
+
ctx.autoImplStatus.running = true;
|
|
15643
|
+
ctx.autoImplStatus.type = type;
|
|
14446
15644
|
const { ClientSideConnection: ClientSideConnection2, ndJsonStream: ndJsonStream2, PROTOCOL_VERSION: PROTOCOL_VERSION2 } = await import("@agentclientprotocol/sdk");
|
|
14447
15645
|
const { Readable: Readable2, Writable: Writable2 } = await import("stream");
|
|
14448
15646
|
const { spawn: spawnFn2 } = await import("child_process");
|
|
@@ -14534,7 +15732,7 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14534
15732
|
} catch {
|
|
14535
15733
|
}
|
|
14536
15734
|
try {
|
|
14537
|
-
|
|
15735
|
+
fs13.unlinkSync(promptFile);
|
|
14538
15736
|
} catch {
|
|
14539
15737
|
}
|
|
14540
15738
|
ctx.log(`Auto-implement (ACP) ${success ? "completed" : "failed"}: ${type} (exit: ${code})`);
|
|
@@ -14612,7 +15810,8 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14612
15810
|
}
|
|
14613
15811
|
}
|
|
14614
15812
|
sendAutoImplSSE(ctx, { event: "progress", data: { function: "_init", status: "spawning", message: `Spawning agent: ${shellCmd.substring(0, 200)}... (prompt: ${prompt.length} chars)` } });
|
|
14615
|
-
ctx.autoImplStatus =
|
|
15813
|
+
ctx.autoImplStatus.running = true;
|
|
15814
|
+
ctx.autoImplStatus.type = type;
|
|
14616
15815
|
const spawnedAt = Date.now();
|
|
14617
15816
|
let child;
|
|
14618
15817
|
let isPty = false;
|
|
@@ -14655,6 +15854,7 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14655
15854
|
let approvalKeys = { 0: "y\r" };
|
|
14656
15855
|
let approvalBuffer = "";
|
|
14657
15856
|
let lastApprovalTime = 0;
|
|
15857
|
+
let completionSignalSeen = false;
|
|
14658
15858
|
try {
|
|
14659
15859
|
const { normalizeCliProviderForRuntime: normalizeCliProviderForRuntime2 } = await Promise.resolve().then(() => (init_provider_cli_adapter(), provider_cli_adapter_exports));
|
|
14660
15860
|
const normalized = normalizeCliProviderForRuntime2(agentProvider);
|
|
@@ -14668,6 +15868,7 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14668
15868
|
approvalBuffer = (approvalBuffer + cleanData).slice(-1500);
|
|
14669
15869
|
const elapsed = Date.now() - spawnedAt;
|
|
14670
15870
|
if (elapsed > 15e3 && cleanData.includes("_PIPELINE_COMPLETE_SIGNAL_")) {
|
|
15871
|
+
completionSignalSeen = true;
|
|
14671
15872
|
ctx.log(`Agent finished task after ${Math.round(elapsed / 1e3)}s. Terminating interactive CLI session to unblock pipeline.`);
|
|
14672
15873
|
sendAutoImplSSE(ctx, { event: "output", data: { chunk: `
|
|
14673
15874
|
[\u{1F916} ADHDev Pipeline] Completion token detected. Proceeding...
|
|
@@ -14691,6 +15892,55 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14691
15892
|
lastApprovalTime = Date.now();
|
|
14692
15893
|
}
|
|
14693
15894
|
};
|
|
15895
|
+
const finalizeCliAutoImpl = async (code) => {
|
|
15896
|
+
ctx.autoImplProcess = null;
|
|
15897
|
+
let success = completionSignalSeen || code === 0;
|
|
15898
|
+
let message = success ? completionSignalSeen && code !== 0 ? "\u2705 Auto-implement complete (completion signal)" : "\u2705 Auto-implement complete" : `\u274C Agent exited (code: ${code})`;
|
|
15899
|
+
let verificationSummary = null;
|
|
15900
|
+
try {
|
|
15901
|
+
ctx.providerLoader.reload();
|
|
15902
|
+
} catch {
|
|
15903
|
+
}
|
|
15904
|
+
if (provider.category === "cli" && verification) {
|
|
15905
|
+
sendAutoImplSSE(ctx, {
|
|
15906
|
+
event: "progress",
|
|
15907
|
+
data: {
|
|
15908
|
+
function: "_verify",
|
|
15909
|
+
status: "running",
|
|
15910
|
+
message: "Running exact post-patch verification..."
|
|
15911
|
+
}
|
|
15912
|
+
});
|
|
15913
|
+
try {
|
|
15914
|
+
verificationSummary = await runCliAutoImplVerification(ctx, type, verification);
|
|
15915
|
+
sendAutoImplSSE(ctx, { event: "verification", data: verificationSummary });
|
|
15916
|
+
success = verificationSummary.pass;
|
|
15917
|
+
message = verificationSummary.pass ? `\u2705 Auto-implement complete (${verificationSummary.mode})` : `\u274C Post-patch verification failed (${verificationSummary.mode}): ${verificationSummary.failures.join("; ") || "unknown failure"}`;
|
|
15918
|
+
} catch (error) {
|
|
15919
|
+
success = false;
|
|
15920
|
+
message = `\u274C Post-patch verification error: ${error?.message || error}`;
|
|
15921
|
+
sendAutoImplSSE(ctx, {
|
|
15922
|
+
event: "verification",
|
|
15923
|
+
data: { pass: false, error: error?.message || String(error) }
|
|
15924
|
+
});
|
|
15925
|
+
}
|
|
15926
|
+
}
|
|
15927
|
+
ctx.autoImplStatus.running = false;
|
|
15928
|
+
sendAutoImplSSE(ctx, {
|
|
15929
|
+
event: "complete",
|
|
15930
|
+
data: {
|
|
15931
|
+
success,
|
|
15932
|
+
exitCode: code,
|
|
15933
|
+
functions,
|
|
15934
|
+
message,
|
|
15935
|
+
verification: verificationSummary
|
|
15936
|
+
}
|
|
15937
|
+
});
|
|
15938
|
+
try {
|
|
15939
|
+
fs13.unlinkSync(promptFile);
|
|
15940
|
+
} catch {
|
|
15941
|
+
}
|
|
15942
|
+
ctx.log(`Auto-implement ${success ? "completed" : "failed"}: ${type} (exit: ${code})${verificationSummary ? ` verify=${verificationSummary.pass ? "pass" : "fail"}` : ""}`);
|
|
15943
|
+
};
|
|
14694
15944
|
if (isPty) {
|
|
14695
15945
|
child.onData((data) => {
|
|
14696
15946
|
stdout += data;
|
|
@@ -14702,21 +15952,7 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14702
15952
|
sendAutoImplSSE(ctx, { event: "output", data: { chunk: data, stream: "stdout" } });
|
|
14703
15953
|
});
|
|
14704
15954
|
child.onExit(({ exitCode: code }) => {
|
|
14705
|
-
|
|
14706
|
-
ctx.autoImplStatus.running = false;
|
|
14707
|
-
const success = code === 0;
|
|
14708
|
-
sendAutoImplSSE(ctx, {
|
|
14709
|
-
event: "complete",
|
|
14710
|
-
data: { success, exitCode: code, functions, message: success ? "\u2705 Auto-implement complete" : `\u274C Agent exited (code: ${code})` }
|
|
14711
|
-
});
|
|
14712
|
-
try {
|
|
14713
|
-
ctx.providerLoader.reload();
|
|
14714
|
-
} catch {
|
|
14715
|
-
}
|
|
14716
|
-
try {
|
|
14717
|
-
fs12.unlinkSync(promptFile);
|
|
14718
|
-
} catch {
|
|
14719
|
-
}
|
|
15955
|
+
void finalizeCliAutoImpl(code);
|
|
14720
15956
|
});
|
|
14721
15957
|
} else {
|
|
14722
15958
|
child.stdout?.on("data", (d) => {
|
|
@@ -14733,27 +15969,7 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14733
15969
|
sendAutoImplSSE(ctx, { event: "output", data: { chunk, stream: "stderr" } });
|
|
14734
15970
|
});
|
|
14735
15971
|
child.on("exit", (code) => {
|
|
14736
|
-
|
|
14737
|
-
ctx.autoImplStatus.running = false;
|
|
14738
|
-
const success = code === 0;
|
|
14739
|
-
sendAutoImplSSE(ctx, {
|
|
14740
|
-
event: "complete",
|
|
14741
|
-
data: {
|
|
14742
|
-
success,
|
|
14743
|
-
exitCode: code,
|
|
14744
|
-
functions,
|
|
14745
|
-
message: success ? "\u2705 Auto-implement complete" : `\u274C Agent exited (code: ${code})`
|
|
14746
|
-
}
|
|
14747
|
-
});
|
|
14748
|
-
try {
|
|
14749
|
-
ctx.providerLoader.reload();
|
|
14750
|
-
} catch {
|
|
14751
|
-
}
|
|
14752
|
-
try {
|
|
14753
|
-
fs12.unlinkSync(promptFile);
|
|
14754
|
-
} catch {
|
|
14755
|
-
}
|
|
14756
|
-
ctx.log(`Auto-implement ${success ? "completed" : "failed"}: ${type} (exit: ${code})`);
|
|
15972
|
+
void finalizeCliAutoImpl(code);
|
|
14757
15973
|
});
|
|
14758
15974
|
}
|
|
14759
15975
|
ctx.json(res, 202, {
|
|
@@ -14770,9 +15986,9 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14770
15986
|
ctx.json(res, 500, { error: `Auto-implement failed: ${e.message}` });
|
|
14771
15987
|
}
|
|
14772
15988
|
}
|
|
14773
|
-
function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, userComment, referenceType) {
|
|
15989
|
+
function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, userComment, referenceType, verification) {
|
|
14774
15990
|
if (provider.category === "cli") {
|
|
14775
|
-
return buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType);
|
|
15991
|
+
return buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType, verification);
|
|
14776
15992
|
}
|
|
14777
15993
|
const lines = [];
|
|
14778
15994
|
lines.push("You are implementing browser automation scripts for an IDE provider.");
|
|
@@ -14797,7 +16013,7 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
|
|
|
14797
16013
|
setMode: "set_mode.js"
|
|
14798
16014
|
};
|
|
14799
16015
|
const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
|
|
14800
|
-
const scriptsDir =
|
|
16016
|
+
const scriptsDir = path17.join(providerDir, "scripts");
|
|
14801
16017
|
const latestScriptsDir = getLatestScriptVersionDir(scriptsDir);
|
|
14802
16018
|
if (latestScriptsDir) {
|
|
14803
16019
|
lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
|
|
@@ -14805,10 +16021,10 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
|
|
|
14805
16021
|
lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
|
|
14806
16022
|
lines.push("These are the ONLY files you are allowed to modify. Replace the TODO stubs with working implementations.");
|
|
14807
16023
|
lines.push("");
|
|
14808
|
-
for (const file of
|
|
16024
|
+
for (const file of fs13.readdirSync(latestScriptsDir)) {
|
|
14809
16025
|
if (file.endsWith(".js") && targetFileNames.has(file)) {
|
|
14810
16026
|
try {
|
|
14811
|
-
const content =
|
|
16027
|
+
const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
|
|
14812
16028
|
lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
|
|
14813
16029
|
lines.push("```javascript");
|
|
14814
16030
|
lines.push(content);
|
|
@@ -14818,14 +16034,14 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
|
|
|
14818
16034
|
}
|
|
14819
16035
|
}
|
|
14820
16036
|
}
|
|
14821
|
-
const refFiles =
|
|
16037
|
+
const refFiles = fs13.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
|
|
14822
16038
|
if (refFiles.length > 0) {
|
|
14823
16039
|
lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
|
|
14824
16040
|
lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
|
|
14825
16041
|
lines.push("");
|
|
14826
16042
|
for (const file of refFiles) {
|
|
14827
16043
|
try {
|
|
14828
|
-
const content =
|
|
16044
|
+
const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
|
|
14829
16045
|
lines.push(`### \`${file}\` \u{1F512}`);
|
|
14830
16046
|
lines.push("```javascript");
|
|
14831
16047
|
lines.push(content);
|
|
@@ -14866,11 +16082,11 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
|
|
|
14866
16082
|
lines.push("");
|
|
14867
16083
|
}
|
|
14868
16084
|
}
|
|
14869
|
-
const docsDir =
|
|
16085
|
+
const docsDir = path17.join(providerDir, "../../docs");
|
|
14870
16086
|
const loadGuide = (name) => {
|
|
14871
16087
|
try {
|
|
14872
|
-
const p =
|
|
14873
|
-
if (
|
|
16088
|
+
const p = path17.join(docsDir, name);
|
|
16089
|
+
if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
|
|
14874
16090
|
} catch {
|
|
14875
16091
|
}
|
|
14876
16092
|
return null;
|
|
@@ -15028,8 +16244,69 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
|
|
|
15028
16244
|
lines.push("Start NOW. Do not ask for permission. Explore the DOM -> Code -> Test.");
|
|
15029
16245
|
return lines.join("\n");
|
|
15030
16246
|
}
|
|
15031
|
-
function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType) {
|
|
16247
|
+
function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType, verification) {
|
|
15032
16248
|
const lines = [];
|
|
16249
|
+
const defaultExercisePayload = {
|
|
16250
|
+
type,
|
|
16251
|
+
workingDir: providerDir,
|
|
16252
|
+
freshSession: true,
|
|
16253
|
+
autoLaunch: true,
|
|
16254
|
+
autoResolveApprovals: true,
|
|
16255
|
+
approvalButtonIndex: 0,
|
|
16256
|
+
timeoutMs: 45e3,
|
|
16257
|
+
traceLimit: 200,
|
|
16258
|
+
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."
|
|
16259
|
+
};
|
|
16260
|
+
const exercisePayload = {
|
|
16261
|
+
...defaultExercisePayload,
|
|
16262
|
+
...verification?.request || {},
|
|
16263
|
+
type,
|
|
16264
|
+
workingDir: providerDir
|
|
16265
|
+
};
|
|
16266
|
+
const exerciseJson = JSON.stringify(exercisePayload).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
|
|
16267
|
+
const verificationInspectFields = verification?.inspectFields?.length ? verification.inspectFields : [
|
|
16268
|
+
"debug.messages",
|
|
16269
|
+
"trace.entries[].payload.parsedLastAssistant",
|
|
16270
|
+
"trace.entries[].payload.lastAssistant"
|
|
16271
|
+
];
|
|
16272
|
+
const verificationMustContainAny = verification?.mustContainAny || [];
|
|
16273
|
+
const verificationMustNotContainAny = verification?.mustNotContainAny || [];
|
|
16274
|
+
const verificationMustMatchAny = verification?.mustMatchAny || [];
|
|
16275
|
+
const verificationMustNotMatchAny = verification?.mustNotMatchAny || [];
|
|
16276
|
+
const verificationLastAssistantMustContainAny = verification?.lastAssistantMustContainAny || [];
|
|
16277
|
+
const verificationLastAssistantMustNotContainAny = verification?.lastAssistantMustNotContainAny || [];
|
|
16278
|
+
const verificationLastAssistantMustMatchAny = verification?.lastAssistantMustMatchAny || [];
|
|
16279
|
+
const verificationLastAssistantMustNotMatchAny = verification?.lastAssistantMustNotMatchAny || [];
|
|
16280
|
+
const quotedMustContain = verificationMustContainAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16281
|
+
const quotedMustNotContain = verificationMustNotContainAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16282
|
+
const quotedMustMatch = verificationMustMatchAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16283
|
+
const quotedMustNotMatch = verificationMustNotMatchAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16284
|
+
const quotedLastAssistantMustContain = verificationLastAssistantMustContainAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16285
|
+
const quotedLastAssistantMustNotContain = verificationLastAssistantMustNotContainAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16286
|
+
const quotedLastAssistantMustMatch = verificationLastAssistantMustMatchAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16287
|
+
const quotedLastAssistantMustNotMatch = verificationLastAssistantMustNotMatchAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16288
|
+
const fixtureName = verification?.fixtureName || `${type}-provider-fix`;
|
|
16289
|
+
const fixtureNames = Array.isArray(verification?.fixtureNames) ? verification.fixtureNames.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
16290
|
+
const fixtureCaptureJson = JSON.stringify({
|
|
16291
|
+
type,
|
|
16292
|
+
name: fixtureName,
|
|
16293
|
+
request: exercisePayload,
|
|
16294
|
+
assertions: {
|
|
16295
|
+
mustContainAny: verificationMustContainAny,
|
|
16296
|
+
mustNotContainAny: verificationMustNotContainAny,
|
|
16297
|
+
mustMatchAny: verificationMustMatchAny,
|
|
16298
|
+
mustNotMatchAny: verificationMustNotMatchAny,
|
|
16299
|
+
lastAssistantMustContainAny: verificationLastAssistantMustContainAny,
|
|
16300
|
+
lastAssistantMustNotContainAny: verificationLastAssistantMustNotContainAny,
|
|
16301
|
+
lastAssistantMustMatchAny: verificationLastAssistantMustMatchAny,
|
|
16302
|
+
lastAssistantMustNotMatchAny: verificationLastAssistantMustNotMatchAny,
|
|
16303
|
+
requireNotTimedOut: true
|
|
16304
|
+
}
|
|
16305
|
+
}).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
|
|
16306
|
+
const fixtureReplayJson = JSON.stringify({
|
|
16307
|
+
type,
|
|
16308
|
+
name: fixtureName
|
|
16309
|
+
}).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
|
|
15033
16310
|
lines.push("You are implementing PTY parsing scripts for a CLI provider.");
|
|
15034
16311
|
lines.push("Be concise. Do NOT explain your reasoning. Edit files directly and verify with the local DevServer.");
|
|
15035
16312
|
lines.push("");
|
|
@@ -15043,7 +16320,7 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15043
16320
|
parseApproval: "parse_approval.js"
|
|
15044
16321
|
};
|
|
15045
16322
|
const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
|
|
15046
|
-
const scriptsDir =
|
|
16323
|
+
const scriptsDir = path17.join(providerDir, "scripts");
|
|
15047
16324
|
const latestScriptsDir = getLatestScriptVersionDir(scriptsDir);
|
|
15048
16325
|
if (latestScriptsDir) {
|
|
15049
16326
|
lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
|
|
@@ -15051,11 +16328,11 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15051
16328
|
lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
|
|
15052
16329
|
lines.push("These are the ONLY files you are allowed to modify. Replace TODO or heuristic-only logic with working PTY-aware implementations.");
|
|
15053
16330
|
lines.push("");
|
|
15054
|
-
for (const file of
|
|
16331
|
+
for (const file of fs13.readdirSync(latestScriptsDir)) {
|
|
15055
16332
|
if (!file.endsWith(".js")) continue;
|
|
15056
16333
|
if (!targetFileNames.has(file)) continue;
|
|
15057
16334
|
try {
|
|
15058
|
-
const content =
|
|
16335
|
+
const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
|
|
15059
16336
|
lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
|
|
15060
16337
|
lines.push("```javascript");
|
|
15061
16338
|
lines.push(content);
|
|
@@ -15064,14 +16341,14 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15064
16341
|
} catch {
|
|
15065
16342
|
}
|
|
15066
16343
|
}
|
|
15067
|
-
const refFiles =
|
|
16344
|
+
const refFiles = fs13.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
|
|
15068
16345
|
if (refFiles.length > 0) {
|
|
15069
16346
|
lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
|
|
15070
16347
|
lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
|
|
15071
16348
|
lines.push("");
|
|
15072
16349
|
for (const file of refFiles) {
|
|
15073
16350
|
try {
|
|
15074
|
-
const content =
|
|
16351
|
+
const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
|
|
15075
16352
|
lines.push(`### \`${file}\` \u{1F512}`);
|
|
15076
16353
|
lines.push("```javascript");
|
|
15077
16354
|
lines.push(content);
|
|
@@ -15104,17 +16381,17 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15104
16381
|
lines.push("");
|
|
15105
16382
|
}
|
|
15106
16383
|
}
|
|
15107
|
-
const docsDir =
|
|
16384
|
+
const docsDir = path17.join(providerDir, "../../docs");
|
|
15108
16385
|
const loadGuide = (name) => {
|
|
15109
16386
|
try {
|
|
15110
|
-
const p =
|
|
15111
|
-
if (
|
|
16387
|
+
const p = path17.join(docsDir, name);
|
|
16388
|
+
if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
|
|
15112
16389
|
} catch {
|
|
15113
16390
|
}
|
|
15114
16391
|
return null;
|
|
15115
16392
|
};
|
|
15116
16393
|
const providerGuide = loadGuide("PROVIDER_GUIDE.md");
|
|
15117
|
-
if (providerGuide) {
|
|
16394
|
+
if (providerGuide && provider.category !== "cli") {
|
|
15118
16395
|
lines.push("## Documentation: PROVIDER_GUIDE.md");
|
|
15119
16396
|
lines.push("```markdown");
|
|
15120
16397
|
lines.push(providerGuide);
|
|
@@ -15156,6 +16433,11 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15156
16433
|
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.");
|
|
15157
16434
|
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.");
|
|
15158
16435
|
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.");
|
|
16436
|
+
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.');
|
|
16437
|
+
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.");
|
|
16438
|
+
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.');
|
|
16439
|
+
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.");
|
|
16440
|
+
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.");
|
|
15159
16441
|
lines.push("");
|
|
15160
16442
|
lines.push("## Task");
|
|
15161
16443
|
lines.push(`Edit files in \`${providerDir}\` to implement: **${functions.join(", ")}**`);
|
|
@@ -15163,35 +16445,145 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15163
16445
|
lines.push("## Verification API");
|
|
15164
16446
|
lines.push("Use the DevServer CLI debug endpoints, not DOM/CDP routes.");
|
|
15165
16447
|
lines.push("");
|
|
15166
|
-
lines.push("### 1.
|
|
16448
|
+
lines.push("### 1. Preferred: run a full autonomous repro");
|
|
16449
|
+
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.");
|
|
15167
16450
|
lines.push("```bash");
|
|
15168
|
-
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/
|
|
16451
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/exercise \\`);
|
|
15169
16452
|
lines.push(' -H "Content-Type: application/json" \\');
|
|
15170
|
-
lines.push(` -d '
|
|
16453
|
+
lines.push(` -d '${exerciseJson}'`);
|
|
15171
16454
|
lines.push("```");
|
|
15172
16455
|
lines.push("");
|
|
16456
|
+
if (verification?.description) {
|
|
16457
|
+
lines.push("Verification intent:");
|
|
16458
|
+
lines.push(verification.description);
|
|
16459
|
+
lines.push("");
|
|
16460
|
+
}
|
|
16461
|
+
lines.push("Read the JSON response carefully. It already includes:");
|
|
16462
|
+
lines.push("1. `instanceId`");
|
|
16463
|
+
lines.push("2. `statusesSeen` and `approvalsResolved`");
|
|
16464
|
+
lines.push("3. `debug` for the final settled state");
|
|
16465
|
+
lines.push("4. `trace.entries` for the repro turn");
|
|
16466
|
+
lines.push("");
|
|
16467
|
+
lines.push("Save the response to a temp file and inspect the exact parsed transcript fields before editing:");
|
|
16468
|
+
lines.push("```bash");
|
|
16469
|
+
lines.push(`EXERCISE_JSON=$(mktemp)`);
|
|
16470
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/exercise \\`);
|
|
16471
|
+
lines.push(' -H "Content-Type: application/json" \\');
|
|
16472
|
+
lines.push(` -d '${exerciseJson}' > "$EXERCISE_JSON"`);
|
|
16473
|
+
lines.push(`jq '{timedOut,statusesSeen,approvalsResolved,inspect:{${verificationInspectFields.map((field, index) => `f${index + 1}: .${field}`).join(", ")}}}' "$EXERCISE_JSON"`);
|
|
16474
|
+
lines.push("```");
|
|
16475
|
+
lines.push("");
|
|
16476
|
+
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) {
|
|
16477
|
+
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.");
|
|
16478
|
+
lines.push("```bash");
|
|
16479
|
+
if (verificationMustContainAny.length > 0) {
|
|
16480
|
+
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"`);
|
|
16481
|
+
}
|
|
16482
|
+
if (verificationMustNotContainAny.length > 0) {
|
|
16483
|
+
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"`);
|
|
16484
|
+
}
|
|
16485
|
+
if (verificationMustMatchAny.length > 0) {
|
|
16486
|
+
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"`);
|
|
16487
|
+
}
|
|
16488
|
+
if (verificationMustNotMatchAny.length > 0) {
|
|
16489
|
+
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"`);
|
|
16490
|
+
}
|
|
16491
|
+
if (verificationLastAssistantMustContainAny.length > 0) {
|
|
16492
|
+
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"`);
|
|
16493
|
+
}
|
|
16494
|
+
if (verificationLastAssistantMustNotContainAny.length > 0) {
|
|
16495
|
+
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"`);
|
|
16496
|
+
}
|
|
16497
|
+
if (verificationLastAssistantMustMatchAny.length > 0) {
|
|
16498
|
+
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"`);
|
|
16499
|
+
}
|
|
16500
|
+
if (verificationLastAssistantMustNotMatchAny.length > 0) {
|
|
16501
|
+
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"`);
|
|
16502
|
+
}
|
|
16503
|
+
lines.push("```");
|
|
16504
|
+
lines.push("");
|
|
16505
|
+
}
|
|
16506
|
+
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.");
|
|
16507
|
+
lines.push("");
|
|
16508
|
+
lines.push("### 1b. Persist or replay the exact repro as a reusable fixture");
|
|
16509
|
+
if (fixtureNames.length > 0) {
|
|
16510
|
+
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(", ")}.`);
|
|
16511
|
+
for (const name of fixtureNames) {
|
|
16512
|
+
const replayJson = JSON.stringify({ type, name }).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
|
|
16513
|
+
lines.push("```bash");
|
|
16514
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/replay \\`);
|
|
16515
|
+
lines.push(' -H "Content-Type: application/json" \\');
|
|
16516
|
+
lines.push(` -d '${replayJson}'`);
|
|
16517
|
+
lines.push("```");
|
|
16518
|
+
lines.push("");
|
|
16519
|
+
}
|
|
16520
|
+
lines.push("Do not create new fixtures unless one of the listed fixtures is missing or stale.");
|
|
16521
|
+
} else if (verification?.fixtureName) {
|
|
16522
|
+
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.`);
|
|
16523
|
+
lines.push("```bash");
|
|
16524
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/replay \\`);
|
|
16525
|
+
lines.push(' -H "Content-Type: application/json" \\');
|
|
16526
|
+
lines.push(` -d '${fixtureReplayJson}'`);
|
|
16527
|
+
lines.push("```");
|
|
16528
|
+
lines.push("");
|
|
16529
|
+
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.");
|
|
16530
|
+
lines.push("```bash");
|
|
16531
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/capture \\`);
|
|
16532
|
+
lines.push(' -H "Content-Type: application/json" \\');
|
|
16533
|
+
lines.push(` -d '${fixtureCaptureJson}'`);
|
|
16534
|
+
lines.push("```");
|
|
16535
|
+
} else {
|
|
16536
|
+
lines.push("Capture the exact exercise once before editing. After patching, replay THIS fixture and do not declare success unless replay passes.");
|
|
16537
|
+
lines.push("```bash");
|
|
16538
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/capture \\`);
|
|
16539
|
+
lines.push(' -H "Content-Type: application/json" \\');
|
|
16540
|
+
lines.push(` -d '${fixtureCaptureJson}'`);
|
|
16541
|
+
lines.push("");
|
|
16542
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/replay \\`);
|
|
16543
|
+
lines.push(' -H "Content-Type: application/json" \\');
|
|
16544
|
+
lines.push(` -d '${fixtureReplayJson}'`);
|
|
16545
|
+
lines.push("```");
|
|
16546
|
+
}
|
|
16547
|
+
lines.push("");
|
|
16548
|
+
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.");
|
|
16549
|
+
lines.push("");
|
|
15173
16550
|
lines.push("### 2. Inspect parsed + raw adapter state");
|
|
15174
16551
|
lines.push("```bash");
|
|
16552
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/launch \\`);
|
|
16553
|
+
lines.push(' -H "Content-Type: application/json" \\');
|
|
16554
|
+
lines.push(` -d '{"type":"${type}","workingDir":"${providerDir.replace(/\\/g, "\\\\")}"}'`);
|
|
15175
16555
|
lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/debug/${type}`);
|
|
16556
|
+
lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/trace/${type}`);
|
|
15176
16557
|
lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/status`);
|
|
15177
16558
|
lines.push("```");
|
|
15178
16559
|
lines.push("");
|
|
16560
|
+
lines.push("The CLI trace endpoint is the primary debugging source. Read it BEFORE editing any parser code.");
|
|
16561
|
+
lines.push("Use the trace timeline to find the latest `settled` or `commit_transcript` frame for the repro turn and inspect these fields first:");
|
|
16562
|
+
lines.push("1. `payload.screenText`");
|
|
16563
|
+
lines.push("2. `payload.detectStatus` and `payload.parsedStatus`");
|
|
16564
|
+
lines.push("3. `payload.parsedLastAssistant`");
|
|
16565
|
+
lines.push("4. `payload.approval` / `payload.parsedActiveModal`");
|
|
16566
|
+
lines.push("5. `payload.rawPreview` only when control-sequence residue matters");
|
|
16567
|
+
lines.push("");
|
|
15179
16568
|
lines.push("The debug payload should be read in this priority order:");
|
|
15180
16569
|
lines.push("1. `screenText` / current visible state");
|
|
15181
16570
|
lines.push("2. parsed `status`, `messages`, `activeModal`");
|
|
15182
16571
|
lines.push("3. `rawBuffer` only for style/control-sequence cues");
|
|
15183
16572
|
lines.push("4. `buffer` only when the current screen is insufficient");
|
|
15184
16573
|
lines.push("");
|
|
15185
|
-
lines.push("
|
|
16574
|
+
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.");
|
|
16575
|
+
lines.push("Do NOT guess based only on the final chat bubble or a truncated UI preview.");
|
|
16576
|
+
lines.push("");
|
|
16577
|
+
lines.push("Extract the current `instanceId` from the exercise, launch, or status response and keep using it below.");
|
|
15186
16578
|
lines.push("");
|
|
15187
|
-
lines.push("### 3.
|
|
16579
|
+
lines.push("### 3. Manual fallback only: send a realistic approval-triggering prompt");
|
|
15188
16580
|
lines.push("```bash");
|
|
15189
16581
|
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/send \\`);
|
|
15190
16582
|
lines.push(' -H "Content-Type: application/json" \\');
|
|
15191
16583
|
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."}'`);
|
|
15192
16584
|
lines.push("```");
|
|
15193
16585
|
lines.push("");
|
|
15194
|
-
lines.push("### 4.
|
|
16586
|
+
lines.push("### 4. Manual fallback only: if approval appears, resolve it until the CLI reaches idle");
|
|
15195
16587
|
lines.push("```bash");
|
|
15196
16588
|
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/resolve \\`);
|
|
15197
16589
|
lines.push(' -H "Content-Type: application/json" \\');
|
|
@@ -15201,10 +16593,14 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15201
16593
|
lines.push(` -d '{"type":"${type}","instanceId":"<INSTANCE_ID>","keys":"1"}'`);
|
|
15202
16594
|
lines.push("```");
|
|
15203
16595
|
lines.push("");
|
|
15204
|
-
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.");
|
|
16596
|
+
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.");
|
|
15205
16597
|
lines.push("");
|
|
15206
16598
|
lines.push("### Patch Discipline");
|
|
15207
16599
|
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.");
|
|
16600
|
+
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.");
|
|
16601
|
+
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.");
|
|
16602
|
+
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.");
|
|
16603
|
+
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.');
|
|
15208
16604
|
lines.push("");
|
|
15209
16605
|
lines.push("### 5. Verify the side effects outside the CLI");
|
|
15210
16606
|
lines.push("```bash");
|
|
@@ -15229,6 +16625,8 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15229
16625
|
lines.push("7. Re-run the debug endpoints after edits. Do NOT finish until the parsed result looks correct.");
|
|
15230
16626
|
lines.push("8. Confirm the parser still works after a redraw or scroll change without duplicating transcript history.");
|
|
15231
16627
|
lines.push("9. Confirm the implementation prefers current-screen signals over stale history when both are present.");
|
|
16628
|
+
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.");
|
|
16629
|
+
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.");
|
|
15232
16630
|
lines.push("");
|
|
15233
16631
|
if (userComment) {
|
|
15234
16632
|
lines.push("## \u26A0\uFE0F User Instructions (HIGH PRIORITY)");
|
|
@@ -15237,10 +16635,11 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15237
16635
|
lines.push(userComment);
|
|
15238
16636
|
lines.push("");
|
|
15239
16637
|
}
|
|
15240
|
-
lines.push("Start NOW. Launch the CLI, inspect PTY state, edit the scripts, and verify via the CLI debug endpoints.");
|
|
16638
|
+
lines.push("Start NOW. Launch the CLI, inspect the trace and PTY state, edit the scripts, and verify via the CLI debug + trace endpoints.");
|
|
15241
16639
|
return lines.join("\n");
|
|
15242
16640
|
}
|
|
15243
16641
|
function handleAutoImplSSE(ctx, type, req, res) {
|
|
16642
|
+
clearStaleAutoImplState(ctx, "SSE connection opened");
|
|
15244
16643
|
res.writeHead(200, {
|
|
15245
16644
|
"Content-Type": "text/event-stream",
|
|
15246
16645
|
"Cache-Control": "no-cache",
|
|
@@ -15262,6 +16661,7 @@ data: ${JSON.stringify(p.data)}
|
|
|
15262
16661
|
});
|
|
15263
16662
|
}
|
|
15264
16663
|
function handleAutoImplCancel(ctx, _type, _req, res) {
|
|
16664
|
+
clearStaleAutoImplState(ctx, "cancel request");
|
|
15265
16665
|
if (ctx.autoImplProcess) {
|
|
15266
16666
|
ctx.autoImplProcess.kill("SIGTERM");
|
|
15267
16667
|
setTimeout(() => {
|
|
@@ -15345,11 +16745,16 @@ var DevServer = class _DevServer {
|
|
|
15345
16745
|
{ method: "GET", pattern: "/api/cli/status", handler: (q, s) => this.handleCliStatus(q, s) },
|
|
15346
16746
|
{ method: "POST", pattern: "/api/cli/launch", handler: (q, s) => this.handleCliLaunch(q, s) },
|
|
15347
16747
|
{ method: "POST", pattern: "/api/cli/send", handler: (q, s) => this.handleCliSend(q, s) },
|
|
16748
|
+
{ method: "POST", pattern: "/api/cli/exercise", handler: (q, s) => this.handleCliExercise(q, s) },
|
|
16749
|
+
{ method: "POST", pattern: "/api/cli/fixture/capture", handler: (q, s) => this.handleCliFixtureCapture(q, s) },
|
|
16750
|
+
{ method: "POST", pattern: "/api/cli/fixture/replay", handler: (q, s) => this.handleCliFixtureReplay(q, s) },
|
|
15348
16751
|
{ method: "POST", pattern: "/api/cli/resolve", handler: (q, s) => this.handleCliResolve(q, s) },
|
|
15349
16752
|
{ method: "POST", pattern: "/api/cli/raw", handler: (q, s) => this.handleCliRaw(q, s) },
|
|
15350
16753
|
{ method: "POST", pattern: "/api/cli/stop", handler: (q, s) => this.handleCliStop(q, s) },
|
|
15351
16754
|
{ method: "GET", pattern: "/api/cli/events", handler: (q, s) => this.handleCliSSE(q, s) },
|
|
15352
16755
|
{ method: "GET", pattern: /^\/api\/cli\/debug\/([^/]+)$/, handler: (q, s, p) => this.handleCliDebug(p[0], q, s) },
|
|
16756
|
+
{ method: "GET", pattern: /^\/api\/cli\/trace\/([^/]+)$/, handler: (q, s, p) => this.handleCliTrace(p[0], q, s) },
|
|
16757
|
+
{ method: "GET", pattern: /^\/api\/cli\/fixtures\/([^/]+)$/, handler: (q, s, p) => this.handleCliFixtureList(p[0], q, s) },
|
|
15353
16758
|
// Dynamic routes (provider :type param)
|
|
15354
16759
|
{ method: "POST", pattern: /^\/api\/providers\/([^/]+)\/script$/, handler: (q, s, p) => this.handleRunScript(p[0], q, s) },
|
|
15355
16760
|
{ method: "GET", pattern: /^\/api\/providers\/([^/]+)\/files$/, handler: (q, s, p) => this.handleListFiles(p[0], q, s) },
|
|
@@ -15383,8 +16788,8 @@ var DevServer = class _DevServer {
|
|
|
15383
16788
|
}
|
|
15384
16789
|
getEndpointList() {
|
|
15385
16790
|
return this.routes.map((r) => {
|
|
15386
|
-
const
|
|
15387
|
-
return `${r.method.padEnd(5)} ${
|
|
16791
|
+
const path19 = typeof r.pattern === "string" ? r.pattern : r.pattern.source.replace(/\\\//g, "/").replace(/\(\[.*?\]\+\)/g, ":type").replace(/[\^$]/g, "");
|
|
16792
|
+
return `${r.method.padEnd(5)} ${path19}`;
|
|
15388
16793
|
});
|
|
15389
16794
|
}
|
|
15390
16795
|
async start(port = DEV_SERVER_PORT) {
|
|
@@ -15666,12 +17071,12 @@ var DevServer = class _DevServer {
|
|
|
15666
17071
|
// ─── DevConsole SPA ───
|
|
15667
17072
|
getConsoleDistDir() {
|
|
15668
17073
|
const candidates = [
|
|
15669
|
-
|
|
15670
|
-
|
|
15671
|
-
|
|
17074
|
+
path18.resolve(__dirname, "../../web-devconsole/dist"),
|
|
17075
|
+
path18.resolve(__dirname, "../../../web-devconsole/dist"),
|
|
17076
|
+
path18.join(process.cwd(), "packages/web-devconsole/dist")
|
|
15672
17077
|
];
|
|
15673
17078
|
for (const dir of candidates) {
|
|
15674
|
-
if (
|
|
17079
|
+
if (fs14.existsSync(path18.join(dir, "index.html"))) return dir;
|
|
15675
17080
|
}
|
|
15676
17081
|
return null;
|
|
15677
17082
|
}
|
|
@@ -15681,9 +17086,9 @@ var DevServer = class _DevServer {
|
|
|
15681
17086
|
this.json(res, 500, { error: "DevConsole not found. Run: npm run build -w packages/web-devconsole" });
|
|
15682
17087
|
return;
|
|
15683
17088
|
}
|
|
15684
|
-
const htmlPath =
|
|
17089
|
+
const htmlPath = path18.join(distDir, "index.html");
|
|
15685
17090
|
try {
|
|
15686
|
-
const html =
|
|
17091
|
+
const html = fs14.readFileSync(htmlPath, "utf-8");
|
|
15687
17092
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
15688
17093
|
res.end(html);
|
|
15689
17094
|
} catch (e) {
|
|
@@ -15706,15 +17111,15 @@ var DevServer = class _DevServer {
|
|
|
15706
17111
|
this.json(res, 404, { error: "Not found" });
|
|
15707
17112
|
return;
|
|
15708
17113
|
}
|
|
15709
|
-
const safePath =
|
|
15710
|
-
const filePath =
|
|
17114
|
+
const safePath = path18.normalize(pathname).replace(/^\.\.\//, "");
|
|
17115
|
+
const filePath = path18.join(distDir, safePath);
|
|
15711
17116
|
if (!filePath.startsWith(distDir)) {
|
|
15712
17117
|
this.json(res, 403, { error: "Forbidden" });
|
|
15713
17118
|
return;
|
|
15714
17119
|
}
|
|
15715
17120
|
try {
|
|
15716
|
-
const content =
|
|
15717
|
-
const ext =
|
|
17121
|
+
const content = fs14.readFileSync(filePath);
|
|
17122
|
+
const ext = path18.extname(filePath);
|
|
15718
17123
|
const contentType = _DevServer.MIME_MAP[ext] || "application/octet-stream";
|
|
15719
17124
|
res.writeHead(200, { "Content-Type": contentType, "Cache-Control": "public, max-age=31536000, immutable" });
|
|
15720
17125
|
res.end(content);
|
|
@@ -15822,14 +17227,14 @@ var DevServer = class _DevServer {
|
|
|
15822
17227
|
const files = [];
|
|
15823
17228
|
const scan = (d, prefix) => {
|
|
15824
17229
|
try {
|
|
15825
|
-
for (const entry of
|
|
17230
|
+
for (const entry of fs14.readdirSync(d, { withFileTypes: true })) {
|
|
15826
17231
|
if (entry.name.startsWith(".") || entry.name.endsWith(".bak")) continue;
|
|
15827
17232
|
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
15828
17233
|
if (entry.isDirectory()) {
|
|
15829
17234
|
files.push({ path: rel, size: 0, type: "dir" });
|
|
15830
|
-
scan(
|
|
17235
|
+
scan(path18.join(d, entry.name), rel);
|
|
15831
17236
|
} else {
|
|
15832
|
-
const stat =
|
|
17237
|
+
const stat = fs14.statSync(path18.join(d, entry.name));
|
|
15833
17238
|
files.push({ path: rel, size: stat.size, type: "file" });
|
|
15834
17239
|
}
|
|
15835
17240
|
}
|
|
@@ -15852,16 +17257,16 @@ var DevServer = class _DevServer {
|
|
|
15852
17257
|
this.json(res, 404, { error: `Provider directory not found: ${type}` });
|
|
15853
17258
|
return;
|
|
15854
17259
|
}
|
|
15855
|
-
const fullPath =
|
|
17260
|
+
const fullPath = path18.resolve(dir, path18.normalize(filePath));
|
|
15856
17261
|
if (!fullPath.startsWith(dir)) {
|
|
15857
17262
|
this.json(res, 403, { error: "Forbidden" });
|
|
15858
17263
|
return;
|
|
15859
17264
|
}
|
|
15860
|
-
if (!
|
|
17265
|
+
if (!fs14.existsSync(fullPath) || fs14.statSync(fullPath).isDirectory()) {
|
|
15861
17266
|
this.json(res, 404, { error: `File not found: ${filePath}` });
|
|
15862
17267
|
return;
|
|
15863
17268
|
}
|
|
15864
|
-
const content =
|
|
17269
|
+
const content = fs14.readFileSync(fullPath, "utf-8");
|
|
15865
17270
|
this.json(res, 200, { type, path: filePath, content, lines: content.split("\n").length });
|
|
15866
17271
|
}
|
|
15867
17272
|
/** POST /api/providers/:type/file — write a file { path, content } */
|
|
@@ -15877,15 +17282,15 @@ var DevServer = class _DevServer {
|
|
|
15877
17282
|
this.json(res, 404, { error: `Provider directory not found: ${type}` });
|
|
15878
17283
|
return;
|
|
15879
17284
|
}
|
|
15880
|
-
const fullPath =
|
|
17285
|
+
const fullPath = path18.resolve(dir, path18.normalize(filePath));
|
|
15881
17286
|
if (!fullPath.startsWith(dir)) {
|
|
15882
17287
|
this.json(res, 403, { error: "Forbidden" });
|
|
15883
17288
|
return;
|
|
15884
17289
|
}
|
|
15885
17290
|
try {
|
|
15886
|
-
if (
|
|
15887
|
-
|
|
15888
|
-
|
|
17291
|
+
if (fs14.existsSync(fullPath)) fs14.copyFileSync(fullPath, fullPath + ".bak");
|
|
17292
|
+
fs14.mkdirSync(path18.dirname(fullPath), { recursive: true });
|
|
17293
|
+
fs14.writeFileSync(fullPath, content, "utf-8");
|
|
15889
17294
|
this.log(`File saved: ${fullPath} (${content.length} chars)`);
|
|
15890
17295
|
this.providerLoader.reload();
|
|
15891
17296
|
this.json(res, 200, { saved: true, path: filePath, chars: content.length });
|
|
@@ -15901,9 +17306,9 @@ var DevServer = class _DevServer {
|
|
|
15901
17306
|
return;
|
|
15902
17307
|
}
|
|
15903
17308
|
for (const name of ["scripts.js", "provider.json"]) {
|
|
15904
|
-
const p =
|
|
15905
|
-
if (
|
|
15906
|
-
const source =
|
|
17309
|
+
const p = path18.join(dir, name);
|
|
17310
|
+
if (fs14.existsSync(p)) {
|
|
17311
|
+
const source = fs14.readFileSync(p, "utf-8");
|
|
15907
17312
|
this.json(res, 200, { type, path: p, source, lines: source.split("\n").length });
|
|
15908
17313
|
return;
|
|
15909
17314
|
}
|
|
@@ -15922,11 +17327,11 @@ var DevServer = class _DevServer {
|
|
|
15922
17327
|
this.json(res, 404, { error: `Provider not found: ${type}` });
|
|
15923
17328
|
return;
|
|
15924
17329
|
}
|
|
15925
|
-
const target =
|
|
15926
|
-
const targetPath =
|
|
17330
|
+
const target = fs14.existsSync(path18.join(dir, "scripts.js")) ? "scripts.js" : "provider.json";
|
|
17331
|
+
const targetPath = path18.join(dir, target);
|
|
15927
17332
|
try {
|
|
15928
|
-
if (
|
|
15929
|
-
|
|
17333
|
+
if (fs14.existsSync(targetPath)) fs14.copyFileSync(targetPath, targetPath + ".bak");
|
|
17334
|
+
fs14.writeFileSync(targetPath, source, "utf-8");
|
|
15930
17335
|
this.log(`Saved provider: ${targetPath} (${source.length} chars)`);
|
|
15931
17336
|
this.providerLoader.reload();
|
|
15932
17337
|
this.json(res, 200, { saved: true, path: targetPath, chars: source.length });
|
|
@@ -16083,21 +17488,21 @@ var DevServer = class _DevServer {
|
|
|
16083
17488
|
}
|
|
16084
17489
|
let targetDir;
|
|
16085
17490
|
targetDir = this.providerLoader.getUserProviderDir(category, type);
|
|
16086
|
-
const jsonPath =
|
|
16087
|
-
if (
|
|
17491
|
+
const jsonPath = path18.join(targetDir, "provider.json");
|
|
17492
|
+
if (fs14.existsSync(jsonPath)) {
|
|
16088
17493
|
this.json(res, 409, { error: `Provider already exists at ${targetDir}`, path: targetDir });
|
|
16089
17494
|
return;
|
|
16090
17495
|
}
|
|
16091
17496
|
try {
|
|
16092
17497
|
const result = generateFiles(type, name, category, { cdpPorts, cli, processName, installPath, binary, extensionId, version, osPaths, processNames });
|
|
16093
|
-
|
|
16094
|
-
|
|
17498
|
+
fs14.mkdirSync(targetDir, { recursive: true });
|
|
17499
|
+
fs14.writeFileSync(jsonPath, result["provider.json"], "utf-8");
|
|
16095
17500
|
const createdFiles = ["provider.json"];
|
|
16096
17501
|
if (result.files) {
|
|
16097
17502
|
for (const [relPath, content] of Object.entries(result.files)) {
|
|
16098
|
-
const fullPath =
|
|
16099
|
-
|
|
16100
|
-
|
|
17503
|
+
const fullPath = path18.join(targetDir, relPath);
|
|
17504
|
+
fs14.mkdirSync(path18.dirname(fullPath), { recursive: true });
|
|
17505
|
+
fs14.writeFileSync(fullPath, content, "utf-8");
|
|
16101
17506
|
createdFiles.push(relPath);
|
|
16102
17507
|
}
|
|
16103
17508
|
}
|
|
@@ -16146,45 +17551,45 @@ var DevServer = class _DevServer {
|
|
|
16146
17551
|
}
|
|
16147
17552
|
// ─── Phase 2: Auto-Implement Backend ───
|
|
16148
17553
|
getLatestScriptVersionDir(scriptsDir) {
|
|
16149
|
-
if (!
|
|
16150
|
-
const versions =
|
|
17554
|
+
if (!fs14.existsSync(scriptsDir)) return null;
|
|
17555
|
+
const versions = fs14.readdirSync(scriptsDir).filter((d) => {
|
|
16151
17556
|
try {
|
|
16152
|
-
return
|
|
17557
|
+
return fs14.statSync(path18.join(scriptsDir, d)).isDirectory();
|
|
16153
17558
|
} catch {
|
|
16154
17559
|
return false;
|
|
16155
17560
|
}
|
|
16156
17561
|
}).sort((a, b) => b.localeCompare(a, void 0, { numeric: true, sensitivity: "base" }));
|
|
16157
17562
|
if (versions.length === 0) return null;
|
|
16158
|
-
return
|
|
17563
|
+
return path18.join(scriptsDir, versions[0]);
|
|
16159
17564
|
}
|
|
16160
17565
|
resolveAutoImplWritableProviderDir(category, type, requestedDir) {
|
|
16161
|
-
const canonicalUserDir =
|
|
16162
|
-
const desiredDir = requestedDir ?
|
|
16163
|
-
const upstreamRoot =
|
|
16164
|
-
if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${
|
|
17566
|
+
const canonicalUserDir = path18.resolve(this.providerLoader.getUserProviderDir(category, type));
|
|
17567
|
+
const desiredDir = requestedDir ? path18.resolve(requestedDir) : canonicalUserDir;
|
|
17568
|
+
const upstreamRoot = path18.resolve(this.providerLoader.getUpstreamDir());
|
|
17569
|
+
if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path18.sep}`)) {
|
|
16165
17570
|
return { dir: null, reason: `Refusing to write into upstream provider directory: ${desiredDir}` };
|
|
16166
17571
|
}
|
|
16167
|
-
if (
|
|
17572
|
+
if (path18.basename(desiredDir) !== type) {
|
|
16168
17573
|
return { dir: null, reason: `Requested writable provider directory must end with '${type}': ${desiredDir}` };
|
|
16169
17574
|
}
|
|
16170
17575
|
const sourceDir = this.findProviderDir(type);
|
|
16171
17576
|
if (!sourceDir) {
|
|
16172
17577
|
return { dir: null, reason: `Provider source directory not found for '${type}'` };
|
|
16173
17578
|
}
|
|
16174
|
-
if (!
|
|
16175
|
-
|
|
16176
|
-
|
|
17579
|
+
if (!fs14.existsSync(desiredDir)) {
|
|
17580
|
+
fs14.mkdirSync(path18.dirname(desiredDir), { recursive: true });
|
|
17581
|
+
fs14.cpSync(sourceDir, desiredDir, { recursive: true });
|
|
16177
17582
|
this.log(`Auto-implement writable copy created: ${desiredDir}`);
|
|
16178
17583
|
}
|
|
16179
|
-
const providerJson =
|
|
16180
|
-
if (!
|
|
17584
|
+
const providerJson = path18.join(desiredDir, "provider.json");
|
|
17585
|
+
if (!fs14.existsSync(providerJson)) {
|
|
16181
17586
|
return { dir: null, reason: `provider.json not found in writable provider directory: ${desiredDir}` };
|
|
16182
17587
|
}
|
|
16183
17588
|
try {
|
|
16184
|
-
const providerData = JSON.parse(
|
|
17589
|
+
const providerData = JSON.parse(fs14.readFileSync(providerJson, "utf-8"));
|
|
16185
17590
|
if (providerData.disableUpstream !== true) {
|
|
16186
17591
|
providerData.disableUpstream = true;
|
|
16187
|
-
|
|
17592
|
+
fs14.writeFileSync(providerJson, JSON.stringify(providerData, null, 2));
|
|
16188
17593
|
}
|
|
16189
17594
|
} catch (error) {
|
|
16190
17595
|
return {
|
|
@@ -16224,7 +17629,7 @@ var DevServer = class _DevServer {
|
|
|
16224
17629
|
setMode: "set_mode.js"
|
|
16225
17630
|
};
|
|
16226
17631
|
const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
|
|
16227
|
-
const scriptsDir =
|
|
17632
|
+
const scriptsDir = path18.join(providerDir, "scripts");
|
|
16228
17633
|
const latestScriptsDir = this.getLatestScriptVersionDir(scriptsDir);
|
|
16229
17634
|
if (latestScriptsDir) {
|
|
16230
17635
|
lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
|
|
@@ -16232,10 +17637,10 @@ var DevServer = class _DevServer {
|
|
|
16232
17637
|
lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
|
|
16233
17638
|
lines.push("These are the ONLY files you are allowed to modify. Replace the TODO stubs with working implementations.");
|
|
16234
17639
|
lines.push("");
|
|
16235
|
-
for (const file of
|
|
17640
|
+
for (const file of fs14.readdirSync(latestScriptsDir)) {
|
|
16236
17641
|
if (file.endsWith(".js") && targetFileNames.has(file)) {
|
|
16237
17642
|
try {
|
|
16238
|
-
const content =
|
|
17643
|
+
const content = fs14.readFileSync(path18.join(latestScriptsDir, file), "utf-8");
|
|
16239
17644
|
lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
|
|
16240
17645
|
lines.push("```javascript");
|
|
16241
17646
|
lines.push(content);
|
|
@@ -16245,14 +17650,14 @@ var DevServer = class _DevServer {
|
|
|
16245
17650
|
}
|
|
16246
17651
|
}
|
|
16247
17652
|
}
|
|
16248
|
-
const refFiles =
|
|
17653
|
+
const refFiles = fs14.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
|
|
16249
17654
|
if (refFiles.length > 0) {
|
|
16250
17655
|
lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
|
|
16251
17656
|
lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
|
|
16252
17657
|
lines.push("");
|
|
16253
17658
|
for (const file of refFiles) {
|
|
16254
17659
|
try {
|
|
16255
|
-
const content =
|
|
17660
|
+
const content = fs14.readFileSync(path18.join(latestScriptsDir, file), "utf-8");
|
|
16256
17661
|
lines.push(`### \`${file}\` \u{1F512}`);
|
|
16257
17662
|
lines.push("```javascript");
|
|
16258
17663
|
lines.push(content);
|
|
@@ -16293,11 +17698,11 @@ var DevServer = class _DevServer {
|
|
|
16293
17698
|
lines.push("");
|
|
16294
17699
|
}
|
|
16295
17700
|
}
|
|
16296
|
-
const docsDir =
|
|
17701
|
+
const docsDir = path18.join(providerDir, "../../docs");
|
|
16297
17702
|
const loadGuide = (name) => {
|
|
16298
17703
|
try {
|
|
16299
|
-
const p =
|
|
16300
|
-
if (
|
|
17704
|
+
const p = path18.join(docsDir, name);
|
|
17705
|
+
if (fs14.existsSync(p)) return fs14.readFileSync(p, "utf-8");
|
|
16301
17706
|
} catch {
|
|
16302
17707
|
}
|
|
16303
17708
|
return null;
|
|
@@ -16470,7 +17875,7 @@ var DevServer = class _DevServer {
|
|
|
16470
17875
|
parseApproval: "parse_approval.js"
|
|
16471
17876
|
};
|
|
16472
17877
|
const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
|
|
16473
|
-
const scriptsDir =
|
|
17878
|
+
const scriptsDir = path18.join(providerDir, "scripts");
|
|
16474
17879
|
const latestScriptsDir = this.getLatestScriptVersionDir(scriptsDir);
|
|
16475
17880
|
if (latestScriptsDir) {
|
|
16476
17881
|
lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
|
|
@@ -16478,11 +17883,11 @@ var DevServer = class _DevServer {
|
|
|
16478
17883
|
lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
|
|
16479
17884
|
lines.push("These are the ONLY files you are allowed to modify. Replace TODO or heuristic-only logic with working PTY-aware implementations.");
|
|
16480
17885
|
lines.push("");
|
|
16481
|
-
for (const file of
|
|
17886
|
+
for (const file of fs14.readdirSync(latestScriptsDir)) {
|
|
16482
17887
|
if (!file.endsWith(".js")) continue;
|
|
16483
17888
|
if (!targetFileNames.has(file)) continue;
|
|
16484
17889
|
try {
|
|
16485
|
-
const content =
|
|
17890
|
+
const content = fs14.readFileSync(path18.join(latestScriptsDir, file), "utf-8");
|
|
16486
17891
|
lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
|
|
16487
17892
|
lines.push("```javascript");
|
|
16488
17893
|
lines.push(content);
|
|
@@ -16491,14 +17896,14 @@ var DevServer = class _DevServer {
|
|
|
16491
17896
|
} catch {
|
|
16492
17897
|
}
|
|
16493
17898
|
}
|
|
16494
|
-
const refFiles =
|
|
17899
|
+
const refFiles = fs14.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
|
|
16495
17900
|
if (refFiles.length > 0) {
|
|
16496
17901
|
lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
|
|
16497
17902
|
lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
|
|
16498
17903
|
lines.push("");
|
|
16499
17904
|
for (const file of refFiles) {
|
|
16500
17905
|
try {
|
|
16501
|
-
const content =
|
|
17906
|
+
const content = fs14.readFileSync(path18.join(latestScriptsDir, file), "utf-8");
|
|
16502
17907
|
lines.push(`### \`${file}\` \u{1F512}`);
|
|
16503
17908
|
lines.push("```javascript");
|
|
16504
17909
|
lines.push(content);
|
|
@@ -16531,11 +17936,11 @@ var DevServer = class _DevServer {
|
|
|
16531
17936
|
lines.push("");
|
|
16532
17937
|
}
|
|
16533
17938
|
}
|
|
16534
|
-
const docsDir =
|
|
17939
|
+
const docsDir = path18.join(providerDir, "../../docs");
|
|
16535
17940
|
const loadGuide = (name) => {
|
|
16536
17941
|
try {
|
|
16537
|
-
const p =
|
|
16538
|
-
if (
|
|
17942
|
+
const p = path18.join(docsDir, name);
|
|
17943
|
+
if (fs14.existsSync(p)) return fs14.readFileSync(p, "utf-8");
|
|
16539
17944
|
} catch {
|
|
16540
17945
|
}
|
|
16541
17946
|
return null;
|
|
@@ -16600,6 +18005,7 @@ var DevServer = class _DevServer {
|
|
|
16600
18005
|
lines.push("### 2. Inspect parsed + raw adapter state");
|
|
16601
18006
|
lines.push("```bash");
|
|
16602
18007
|
lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/debug/${type}`);
|
|
18008
|
+
lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/trace/${type}`);
|
|
16603
18009
|
lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/status`);
|
|
16604
18010
|
lines.push("```");
|
|
16605
18011
|
lines.push("");
|
|
@@ -16734,6 +18140,16 @@ data: ${JSON.stringify(msg.data)}
|
|
|
16734
18140
|
async handleCliSend(req, res) {
|
|
16735
18141
|
return handleCliSend(this, req, res);
|
|
16736
18142
|
}
|
|
18143
|
+
/** POST /api/cli/exercise — launch/send/approve/wait helper for provider-fix loops */
|
|
18144
|
+
async handleCliExercise(req, res) {
|
|
18145
|
+
return handleCliExercise(this, req, res);
|
|
18146
|
+
}
|
|
18147
|
+
async handleCliFixtureCapture(req, res) {
|
|
18148
|
+
return handleCliFixtureCapture(this, req, res);
|
|
18149
|
+
}
|
|
18150
|
+
async handleCliFixtureReplay(req, res) {
|
|
18151
|
+
return handleCliFixtureReplay(this, req, res);
|
|
18152
|
+
}
|
|
16737
18153
|
/** POST /api/cli/stop — stop a running CLI { type } */
|
|
16738
18154
|
async handleCliStop(req, res) {
|
|
16739
18155
|
return handleCliStop(this, req, res);
|
|
@@ -16757,6 +18173,13 @@ data: ${JSON.stringify(msg.data)}
|
|
|
16757
18173
|
async handleCliDebug(type, _req, res) {
|
|
16758
18174
|
return handleCliDebug(this, type, _req, res);
|
|
16759
18175
|
}
|
|
18176
|
+
/** GET /api/cli/trace/:type — recent CLI trace timeline plus current debug snapshot */
|
|
18177
|
+
async handleCliTrace(type, _req, res) {
|
|
18178
|
+
return handleCliTrace(this, type, _req, res);
|
|
18179
|
+
}
|
|
18180
|
+
async handleCliFixtureList(type, _req, res) {
|
|
18181
|
+
return handleCliFixtureList(this, type, _req, res);
|
|
18182
|
+
}
|
|
16760
18183
|
/** POST /api/cli/resolve — resolve an approval modal { type, buttonIndex } */
|
|
16761
18184
|
async handleCliResolve(req, res) {
|
|
16762
18185
|
return handleCliResolve(this, req, res);
|
|
@@ -16927,7 +18350,18 @@ var SessionHostRuntimeTransport = class {
|
|
|
16927
18350
|
});
|
|
16928
18351
|
}
|
|
16929
18352
|
async boot() {
|
|
16930
|
-
|
|
18353
|
+
if (typeof this.options.ensureReady === "function") {
|
|
18354
|
+
await this.options.ensureReady();
|
|
18355
|
+
}
|
|
18356
|
+
try {
|
|
18357
|
+
await this.client.connect();
|
|
18358
|
+
} catch (error) {
|
|
18359
|
+
if (typeof this.options.ensureReady !== "function") {
|
|
18360
|
+
throw error;
|
|
18361
|
+
}
|
|
18362
|
+
await this.options.ensureReady();
|
|
18363
|
+
await this.client.connect();
|
|
18364
|
+
}
|
|
16931
18365
|
this.unsubscribe = this.client.onEvent((event) => this.handleEvent(event));
|
|
16932
18366
|
let record = null;
|
|
16933
18367
|
if (this.options.attachExisting) {
|
|
@@ -17306,8 +18740,8 @@ async function installExtension(ide, extension) {
|
|
|
17306
18740
|
const res = await fetch(extension.vsixUrl);
|
|
17307
18741
|
if (res.ok) {
|
|
17308
18742
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
17309
|
-
const
|
|
17310
|
-
|
|
18743
|
+
const fs15 = await import("fs");
|
|
18744
|
+
fs15.writeFileSync(vsixPath, buffer);
|
|
17311
18745
|
return new Promise((resolve10) => {
|
|
17312
18746
|
const cmd = `"${ide.cliCommand}" --install-extension "${vsixPath}" --force`;
|
|
17313
18747
|
(0, import_child_process10.exec)(cmd, { timeout: 6e4 }, (error, _stdout, stderr) => {
|